Skip to content

fix(server,tools): surface live chat_run/mcp_server tasks (#472) + count shell.exec truncation in bytes (#441) - #482

Draft
TYRMars wants to merge 2 commits into
mainfrom
claude/vibrant-dijkstra-u95inx
Draft

fix(server,tools): surface live chat_run/mcp_server tasks (#472) + count shell.exec truncation in bytes (#441)#482
TYRMars wants to merge 2 commits into
mainfrom
claude/vibrant-dijkstra-u95inx

Conversation

@TYRMars

@TYRMars TYRMars commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Auto-resolving open issues. This PR bundles two independent, self-contained bugfixes (each with regression tests). The branch is constrained to claude/vibrant-dijkstra-u95inx, so both land here.


Fixes #472GET /v1/tasks never surfaced chat runs or unhealthy MCP servers

collectTasks in packages/server/src/tasks-routes.ts skipped its chat_run and mcp_server sources on a stale "not on AppState yet" premise. Both fields have long existed and are populated: AppState.chatRuns (ChatRunRegistry, driven by the WS turn loop) and AppState.mcpManager (McpManager).

Consequences (user-visible):

  • GET /v1/tasks?conversation=<id> returned items: [] for an ordinary chat turn running right now — the background-tasks panel reported "nothing in flight" while the agent was mid-tool-call. The taskMatchesConversation chat_run branch (match by id) was unreachable because no chat_run entries were ever produced.
  • Stopped / unhealthy MCP servers were never surfaced.

Fix: implement both collectors against the live AppState fields, mirroring the Rust origin (collect_tasks): chat runs via chatRuns.list(true) (label Chat · running <tool> / Chat turn, keyed by conversation id); MCP servers via mcpManager.list() (skip cleanly-running idle infra, surface stopped/unhealthy as MCP <prefix> · <status>). Corrected the stale doc comments; added regression tests for both collectors and the chat_run filter path.

Fixes #441shell.exec truncation cap counted UTF-16 code units, not bytes

ShellExecTool.#accumulate (packages/tools/src/shell.ts) capped at #maxBytes (64 KiB) using JS string .length — UTF-16 code units, not bytes. For multibyte output (CJK ≈ 3 bytes/char, emoji 4 bytes / 2 code units) the cap was under-counted 2–4×, so the [... truncated ...] marker never fired and the reported (N bytes) was a code-unit count.

Fix: measure UTF-8 bytes via Buffer.byteLength(line, "utf8") for the cap and total, tracking a running bufBytes to keep accumulation O(n) — matching the Rust origin (line.len()). Added a multibyte regression test.


Testing

  • @jarvis/server tasks-routes: 14/14 pass (4 new). @jarvis/tools shell: 15/15 (1 new); full tools suite 268/268.
  • tsc --noEmit clean for both packages; eslint clean on changed files.
  • Note: 3 @jarvis/server tests (server, ui, workspace-terminal-routes) fail to load in this environment only because the node-pty native module isn't built (postinstall skipped) — a pre-existing environment limitation unrelated to this change; CI builds it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GrTJW6ReXyCgkq4QnN7DAh

claude added 2 commits July 20, 2026 01:24
…units

`ShellExecTool.#accumulate` documented itself as counting "every observed
byte" and capping the captured buffer at `#maxBytes` (default 64 KiB), but
it measured JS string `.length` — UTF-16 code units, not bytes. For
multibyte output (CJK ~3 bytes/char, most emoji 4 bytes / 2 code units)
the cap was under-counted 2-4×, so the `[... truncated ...]` marker never
fired and the reported "(N bytes)" was a code-unit count. A single line of
60k CJK chars (~180 KB) sailed under the 64 KiB cap and flooded the model
context ~3× over the advertised bound with no truncation signal.

Measure UTF-8 bytes via `Buffer.byteLength(line, "utf8")` for both the cap
comparison and `total`, tracking a running `bufBytes` on `StreamBuf` to
keep accumulation O(n). This matches the reported unit and the Rust origin
(`line.len()` = UTF-8 byte length).

Fixes #441

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrTJW6ReXyCgkq4QnN7DAh
`collectTasks` skipped its `chat_run` and `mcp_server` sources on a stale
"not on AppState yet" premise, but both fields have long existed and are
populated: `AppState.chatRuns` (ChatRunRegistry, driven by the WS turn
loop) and `AppState.mcpManager` (McpManager). As a result:

- `GET /v1/tasks?conversation=<id>` returned `items: []` for an ordinary
  chat turn running right now — the background-tasks panel reported
  "nothing in flight" while the agent was mid-tool-call. The
  `taskMatchesConversation` `chat_run` branch (match by id) was
  unreachable because no chat_run entries were ever produced.
- Stopped / unhealthy MCP servers were never surfaced.

Implement both collectors against the live AppState fields, mirroring the
Rust origin (`crates/harness-server/src/tasks_routes.rs::collect_tasks`):

- Chat runs: `chatRuns.list(true)`, active-state guard, label
  "Chat · running <tool>" or "Chat turn", keyed by conversation id.
- MCP servers: `mcpManager.list()`, skip running (idle infra), surface
  stopped/unhealthy with "MCP <prefix> · <status>", pinned to now().

Correct the stale file-header and `taskMatchesConversation` comments.
Adds regression tests for both collectors and the chat_run filter path.

Fixes #472

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrTJW6ReXyCgkq4QnN7DAh
@TYRMars TYRMars changed the title fix(tools): count shell.exec truncation cap in UTF-8 bytes, not code units fix(server,tools): surface live chat_run/mcp_server tasks (#472) + count shell.exec truncation in bytes (#441) Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment