Add --ws: stream a run to a remote UI as a log destination - #114
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`explorbot --ws <url>` (or EXPLORBOT_WS_URL) dials out to a host UI and
streams the run over one WebSocket — logs, activity, and the questions the
run needs answered. Dialling out rather than listening means a child process
the host spawned and a CI bot connecting from elsewhere are the same case.
It is one class in one file, and nothing else in explorbot knows it exists.
It plugs into the two extension points that already exist:
- `Remote implements LogDestination` and registers itself via the logger's
new `addDestination()`. It receives the entry the logger already built, so
the existing `LogType` travels as the frame's level and the host styles
steps, substeps, markdown and errors distinctly instead of scraping them
back out of flattened text.
- `executionController.setInputCallback()` — every ask already goes through
the controller, so the Pilot's `askUser`, the Navigator's failed-login
prompt and `drill_ask` are all answered remotely without any of them
knowing where the answer came from. Installing it is also what stops a
TTY-less child hanging on the readline fallback.
Consequently `isInteractive()` becomes `INK_RUNNING ||
executionController.hasInputCallback()` — "somebody can answer", asked of
the controller rather than of any particular front end.
There are no frame types: a frame is `{type, ts, ...whatever}` and the host
renders what it recognises, so either side can start sending more at any
time. The connection queues while disconnected, reconnects with backoff, and
flushes before exit.
`remote.registerOption(program)` adds the flag through a Commander
preAction hook, so it covers every command including the mounted api/docs
subcommands and the standalone boat bins.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Explorbot Self-RegressionCommit
Attempt details
Session analysis — basic (native): Session AnalysisThe Issues list feature was explored across 5 tests covering creation, search, label filtering, status filtering, and empty state handling. All core flows work correctly: issues can be created and appear in the list, search and filters correctly narrow results, and the empty state displays appropriately for no matches. One test had an automation issue where the "Apply filters" button click failed but the filter still applied via form submission. Coverage
What works
Execution Issues
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
explorbot --ws <url>(orEXPLORBOT_WS_URL) dials out to a host UI and streams the run over one WebSocket — logs, activity, and the questions the run needs answered. Dialling out rather than listening means a child process the host spawned and a CI bot connecting from elsewhere are the same case.Built for the Testeiya app, which spawns a run and renders it as a live card, but it assumes nothing about who is on the other end.
The whole integration is two existing extension points
Remoteis one class in one file. Nothing else in explorbot knows it exists — no bot instance is handed to it, no agent imports it.LogDestination, and registers itself through the logger's newaddDestination(). It receives the entry the logger already built, so the existingLogTypetravels as the frame'sleveland the host styles steps, substeps, markdown and errors distinctly instead of scraping them back out of flattened text.htmlis dropped, ANSI stripped, content capped.executionController.setInputCallback(). Every ask in the codebase already goes through the controller, so the Pilot'saskUser, the Navigator's failed-login prompt anddrill_askare all answered remotely without any of them knowing where the answer came from. Installing it is also what stops a TTY-less child hanging on the readline fallback.Consequently
isInteractive()becomesINK_RUNNING || executionController.hasInputCallback()— "somebody can answer", asked of the controller rather than of any particular front end. That is what unlocks the credential prompts in a bridged run.No frame types
A frame is
{type, ts, ...whatever};send(type, data)puts data on the wire and the host renders what it recognises. Neither side validates the other's shape, so either can start sending more at any time without a version bump.The connection queues while disconnected, reconnects with backoff, and
remote.close(exitCode)flushes before exit.Diff shape
src/remote.tsis new (238 lines). Everything else is 32 lines across 6 files, all generic:src/utils/logger.tsLogDestination, addaddDestination()(idempotent) + the fan-outsrc/execution-controller.tshasInputCallback()/clearInputCallback()src/ai/task-agent.tsisInteractive()asks the controllerbin/explorbot-cli.tsremote.registerOption(program); flush inshowStatsAndExitboat/*/bin/*.tsremote.registerOption(program)src/explorbot.tsand every agent are untouched.remote.registerOption(program)adds the flag through a CommanderpreActionhook, so it covers every command including the mountedapi/docssubcommands and the standalone boat bins.Testing
tests/unit/remote.test.ts— 7 tests against an in-process WebSocket fixture: hello, queue-then-flush ordering, one frame perLogType(with the step's error alongside, andhtmldropped), the ask round-trip throughexecutionController.requestInput, skip, interrupt, an ignored unknown frame, and close reporting the exit code while resolving outstanding asks.Also driven end to end against the real Testeiya app-server: the CLI connects, sends
hello, streams its config and error logs, and the host resolves the run from the child's exit code.Known gap
A run currently sends
hello,log,activity,askandresult. It does not send page state or the test checklist — those live on the per-instancestateManager/Planemitters, and reaching them needs a bot-specific hook rather than a log destination. Left out deliberately; happy to follow up with a general side channel if that data is wanted.🤖 Generated with Claude Code