Skip to content

Stream the docs index docbot generates over the WebSocket - #163

Merged
DavertMik merged 1 commit into
mainfrom
feat/docbot-ws-docs-frame
Aug 30, 2026
Merged

Stream the docs index docbot generates over the WebSocket#163
DavertMik merged 1 commit into
mainfrom
feat/docbot-ws-docs-frame

Conversation

@DavertMik

Copy link
Copy Markdown
Contributor

Docbot was already half-wired to the --ws stream: its bin calls remote.registerOption, and it inherits every frame the shared modules emit — hello, log, activity, state (state-manager), screenshot (action.ts), research (researcher cache) and ask through the execution controller. But every tag('data') call site lives in src/; nothing under boat/ emitted a frame of its own, so docs/index.md — the artifact the whole run produces — never reached a listener.

Changes

  • saveIndex() emits a docs frame (boat/doc-collector/src/docbot.ts) — keeps the rendered markdown in a local, writes it, then tag('data').log('docs', { path, content }), mirroring writeReport() in src/ai/session-analyst.ts. Same {path, content} payload as report, but a new kind rather than reusing it: a UI keeps the last frame per type, and the analyst's report and the spec index are unrelated artifacts.
  • The exit path closes the remote (boat/doc-collector/src/cli.ts) — docs collect ended on a bare process.exit(0), while the main CLI routes exits through showStatsAndExitremote.close(code), which sends result and flushes. Docbot did neither, so a listener never learned the run ended. This is a prerequisite, not a separate nit: saveIndex() fires at the very last moment before exit, so the new frame was the one most likely to die in the socket buffer. close() early-returns when unattached, so no guard is needed.
  • docs/reference/websocket.md — added the docs row to the frame table, which is the contract listeners actually read.

Scope is index.md only. Per-page pages/*.md and state-diagram.mmd stay unstreamed, matching the one-shot-at-end shape of report.

Not fixed here

boat/api-tester has the same missing remote.close() on exit, so apibot runs never send result either. Left alone to keep this PR to its stated purpose, and because that boat has unrelated work in flight.

Testing

bun run format, bun run lint, and bun test tests/unit/doc-collector.test.ts (45 pass) are green.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Twc8quqSCEZNzPn4sK1o55

Docbot registers --ws and inherits every frame the shared modules emit —
hello, log, activity, state, screenshot, research and ask — but nothing
under boat/ ever emitted a frame of its own, so the artifact the whole run
produces never reached a listener.

saveIndex() now emits the rendered index as a `docs` frame the moment it
writes it, mirroring how the analyst emits its report. A new kind rather
than reusing `report`: a UI keeps the last frame per type, and the two are
unrelated artifacts.

The exit path had to move with it. `docs collect` ended on a bare
process.exit(0), while the main CLI routes exits through remote.close(),
which sends `result` and flushes. Since saveIndex() runs at the very end of
the crawl, the new frame was the one most likely to die in the socket
buffer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Twc8quqSCEZNzPn4sK1o55
@DavertMik
DavertMik merged commit 8f075ee into main Aug 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant