Files
MeshBot/ORCHESTRATOR.md
T
kamilkrzywda a0e1c37291 Add GET /api/channels, move endpoints under /api prefix, serve Scalar docs at / (issue #1)
- New GET /api/channels: device Meshtastic channels from FromRadio.channel
  frames during initial sync; psk/keys redacted; map reset on reconnect.
- app.setGlobalPrefix('api'): /status -> /api/status, /send -> /api/send.
- Scalar API docs at / (OpenAPI JSON at /docs-json) via @nestjs/swagger +
  @scalar/nestjs-api-reference; light swagger decorators on endpoints/body.
- MESH_RECONNECT_MS env knob (default 5000 ms).
- v0.1.0: CHANGELOG entry, README/AGENTS/ORCHESTRATOR docs sync.
2026-09-23 19:03:00 +00:00

7.4 KiB

ORCHESTRATOR.md — Operating Manual for Successor Agents

You are taking over the autonomous development loop on MeshBot (NestJS bot that TCP-connects to a Meshtastic serial bridge, decodes protobuf frames, logs inbound text messages and exposes HTTP endpoints to send them). This document captures operational knowledge that is NOT written down in AGENTS.md. Read AGENTS.md first — it defines the mandatory change process and this file complements it.

Credentials are deliberately NOT stored in this file — see §1 for where they live. Never paste token values into any tracked file.

1. Project identity

  • Repo: Gitea at https://git.kkhost.pl → org OpenCode, repo MeshBot (full name OpenCode/MeshBot). Every Gitea API/MCP call uses owner=OpenCode, repo=MeshBot.
  • Local workdir + deploy dir: /home/krzyw/projects/sandbox/mesh-bot (git checkout; docker compose up -d --build deploys from here).
  • Service URL: http://bot.dev.kkhost.pl (Traefik → docker-compose stack on this host, container port 3000 via external network dev_net; no published ports — TLS is terminated upstream of the local dev_traefik and *.dev.kkhost.pl DNS is network-wide). GET /api/status reports bridge connectivity.
  • Gitea API access for git.kkhost.pl: prefer the gitea MCP tools — they are already configured with a token. For raw REST/curl, read the current token from the local .env file at repo root (variable GITEA_TOKEN; create it if missing — .env is gitignored and must never be committed): source .env && curl -s -H "Authorization: token $GITEA_TOKEN" https://git.kkhost.pl/api/v1/user.
  • Current user: kamilkrzywda (id 1).

2. Remote repo workflow

  • origin = https://git.kkhost.pl/OpenCode/MeshBot.git, branch main. Work happens against the remote: pull before starting work (git fetch origin && git reset --hard origin/main), implement issues added to the remote repo (Gitea at git.kkhost.pl, org OpenCode / repo MeshBot), commit + push per completed issue.

3. The loop (per issue)

  1. Small incremental change on a feature branch off main; connected issues may share one branch/PR with a single version bump.
  2. Bump package.json version + add CHANGELOG.md entry at top: ## v<version> — YYYY-MM-DD HH:MM then bullet points of what changed (docs-only changes get patch bumps too).
  3. Local verification ONLY (there is no CI): npm run build, npm test (the whole vitest unit suite must pass — the test count grows over time, so compare against the previous green run, never a hardcoded number), and for container-affecting changes docker compose build.
  4. Push → open PR to main → merge manually once local checks are green (no CI gate, no auto-merge configured); verify via GET /pulls/{n} that state=closed and merged_at is set.
  5. Tag the version on main: git tag v<version> origin/main && git push origin v<version> (after git fetch origin main).
  6. Deploy in a PTY (long task): cd /home/krzyw/projects/sandbox/mesh-bot && git checkout main && git reset --hard origin/main && docker compose up -d --build; then verify curl -s -H "Host: bot.dev.kkhost.pl" http://localhost/api/status returns JSON with the expected host/port ("connected":false is normal while the bridge/device is down — the service retries every 5 s).
  7. Close each issue with a comment: what changed, version, PR number, merge commit SHA, deployed URL.
  8. Repeat for the next open issue: GET /issues?state=open&type=issues — note Gitea lists pull requests in this endpoint too; filter out entries with a pull_request key.

4. Gitea API + MCP quirks (learned the hard way)

  • The gitea MCP tools work for issues/PRs/files/tags IF you pass owner=OpenCode, repo=MeshBot. A wrong owner/repo makes EVERY call return {"message":"not found","url":"https://git.kkhost.pl/api/swagger"} — that swagger URL inside a 404 is the signature of "wrong path", not "Gitea is down".
  • MCP has no working merge method (the endpoint 405s) → merge via REST: POST /repos/OpenCode/MeshBot/pulls/{n}/merge with body {"do":"merge","merge_style":"merge"}.

5. Docker & deploy facts

  • Multi-stage node:22-alpine Dockerfile: build stage runs npm ci + nest build, final stage copies pruned node_modules + dist; entrypoint node dist/main.js.
  • docker-compose.yml has Traefik labels ONLY — no published ports, no websecure/certs. Router rule Host(bot.dev.kkhost.pl) on the web entrypoint → container port 3000, attached to external network dev_net. From the host you verify with a Host header: curl -s -H "Host: bot.dev.kkhost.pl" http://localhost/api/status.
  • The build runs npm ci → package-lock.json must stay in sync with package.json (see AGENTS.md → Other for the host npm quirk).

6. Mesh protocol facts (learned the hard way)

  • Every message in BOTH directions is framed [0x94][0xC3][len MSB][len LSB][protobuf] — big-endian uint16 length, max payload 1024 bytes; payloads are protobuf FromRadio/ToRadio from @meshtastic/protobufs.
  • @meshtastic/protobufs is ESM-only → it is accessed via a cached dynamic import from the CJS app (works on Node ≥ 22.12 require(ESM)). "type":"module" was deliberately removed from package.json — do not re-add it; the whole app is CommonJS.
  • TEXT_MESSAGE_APP portnum = 1 (NOT 3 — 3 is a different app).
  • FromRadio oneof cases are myInfo / configCompleteId / packet; on a MeshPacket, decoded data lives under payloadVariant.value (case 'decoded').
  • The serial bridge accepts only ONE client at a time — a second connection is refused until the first drops. If the bot keeps failing to connect while the port is open, another app is probably holding the bridge.

7. App architecture quick map

  • src/mesh/codec.ts — framing (extractFrames, frame) + protobuf encode/decode (decodeFromRadio, buildTextToRadio); unit tests in codec.spec.ts.
  • src/mesh/mesh.service.ts — TCP connection to the bridge (MESH_HOST/MESH_PORT env, defaults 192.168.1.4:4003), buffer + frame extraction on data, FromRadio dispatch (myInfo → myNodeNum, channel → upsert into channelMap keyed by index — reset on every reconnect, configCompleteId → ready log, packet → text-message logging), fixed-interval reconnect (MESH_RECONNECT_MS, default 5 s).
  • src/mesh/mesh.controller.ts — GET /status, POST /send, GET /channels (all under the /api global prefix set in main.ts; Scalar docs at /, OpenAPI JSON at /docs-json).

8. Finding current state (this file keeps no snapshots)

This manual is meant to stay valid long-term, so it deliberately contains NO version or issue snapshots — they rot. To orient yourself when you take over: git log --oneline -10 (+ CHANGELOG.md once it exists) for what has shipped; GET /issues?state=open&type=issues (filter out entries with a pull_request key) for what is left to do.

9. Standing user instruction & etiquette

  • The user's standing order for this project: work through the issue list autonomously — implement → PR → merge → tag → deploy → close with comment → next — until told to stop.
  • Small incremental changes; each part fully tested (unit) before moving on.
  • Long terminal tasks → pty_* tools (deploys, builds). Background watchers → run_background_process with notify=true.
  • Never push directly to main; never deploy from a feature branch; production deploys are manual only (docker compose up -d --build after tagging).
  • Never commit credentials. Tokens/secrets live in the MCP server config or with the user, never in tracked files (see §1).