Bumps the pi SDK dependencies to 0.80.6 across the CLI, dashboard, engine, and pi-claude-cli packages, and adapts the Claude CLI thinking-effort mapper for the new `max` ThinkingLevel.
- Bump @earendil-works/pi-ai and @earendil-works/pi-coding-agent from ^0.80.5 to ^0.80.6 in packages/cli, packages/dashboard, packages/engine, and packages/pi-claude-cli (dependency/peerDependency/devDependency entries)
- Regenerate pnpm-lock.yaml for the new SDK versions
- Map the new `max` ThinkingLevel in packages/pi-claude-cli/src/thinking-config.ts: non-Opus models downgrade to `high` (effort max unsupported), Opus models map to `max`
- Extend packages/pi-claude-cli/src/__tests__/thinking-config.test.ts with coverage for the `max` level
- Add .changeset/fn-7755-pi-sdk-bump.md (patch) documenting the SDK bump
Files changed:
.changeset/fn-7755-pi-sdk-bump.md | 7 ++
packages/cli/package.json | 4 +-
packages/dashboard/package.json | 2 +-
packages/engine/package.json | 4 +-
packages/pi-claude-cli/package.json | 8 +-
.../src/__tests__/thinking-config.test.ts | 12 +++
packages/pi-claude-cli/src/thinking-config.ts | 10 ++-
pnpm-lock.yaml | 92 +++++++++++-----------
8 files changed, 82 insertions(+), 57 deletions(-)
Fusion-Task-Id: FN-7755
Fusion-Task-Lineage: f6a9084b-dfb9-4ad5-bd99-4627dae4c666
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
The Typecheck CI gate was failing because pi-claude-cli declared pi-ai and
pi-coding-agent as unpinned "*" peers. pi-ai 0.80 was published and, via
hoisting/non-frozen resolution, the bare `@earendil-works/pi-ai` import floated
to 0.80.3 — which moved the top-level `getModels` export to the deprecated
`/compat` shim ("has no exported member named 'getModels'").
Migrate forward to the latest, consistent with cli/engine which already pin
^0.80.3:
- Pin pi-ai and pi-coding-agent to ^0.80.3 (peer + dev) so the whole extension
resolves one pi-ai version; pinning pi-coding-agent too avoids the
AssistantMessageEventStream type skew that a pi-ai-only bump reintroduced.
- Import the canonical `getBuiltinModels` from
`@earendil-works/pi-ai/providers/all` (identical signature to the old
`getModels`; the top-level export is now the deprecated compat alias).
- Update the provider test mock to the new subpath.
Behavior-preserving: getBuiltinModels === getModels. Typecheck, the full
recursive typecheck, and all 347 pi-claude-cli tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- P1 (Greptile): a tool-use break-early turn released the warm connection
(inUse=false) while conn.prompt() was still pending, letting the next turn
launch a concurrent prompt on the same ACP session (protocol corruption).
keepWarm now requires !sawToolCall, so a tool-use turn tears the connection
down like the non-reuse path; only a clean stop turn (prompt fully resolved
before finish) keeps it warm. + test.
- buildBridgeEnv: treat a whitespace-only auth var as absent (v.trim()), so a
blank higher-preference token can't shadow a real lower-preference one and we
never forward a useless blank token. + test.
- Auth-forwarding tests: clear ambient auth vars in beforeEach so a runner-env
token can't shadow the case under test (CodeRabbit).
- Doc: clarify the allow-list never carries API keys by default; the single
FUSION_CLAUDE_ACP_FORWARD_AUTH opt-in (default OFF) is the only exception.
348/348 pass, tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep a warm bridge connection + ACP session across turns of one
conversation (gated by FUSION_CLAUDE_ACP_REUSE=1, default OFF), so
multi-turn lanes skip the cold bridge/claude spawn and session/new
round-trip and send only the latest-turn delta (buildResumePrompt).
A stable router indirection serves each turn's handlers.
Addresses the adversarial review of the reuse path:
- P0: a warm-child death routes failure to the CURRENT owner turn via
router.fail, so a reuse turn fails fast instead of hanging until the
30-min inactivity timeout.
- P1: eviction is cache-identity-aware (evictCachedAcpConn only deletes
the map key when it still points at the entry), so a concurrent cold
turn / stale close handler / idle timer can't evict or kill a newer
live entry's child.
- P1: an empty resume delta cold-starts instead of issuing an empty
prompt that could hang.
- P2: a per-turn token drops cross-turn stray updates on the shared
warm connection.
- The idle reaper is unref'd so it never pins the process.
Default OFF → the cold path is functionally unchanged (reviewer-verified).
Adds multi-turn tests: reuse skips spawn+session/new, flag-off spawns
fresh, fail-fast on warm-child death, empty-resume cold fallback.
346/346 pass, tsc clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- P2: event-bridge handleMessageDelta now consumes cache_read/cache_creation
tokens (parity with handleMessageStart) — the OQ3 usage path carried them but
they were silently dropped, understating cost for cached turns.
- P2: validate the untrusted bridge usage payload — coerce each field to a
finite, non-negative number before forwarding, so a malformed value
(string/NaN/negative) can't corrupt totalTokens/cost.
- Tests: usage now asserts cache tokens + totalTokens; new cases for malformed
usage, tool-use turns reporting zero usage, the ANTHROPIC_AUTH_TOKEN middle
precedence, and that the auth token is read from process.env (never a
caller-supplied value — no token substitution).
- Doc: state the auth-forwarding exposure trade-off in the code comment.
acp-driver 13/13; event-bridge tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Item 2 (OQ3): capture PromptResponse.usage from conn.prompt and feed it into
the bridge before finish(), so ACP-path turns report token usage/cost instead
of always zero. Zero-when-absent is safe; tool-use (break-early) turns
inherently report zero (the prompt result never resolves).
- Item 3 (R17): opt-in headless credential delivery. When
FUSION_CLAUDE_ACP_FORWARD_AUTH=1, buildBridgeEnv forwards a SINGLE Claude auth
token (CLAUDE_CODE_OAUTH_TOKEN > ANTHROPIC_AUTH_TOKEN > ANTHROPIC_API_KEY) from
the operator's launch env so a detached daemon (no login Keychain) can
authenticate. Default OFF — the secure no-secrets posture is unchanged.
acp-driver tests 9/9 (usage + the three auth-opt-in cases); typecheck clean.
Remaining: item 1 (connection reuse / resume latency).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- CodeRabbit: spinner class `spin` -> `animate-spin` (matches the card's other
Loader2 usages).
- CodeRabbit (major): tighten auth-failure detection so it only fires when the
WHOLE turn is the short "Not logged in" message (<=80 chars), not when a long
legitimate answer merely mentions the phrase — avoids false positives.
- CodeRabbit (major): expand the auth-signal test to assert the full invariant —
set on a not-logged-in turn, clear (unlink) on a real response, and NOT flag a
long answer that mentions the phrase.
pi-claude-cli acp-driver 5/5; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Greptile P2: `acp.active` now reflects the ACTUAL dispatch determinant
(FUSION_CLAUDE_ACP, which includes the operator force-override), not the
experimental flag alone — so the status isn't misleading when forced on/off.
- CodeRabbit/Greptile P2: add FNXC:ClaudeAcp comments to the new code blocks
per the AGENTS.md greppable-comment convention.
Already fixed in the prior commit (daa37d08c): the P1 "sticky env" / latch
(applyClaudeAcpEnable now recomputes each call + FUSION_CLAUDE_ACP_FORCE
override) and the enable->disable-on-same-env regression test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the bridged `claude` can't authenticate (detached daemon / no keychain),
the turn returns "Not logged in" instead of a real answer. Rather than silently
relay that, detect it and let the user choose.
- Driver: detect a "Not logged in"-only turn and write a cross-process signal
(fusion-acp-bridge-auth.json); a real response clears it (acp-driver test).
- Dashboard status: GET /providers/claude-cli/status reports
acp.authFailed + authReason from the signal.
- UI: the Claude CLI provider card shows an auth-failure banner with
"Use claude -p" (sets experimentalFeatures.claudeCliAcp=false) and
"I fixed auth — re-test", plus a fix hint (run `claude` to log in).
- Enable resolution now recomputes each call with an operator force-override
(FUSION_CLAUDE_ACP_FORCE), so the "Use -p" fallback takes effect on the next
turn — no restart. claude-acp-enable tests updated.
pi-claude-cli + engine tests green; dashboard typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
streamSimple routes to streamViaAcp ONLY when FUSION_CLAUDE_ACP=1 AND a bridge
path is provided; otherwise stays on the -p streamViaCli path. Covers the three
cases (off / flag-without-path / flag+path) and asserts the bridge path + env
are forwarded. 333/333 pi-claude-cli tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three-reviewer pass (correctness/security/reliability) on the highest-risk file.
P0:
- Break-early now gates on isPiKnownClaudeTool: Claude's internal ToolSearch
(used to load deferred MCP tools) no longer aborts the turn before the real
fn_* call. Surface+break works from both tool_call updates and
request_permission. New test replays the U9 [ToolSearch, fn_task_list] sequence.
- Downgrade a tool_use turn that surfaced zero pi tool calls -> stop (mirrors
provider.ts), so pi never dispatches non-existent tools.
- register the bridge child in the process registry (no orphan on teardown).
- inactivity timeout (30 min, re-armed per chunk) + per-RPC timeouts on
newSession (a hung bridge now ends the stream and dies).
P1:
- capture bridge stderr + child 'close' handler -> surface exit code/stderr
(no more silent, undebuggable failures).
- sanitize untrusted agent output: strip ANSI/control chars, per-chunk +
per-turn caps, bound tool ids/names (no terminal-escape injection / DoS).
- validate bridge path (absolute + exists) before spawn.
- preserve image content blocks in the prompt (flatten-to-text dropped vision).
P2:
- enforce the bridge env allow-list INSIDE the driver (HOME/PATH/terminal only),
not trusting the caller-supplied object.
Documented residual (kill-switch stays OFF until verified): the bridge's
tool-execution ordering and native-tool (Bash/Read/Write) execution-prevention
need a live behavioral test before any lane enables this path.
pi-claude-cli: 330/330 tests green; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds streamViaAcp: a drop-in alternative to streamViaCli that drives Claude
through the claude-code-cli-acp bridge over ACP instead of `claude -p`. Returns
the same AssistantMessageEventStream, so streamSimple dispatches to either
transport behind a kill-switch (FUSION_CLAUDE_ACP=1 + an injected bridge path),
OFF by default — the live `-p` path is byte-for-byte untouched until soak.
- Full-history prompt every turn (buildPrompt) — the ACP path has no --resume (R13).
- Forwards schema-only MCP servers so Claude emits correct tool calls; breaks
early on the first tool_call (cancel turn, surface to pi) so the bridge never
executes Fusion's tools — mirrors the `-p` break-early pattern.
- Translation reuses the tested createEventBridge by synthesizing Claude stream
events from ACP session/updates, sharing pi sequencing + tool-name mapping.
- Bridge env forwards only HOME/PATH so `claude` authenticates from the login
session (R17); never inherited process.env or API keys.
Verified: 3/3 translation unit tests; real-bridge session/update shapes confirmed
(agent_message_chunk text + tool_call); 326/326 existing pi-claude-cli tests green;
typecheck clean.
Remaining for Route A: engine injection of the bridge path (KTD10), U12 picker/
auth/status, U13 workflow verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>