acpmux: live progress for Claude workflows and background agents, and stop

Merged
TE
24d ago
+458-13

Summary

Claude's dynamic workflows and background agents now report live progress through acpmux, and the app can stop one. This is the acpmux half of the GUI work for workflows and background tasks. The web half comes in a separate PR.

  • Workflows are subagents. A Workflow tool call becomes a background subagent of kind workflow. Its tool result only marks the launch, carrying the run's name, summary, task id and transcript dir. A failed script check ends it as failed, and its task_notification ends it.
  • Live progress. Claude's task_progress lines become subagent_progress updates in the subagent model (_meta.acpmux.subagents). Each carries tokens, tool uses, elapsed ms and the current action: a workflow's step, otherwise the agent's progress summary or last tool. A workflow also gets agents {done, started}, counted from its journal.jsonl. That read caps at 4 MiB and runs off the stdout loop.
  • Stop. _acpmux/subagent_stop {sessionId, subagentSessionId} sends Claude's stop_task control request for the subagent's task, and Claude's answer is the reply. The task's task_notification then ends the subagent as cancelled.
  • Who may stop. The app's agent pane connects over the WebSocket listener as LocalApp, and unhandled session methods used to reach the harness only from the unix socket. remote_guard::forwards now lets LocalApp pass exactly _acpmux/subagent_stop. Web and peer connections are still refused. The person gate (harness_forward_check) is unchanged, and hub/person.rs is not touched.

Security notes (relay authorization)

  • Local command or content execution: none. The only params the method reads are subagentSessionId, which is looked up in the translator's own map. Claude receives only a task id the translator already holds, so caller params never reach Claude.
  • Unowned objects: the hub resolves sessionId and rewrites it to that session's agent session, so a stop can reach only a subagent of that session.
  • Local state exposure: none; the reply is {} or Claude's error text.
  • Origins: Web is refused, and so is a peer. LocalApp may pass this one method and no other unhandled method. tests/remote_guard.rs covers all three cases.

Testing

  • claude_stdio/workflow_tests.rs (hosted verify-cmux-tui-hosted --filter workflow_tests):
    • a workflow as a background subagent with progress;
    • a failed script check;
    • a background agent's progress;
    • progress in the subagent model;
    • stop: the stop_task line, the success reply, Claude's refusal, and an unknown subagent.
    • Red on 7eb9b65d (test commit); green on this head (run in progress, results to follow).
  • tests/remote_guard.rs an_unhandled_session_method_reaches_the_harness_only_from_the_unix_socket, extended to check that Web and peer are refused and only LocalApp may stop. Red on a branch with the test and no gate change; green on this head (runs in progress).
  • God-file check: requests.rs does not grow (net 0). Crash ratchet passes.
  • An Opus review found no blockers. Its should-fixes (guard tests, journal read off the stdout loop) and nits are addressed in de305b2b.

Changelog

  • Added: Claude workflows and background agents report live progress (tokens, tool uses, current step, agents done), and the app can stop one.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Claude's dynamic workflows and background agents report live progress through acpmux, and the app can stop one.

A Workflow tool call runs as a background subagent of kind workflow: its launch names the run, a failed script check ends it as failed, and its task_notification ends it. Claude's task_progress lines become subagent_progress updates carrying tokens, tool uses, elapsed ms, and the current action (a workflow's step, else the agent's progress summary or last tool); a workflow also gets agents {done, started}, counted from its journal.jsonl. That read caps at 4 MiB and runs off the stdout loop.

_acpmux/subagent_stop sends Claude's stop_task for a subagent's task and answers with Claude's reply. The app connection may pass this one unhandled method; Web and peer connections are still refused.

Relay authorization

  • The only parameter read is the subagent id, looked up in the translator's own maps; no caller parameter reaches Claude.
  • The hub resolves sessionId to that session's agent session, so a stop reaches only a subagent of that session.
  • tests/remote_guard.rs covers the unix socket, the app, and the Web and peer refusals.