mirror of
https://github.com/hanzoai/chat.git
synced 2026-09-30 20:51:17 +00:00
A tool call's `output` is set when the tool RETURNS, so running code — the
most expensive thing this product does — was also the most silent: a spinner,
then a wall of text. Cloud now appends a command's output to a session log as
the program produces it, and this is the half that asks for it.
The chain, and each link is the smallest thing that could work:
- `POST /v1/sandboxes/run` instead of `/v1/sandboxes/:id/exec`. The same
command through the same code path, and exactly one of them carries a
session to narrate into — `/:id/exec` has no field for one. It is also the
op the fleet publishes to agents as `run_in_sandbox`, so a person driving
a sandbox and an agent driving one share one address.
- `services/Tools/watch.js` opens the session and says where it is, on an
`on_run_step_delta` addressed at the step the browser is already drawing
— the channel the OAuth prompt already uses to attach state to one call
mid-run. It is INJECTED into the tool the way web search's callback is,
so `packages/api` still has no Express in it. Every failure answers an
empty handle: watching is how a run is READ, never how it is run.
- `/v1/chat/runs/{stream,stop}` relays both, resolving the credential
through `currentBearer` like `/v1/chat/ask`. No guests — a session and a
sandbox belong to the org that leased them, and the shared guest key
carries no tenant.
- `useRunLog` tails the feed and `ExecuteCode` renders it until the real
output lands, so the finished result replaces the narration instead of
appearing under a second copy of it.
Stop ends the WORK. The sandbox stays leased, so the checkout, the files and
the log are all still there afterwards — and the control says so, because a
button that might delete your work is one people hesitate over instead of
using.
Threading `run` through `useStepHandler` keeps it across deltas rather than
assigning from each one: `auth` is cleared by omission (that is how a prompt
comes down), but the ids are sent once, before the command starts, so reading
them the same way would drop them on the next frame and close the tail
mid-command.
Proven in a browser against a real backend and the real store
(e2e/specs/live-run.spec.ts): each line asserted BEFORE the next is sent, so
every one is a moment when the tool has not returned; Stop offered, clicked,
and shown acting on the sandbox.