@posthog/agent 2.3.735 → 2.3.736
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +2 -10
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +24 -10
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/adapters/claude/permissions/permission-options.js +6 -4
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/session/jsonl-hydration.js +1 -0
- package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -1
- package/dist/adapters/claude/session/models.js +20 -1
- package/dist/adapters/claude/session/models.js.map +1 -1
- package/dist/adapters/claude/tools.js +4 -2
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/adapters/reasoning-effort.js +3 -1
- package/dist/adapters/reasoning-effort.js.map +1 -1
- package/dist/agent.js +524 -72
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +4 -4
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.js +524 -72
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +524 -72
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +6 -6
- package/src/adapters/claude/UPSTREAM.md +80 -3
- package/src/adapters/claude/claude-agent.ts +201 -18
- package/src/adapters/claude/conversion/sdk-to-acp.ts +182 -16
- package/src/adapters/claude/conversion/task-state.test.ts +338 -0
- package/src/adapters/claude/conversion/task-state.ts +178 -0
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +29 -19
- package/src/adapters/claude/hooks.test.ts +162 -0
- package/src/adapters/claude/hooks.ts +44 -3
- package/src/adapters/claude/permissions/permission-options.ts +7 -2
- package/src/adapters/claude/session/commands.ts +1 -0
- package/src/adapters/claude/session/mcp-config.ts +23 -7
- package/src/adapters/claude/session/model-config.test.ts +60 -0
- package/src/adapters/claude/session/model-config.ts +56 -0
- package/src/adapters/claude/session/models.test.ts +119 -0
- package/src/adapters/claude/session/models.ts +31 -0
- package/src/adapters/claude/session/options.test.ts +1 -0
- package/src/adapters/claude/session/options.ts +29 -1
- package/src/adapters/claude/session/settings.test.ts +102 -1
- package/src/adapters/claude/session/settings.ts +33 -6
- package/src/adapters/claude/tools.ts +4 -2
- package/src/adapters/claude/types.ts +8 -0
- package/src/adapters/codex/codex-agent.ts +2 -2
- package/src/server/agent-server.test.ts +173 -0
- package/src/test/mocks/claude-sdk.ts +4 -0
- package/src/test/native-binary.test.ts +27 -0
- package/dist/claude-cli/cli.js +0 -18412
- package/dist/claude-cli/vendor/audio-capture/arm64-darwin/audio-capture.node +0 -0
- package/dist/claude-cli/vendor/audio-capture/arm64-linux/audio-capture.node +0 -0
- package/dist/claude-cli/vendor/audio-capture/arm64-win32/audio-capture.node +0 -0
- package/dist/claude-cli/vendor/audio-capture/x64-darwin/audio-capture.node +0 -0
- package/dist/claude-cli/vendor/audio-capture/x64-linux/audio-capture.node +0 -0
- package/dist/claude-cli/vendor/audio-capture/x64-win32/audio-capture.node +0 -0
- package/dist/claude-cli/vendor/ripgrep/COPYING +0 -3
- package/dist/claude-cli/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/arm64-win32/rg.exe +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/claude-cli/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/dist/claude-cli/yoga.wasm +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.736",
|
|
4
4
|
"repository": "https://github.com/PostHog/code",
|
|
5
5
|
"description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
6
6
|
"exports": {
|
|
@@ -107,13 +107,13 @@
|
|
|
107
107
|
"typescript": "^5.5.0",
|
|
108
108
|
"vitest": "^2.1.8",
|
|
109
109
|
"@posthog/shared": "1.0.0",
|
|
110
|
-
"@posthog/
|
|
111
|
-
"@posthog/
|
|
110
|
+
"@posthog/git": "1.0.0",
|
|
111
|
+
"@posthog/enricher": "1.0.0"
|
|
112
112
|
},
|
|
113
113
|
"dependencies": {
|
|
114
|
-
"@agentclientprotocol/sdk": "0.
|
|
115
|
-
"@anthropic-ai/claude-agent-sdk": "0.
|
|
116
|
-
"@anthropic-ai/sdk": "0.
|
|
114
|
+
"@agentclientprotocol/sdk": "0.22.1",
|
|
115
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.154",
|
|
116
|
+
"@anthropic-ai/sdk": "0.100.0",
|
|
117
117
|
"@hono/node-server": "^1.19.9",
|
|
118
118
|
"@opentelemetry/api-logs": "^0.208.0",
|
|
119
119
|
"@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
|
|
@@ -5,8 +5,8 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
|
|
|
5
5
|
## Fork Point
|
|
6
6
|
|
|
7
7
|
- **Forked**: v0.10.9, commit `5411e0f4`, Dec 2 2025
|
|
8
|
-
- **Last sync**: v0.
|
|
9
|
-
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.
|
|
8
|
+
- **Last sync**: v0.38.0 + #716, commit `61ebda2`, May 28 2026
|
|
9
|
+
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.3.154, `@agentclientprotocol/sdk` 0.22.1, `@anthropic-ai/sdk` 0.100.0
|
|
10
10
|
|
|
11
11
|
## File Mapping
|
|
12
12
|
|
|
@@ -67,6 +67,83 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
|
|
|
67
67
|
- **Effort level sync** (v0.25.x): `xhigh` level added, `applyFlagSettings` on effort change
|
|
68
68
|
- **Auto permission mode** (v0.25.0): Added to `CODE_EXECUTION_MODES`, available modes, ExitPlanMode options
|
|
69
69
|
|
|
70
|
+
## Changes Ported in v0.38.0 Sync
|
|
71
|
+
|
|
72
|
+
- **SDK bumps**: claude-agent-sdk 0.3.144 -> 0.3.154, anthropic SDK 0.96.0 -> 0.100.0 (ACP SDK
|
|
73
|
+
unchanged at 0.22.1).
|
|
74
|
+
- **Compaction state-flag fix** (#716, a172885): SDK 0.3.154 emits the terminal `status` carrying
|
|
75
|
+
`compact_result` twice for failed compactions. Added a per-turn `compactionInProgress` flag in
|
|
76
|
+
`prompt()` so the user sees a single `Compacting completed.` / `Compacting failed: <reason>`
|
|
77
|
+
chunk. Manual `/compact` outcomes now surface here rather than via `compact_boundary` (which only
|
|
78
|
+
fires when there's content to compact).
|
|
79
|
+
- **System-role guard on user/assistant handler** (#716, a172885): Added an early return in
|
|
80
|
+
`handleUserAssistantMessage` for `message.message.role === "system"`, covering both upstream's
|
|
81
|
+
`<local-command-stdout>` strip branch guard and the broader assistant-handler guard. Avoids
|
|
82
|
+
rendering SDK-injected system reminders as user-visible chunks.
|
|
83
|
+
- **New no-op content block types** (#716, a172885): Added `advisor_tool_result` and
|
|
84
|
+
`mid_conv_system` cases to `processContentChunk` so unknown content blocks don't trip the
|
|
85
|
+
`unreachable` default.
|
|
86
|
+
- **Opus 4.8 model entries** (#718, 98b54a0): Added `claude-opus-4-8` to gateway model maps with
|
|
87
|
+
1M context, effort and xhigh-effort support. MCP injection auto-included (Haiku exclusion only).
|
|
88
|
+
|
|
89
|
+
## Skipped in v0.38.0 Sync
|
|
90
|
+
|
|
91
|
+
- **Remove hide Claude auth flag** (#707, 7ed1daf): Our fork already returns `authMethods: []`
|
|
92
|
+
unconditionally; no flag to remove.
|
|
93
|
+
- **`thinking_tokens` status case** (#716, a172885): Our `handleSystemMessage` switch on
|
|
94
|
+
`status === "compacting"` is non-exhaustive (no default `unreachable`), so unknown status values
|
|
95
|
+
already no-op harmlessly.
|
|
96
|
+
- **Empty CI-retry commit** (#718, 98b54a0): No code change in the commit itself; the model entries
|
|
97
|
+
it carried are ported above.
|
|
98
|
+
- **`MessageDisplay` hook + `SessionStart` reloadSkills/sessionTitle** (SDK 0.3.152): Available in
|
|
99
|
+
the bumped SDK but not wired into our fork; upstream doesn't consume them in #716 either. Defer
|
|
100
|
+
to a focused PR if we want the capability.
|
|
101
|
+
|
|
102
|
+
## Changes Ported in v0.37.0 Sync
|
|
103
|
+
|
|
104
|
+
- **SDK bumps**: claude-agent-sdk 0.2.114 -> 0.3.144, ACP SDK 0.19.0 -> 0.22.1, anthropic SDK 0.89.0 -> 0.96.0
|
|
105
|
+
- **TodoWrite -> Task tools migration** (SDK 0.3.142): Replaced TodoWrite snapshot tool with incremental
|
|
106
|
+
TaskCreate/TaskUpdate/TaskGet/TaskList. Added `conversion/task-state.ts` and `createTaskHook` to mirror the
|
|
107
|
+
SDK `TaskCreated`/`TaskCompleted` hook events into a per-session task map; plan entries are derived from
|
|
108
|
+
Map insertion order (preserves upstream ordering semantics).
|
|
109
|
+
- **MCP_CONNECTION_NONBLOCKING=0** (SDK 0.3.142): SDK changed MCP servers to background-connect by default;
|
|
110
|
+
set env to restore blocking-connect behavior so MCP tools are available on first prompt.
|
|
111
|
+
- **ACP SDK 0.22 breaking changes**: Renamed `unstable_resumeSession` -> `resumeSession`; new
|
|
112
|
+
`McpSdkServerConfig` variant (`type: "sdk"`) in the `McpServerConfig` union. Our
|
|
113
|
+
`parseMcpServers` only accepts `http`/`sse`/stdio entries, so `sdk` falls through and is
|
|
114
|
+
implicitly dropped (no explicit filter needed).
|
|
115
|
+
- **Skills option** (SDK 0.2.133): `'Skill'` in `allowedTools` deprecated; replaced with `skills` option.
|
|
116
|
+
- **Memory recall tool calls** (#703, a0bfb98): Emit a `tool_call` for SDK `memory_recall` events so the
|
|
117
|
+
UI shows what memories were surfaced; addresses phantom MEMORY.md read attempts.
|
|
118
|
+
- **Write diff fix** (#618, 8d7e220): `toolUpdateFromEditToolResponse` now also processes `Write` tool
|
|
119
|
+
responses so overwrites show real diffs instead of optimistic "creation" diffs.
|
|
120
|
+
- **Local-command-stdout render** (#649, 3b9b7d5): Strip marker tags from `<local-command-stdout>` content
|
|
121
|
+
and render remaining prose so custom slash commands and skill expansions reach the UI.
|
|
122
|
+
- **Cancelled vs end_turn** (#694, 2414a6f): `session_state_changed: idle` handler now reports
|
|
123
|
+
`stopReason: "cancelled"` when the session was interrupted.
|
|
124
|
+
- **Recover prompt stream** (#706, 2711f50): After a failed turn, drain the trailing
|
|
125
|
+
`session_state_changed: idle` so the next prompt's first `query.next()` doesn't short-circuit.
|
|
126
|
+
- **additionalDirectories field** (#684, f37e9a0): Accept the official ACP field on session lifecycle
|
|
127
|
+
requests; advertise via `sessionCapabilities.additionalDirectories`. Legacy `_meta.additionalRoots` still
|
|
128
|
+
honored as fallback.
|
|
129
|
+
- **availableModels allowlist** (#637, 867a3a0): `ClaudeCodeSettings.availableModels` array merged-and-deduped
|
|
130
|
+
across settings sources, then applied to gateway model options via `applyAvailableModelsAllowlist`.
|
|
131
|
+
- **Model alias version match** (#702, e1e1c69): Refuse cross-version alias matches in `resolveModelPreference`
|
|
132
|
+
so `claude-opus-4-6` doesn't get copied onto the `opus` alias when it resolves to 4.7.
|
|
133
|
+
- **Hide /clear** (#705, cfce130): `/clear` removed from advertised commands; clients should use
|
|
134
|
+
`session/new` for the same effect.
|
|
135
|
+
- **No-op ping events** (#698, 694221a): `streamEventToAcpNotifications` no-ops `ping` keep-alive events
|
|
136
|
+
instead of falling through to `unreachable` and spamming stderr.
|
|
137
|
+
|
|
138
|
+
## Skipped in v0.37.0 Sync
|
|
139
|
+
|
|
140
|
+
- **Avoid redundant initial model sync** (#704, b275f6f): Our flow already guards `setModel` behind
|
|
141
|
+
`!isResume && resolvedSdkModel !== DEFAULT_MODEL`, so the upstream optimization is redundant.
|
|
142
|
+
- **Default effort option** (#701, 9e259d1): Our effort options are model-class-based rather than
|
|
143
|
+
SDK-supplied; the implicit no-override path already covers the "let SDK decide" case.
|
|
144
|
+
- **Gate auto mode on model support** (#604, ec47d34): Our `auto` mode is gated behind `ALLOW_BYPASS`,
|
|
145
|
+
not per-model `supportsAutoMode`. Per-model gating would be a larger refactor.
|
|
146
|
+
|
|
70
147
|
## Skipped in v0.30.0 Sync
|
|
71
148
|
|
|
72
149
|
- **Separate auth methods** (v0.25.0): PostHog returns empty authMethods
|
|
@@ -75,7 +152,7 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
|
|
|
75
152
|
|
|
76
153
|
## Next Sync
|
|
77
154
|
|
|
78
|
-
1. Check upstream changelog since v0.
|
|
155
|
+
1. Check upstream changelog since v0.37.0
|
|
79
156
|
2. Diff upstream source against PostHog Code using the file mapping above
|
|
80
157
|
3. Port in phases: bug fixes first, then features
|
|
81
158
|
4. After each phase: `pnpm --filter agent typecheck && pnpm --filter agent build && pnpm lint`
|
|
@@ -84,6 +84,11 @@ import {
|
|
|
84
84
|
handleSystemMessage,
|
|
85
85
|
handleUserAssistantMessage,
|
|
86
86
|
} from "./conversion/sdk-to-acp";
|
|
87
|
+
import {
|
|
88
|
+
rehydrateTaskState,
|
|
89
|
+
type TaskState,
|
|
90
|
+
taskStateToPlanEntries,
|
|
91
|
+
} from "./conversion/task-state";
|
|
87
92
|
import type { EnrichedReadCache } from "./hooks";
|
|
88
93
|
import { createLocalToolsMcpServer } from "./mcp/local-tools";
|
|
89
94
|
import {
|
|
@@ -95,6 +100,10 @@ import {
|
|
|
95
100
|
import { canUseTool } from "./permissions/permission-handlers";
|
|
96
101
|
import { getAvailableSlashCommands } from "./session/commands";
|
|
97
102
|
import { parseMcpServers } from "./session/mcp-config";
|
|
103
|
+
import {
|
|
104
|
+
applyAvailableModelsAllowlist,
|
|
105
|
+
resolveInitialModelId,
|
|
106
|
+
} from "./session/model-config";
|
|
98
107
|
import {
|
|
99
108
|
DEFAULT_MODEL,
|
|
100
109
|
getEffortOptions,
|
|
@@ -237,6 +246,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
237
246
|
},
|
|
238
247
|
loadSession: true,
|
|
239
248
|
sessionCapabilities: {
|
|
249
|
+
additionalDirectories: {},
|
|
240
250
|
list: {},
|
|
241
251
|
fork: {},
|
|
242
252
|
resume: {},
|
|
@@ -269,11 +279,19 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
269
279
|
throw RequestError.authRequired();
|
|
270
280
|
}
|
|
271
281
|
|
|
272
|
-
const response = await this.createSession(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
282
|
+
const response = await this.createSession(
|
|
283
|
+
{
|
|
284
|
+
cwd: params.cwd,
|
|
285
|
+
mcpServers: params.mcpServers ?? [],
|
|
286
|
+
additionalDirectories: params.additionalDirectories,
|
|
287
|
+
_meta: params._meta,
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
// Revisit these meta values once we support resume
|
|
291
|
+
resume: (params._meta as NewSessionMeta | undefined)?.claudeCode
|
|
292
|
+
?.options?.resume as string | undefined,
|
|
293
|
+
},
|
|
294
|
+
);
|
|
277
295
|
|
|
278
296
|
return response;
|
|
279
297
|
}
|
|
@@ -285,13 +303,14 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
285
303
|
{
|
|
286
304
|
cwd: params.cwd,
|
|
287
305
|
mcpServers: params.mcpServers ?? [],
|
|
306
|
+
additionalDirectories: params.additionalDirectories,
|
|
288
307
|
_meta: params._meta,
|
|
289
308
|
},
|
|
290
309
|
{ resume: params.sessionId, forkSession: true },
|
|
291
310
|
);
|
|
292
311
|
}
|
|
293
312
|
|
|
294
|
-
async
|
|
313
|
+
async resumeSession(
|
|
295
314
|
params: ResumeSessionRequest,
|
|
296
315
|
): Promise<ResumeSessionResponse> {
|
|
297
316
|
// Reuse existing session if it matches
|
|
@@ -302,6 +321,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
302
321
|
{
|
|
303
322
|
cwd: params.cwd,
|
|
304
323
|
mcpServers: params.mcpServers ?? [],
|
|
324
|
+
additionalDirectories: params.additionalDirectories,
|
|
305
325
|
_meta: params._meta,
|
|
306
326
|
},
|
|
307
327
|
{
|
|
@@ -309,6 +329,8 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
309
329
|
},
|
|
310
330
|
);
|
|
311
331
|
|
|
332
|
+
await this.rehydrateTaskStateFromJsonl(params.sessionId);
|
|
333
|
+
|
|
312
334
|
return response;
|
|
313
335
|
}
|
|
314
336
|
|
|
@@ -321,6 +343,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
321
343
|
{
|
|
322
344
|
cwd: params.cwd,
|
|
323
345
|
mcpServers: params.mcpServers ?? [],
|
|
346
|
+
additionalDirectories: params.additionalDirectories,
|
|
324
347
|
_meta: params._meta,
|
|
325
348
|
},
|
|
326
349
|
{ resume: params.sessionId, skipBackgroundFetches: true },
|
|
@@ -421,6 +444,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
421
444
|
|
|
422
445
|
this.session.promptRunning = true;
|
|
423
446
|
let handedOff = false;
|
|
447
|
+
let errored = false;
|
|
424
448
|
let lastAssistantTotalUsage: number | null = null;
|
|
425
449
|
let lastStreamUsage = {
|
|
426
450
|
input_tokens: 0,
|
|
@@ -428,6 +452,13 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
428
452
|
cache_read_input_tokens: 0,
|
|
429
453
|
cache_creation_input_tokens: 0,
|
|
430
454
|
};
|
|
455
|
+
// Tracks whether we're inside a compaction. The SDK emits the terminal
|
|
456
|
+
// `status` (compact_result success/failed) twice for a single failed
|
|
457
|
+
// compaction, and the two messages are indistinguishable, so we report the
|
|
458
|
+
// outcome only while a compaction is in progress, then clear this. A fresh
|
|
459
|
+
// `compacting` status sets it again, so every distinct compaction (e.g.
|
|
460
|
+
// repeated auto-compactions in a long turn) is still shown.
|
|
461
|
+
let compactionInProgress = false;
|
|
431
462
|
if (this.session.lastContextWindowSize == null) {
|
|
432
463
|
this.session.lastContextWindowSize = this.getContextWindowForModel(
|
|
433
464
|
this.session.modelId ?? "",
|
|
@@ -503,6 +534,54 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
503
534
|
if (message.subtype === "local_command_output") {
|
|
504
535
|
promptReplayed = true;
|
|
505
536
|
}
|
|
537
|
+
if (message.subtype === "status") {
|
|
538
|
+
// The SDK signals manual `/compact` completion with a status
|
|
539
|
+
// message carrying `compact_result`, not the `compact_boundary`
|
|
540
|
+
// message (which only fires when there's content to compact).
|
|
541
|
+
// Gate the user-facing outcome on `compactionInProgress` to
|
|
542
|
+
// dedupe the duplicate terminal status the SDK emits for failed
|
|
543
|
+
// compactions.
|
|
544
|
+
if (message.status === "compacting") {
|
|
545
|
+
compactionInProgress = true;
|
|
546
|
+
// Fall through to handleSystemMessage so the COMPACTING
|
|
547
|
+
// extNotification still fires.
|
|
548
|
+
} else if (
|
|
549
|
+
message.compact_result === "success" &&
|
|
550
|
+
compactionInProgress
|
|
551
|
+
) {
|
|
552
|
+
compactionInProgress = false;
|
|
553
|
+
await this.client.sessionUpdate({
|
|
554
|
+
sessionId: params.sessionId,
|
|
555
|
+
update: {
|
|
556
|
+
sessionUpdate: "agent_message_chunk",
|
|
557
|
+
content: {
|
|
558
|
+
type: "text",
|
|
559
|
+
text: "\n\nCompacting completed.",
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
break;
|
|
564
|
+
} else if (
|
|
565
|
+
message.compact_result === "failed" &&
|
|
566
|
+
compactionInProgress
|
|
567
|
+
) {
|
|
568
|
+
compactionInProgress = false;
|
|
569
|
+
const reason = message.compact_error
|
|
570
|
+
? `: ${message.compact_error}`
|
|
571
|
+
: ".";
|
|
572
|
+
await this.client.sessionUpdate({
|
|
573
|
+
sessionId: params.sessionId,
|
|
574
|
+
update: {
|
|
575
|
+
sessionUpdate: "agent_message_chunk",
|
|
576
|
+
content: {
|
|
577
|
+
type: "text",
|
|
578
|
+
text: `\n\nCompacting failed${reason}`,
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
506
585
|
if (
|
|
507
586
|
message.subtype === "session_state_changed" &&
|
|
508
587
|
(message as Record<string, unknown>).state === "idle"
|
|
@@ -564,7 +643,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
564
643
|
},
|
|
565
644
|
});
|
|
566
645
|
|
|
567
|
-
return {
|
|
646
|
+
return {
|
|
647
|
+
stopReason: this.session.cancelled ? "cancelled" : "end_turn",
|
|
648
|
+
};
|
|
568
649
|
}
|
|
569
650
|
await handleSystemMessage(message, context);
|
|
570
651
|
break;
|
|
@@ -838,6 +919,37 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
838
919
|
}
|
|
839
920
|
throw new Error("Session did not end in result");
|
|
840
921
|
} catch (error) {
|
|
922
|
+
errored = true;
|
|
923
|
+
// A failed turn typically leaves a trailing `session_state_changed: idle`
|
|
924
|
+
// (and possibly more) in the query iterator. If we don't drain it here,
|
|
925
|
+
// the next prompt's first `query.next()` consumes that stale idle and
|
|
926
|
+
// short-circuits to end_turn with zero usage.
|
|
927
|
+
try {
|
|
928
|
+
await this.session.query.interrupt();
|
|
929
|
+
const MAX_DRAIN = 100;
|
|
930
|
+
for (let i = 0; i < MAX_DRAIN; i++) {
|
|
931
|
+
const { value: m, done } = await this.session.query.next();
|
|
932
|
+
if (done || !m) break;
|
|
933
|
+
if (
|
|
934
|
+
m.type === "system" &&
|
|
935
|
+
m.subtype === "session_state_changed" &&
|
|
936
|
+
(m as Record<string, unknown>).state === "idle"
|
|
937
|
+
) {
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
if (i === MAX_DRAIN - 1) {
|
|
941
|
+
this.logger.error(
|
|
942
|
+
`Session ${params.sessionId}: drained ${MAX_DRAIN} messages after error without observing idle`,
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
} catch (drainErr) {
|
|
947
|
+
this.logger.error(
|
|
948
|
+
`Session ${params.sessionId}: failed to drain query after prompt error`,
|
|
949
|
+
{ error: drainErr },
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
|
|
841
953
|
if (error instanceof RequestError || !(error instanceof Error)) {
|
|
842
954
|
throw error;
|
|
843
955
|
}
|
|
@@ -868,10 +980,25 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
868
980
|
this.toolUseStreamCache.clear();
|
|
869
981
|
if (!handedOff) {
|
|
870
982
|
this.session.promptRunning = false;
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
983
|
+
if (errored) {
|
|
984
|
+
// The query stream was just drained — handing pending prompts off
|
|
985
|
+
// onto it would let them race with the recovery. Cancel them so
|
|
986
|
+
// each waiting prompt() returns stopReason "cancelled" and the
|
|
987
|
+
// client can decide whether to retry.
|
|
988
|
+
for (const pending of this.session.pendingMessages.values()) {
|
|
989
|
+
pending.resolve(true);
|
|
990
|
+
}
|
|
991
|
+
this.session.pendingMessages.clear();
|
|
992
|
+
} else if (this.session.pendingMessages.size > 0) {
|
|
993
|
+
// Clean exit with queued prompts: hand off the lowest-order one
|
|
994
|
+
// so it can proceed. The rest stay queued for their own turn.
|
|
995
|
+
const next = [...this.session.pendingMessages.entries()].sort(
|
|
996
|
+
(a, b) => a[1].order - b[1].order,
|
|
997
|
+
)[0];
|
|
998
|
+
if (next) {
|
|
999
|
+
next[1].resolve(false);
|
|
1000
|
+
this.session.pendingMessages.delete(next[0]);
|
|
1001
|
+
}
|
|
875
1002
|
}
|
|
876
1003
|
}
|
|
877
1004
|
}
|
|
@@ -933,6 +1060,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
933
1060
|
|
|
934
1061
|
const mcpServers = parseMcpServers(
|
|
935
1062
|
params as Pick<NewSessionRequest, "mcpServers">,
|
|
1063
|
+
this.logger,
|
|
936
1064
|
);
|
|
937
1065
|
await this.refreshSession(mcpServers);
|
|
938
1066
|
return { refreshed: true };
|
|
@@ -1166,6 +1294,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1166
1294
|
params: {
|
|
1167
1295
|
cwd: string;
|
|
1168
1296
|
mcpServers: NewSessionRequest["mcpServers"];
|
|
1297
|
+
additionalDirectories?: NewSessionRequest["additionalDirectories"];
|
|
1169
1298
|
_meta?: unknown;
|
|
1170
1299
|
},
|
|
1171
1300
|
creationOpts: {
|
|
@@ -1205,7 +1334,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1205
1334
|
const earlyModelId =
|
|
1206
1335
|
settingsManager.getSettings().model || meta?.model || "";
|
|
1207
1336
|
const mcpServers = supportsMcpInjection(earlyModelId)
|
|
1208
|
-
? parseMcpServers(params)
|
|
1337
|
+
? parseMcpServers(params, this.logger)
|
|
1209
1338
|
: {};
|
|
1210
1339
|
|
|
1211
1340
|
// Register the in-process general local-tools MCP server. Tools self-gate
|
|
@@ -1250,6 +1379,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1250
1379
|
? (meta.permissionMode as CodeExecutionMode)
|
|
1251
1380
|
: "default";
|
|
1252
1381
|
|
|
1382
|
+
const taskState: TaskState = new Map();
|
|
1253
1383
|
const options = buildSessionOptions({
|
|
1254
1384
|
cwd,
|
|
1255
1385
|
mcpServers,
|
|
@@ -1263,7 +1393,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1263
1393
|
forkSession,
|
|
1264
1394
|
additionalDirectories: [
|
|
1265
1395
|
...(meta?.claudeCode?.options?.additionalDirectories ?? []),
|
|
1266
|
-
|
|
1396
|
+
// Prefer the official ACP `additionalDirectories` field. Fall back
|
|
1397
|
+
// to the legacy `_meta.additionalRoots` extension for clients that
|
|
1398
|
+
// haven't been updated yet.
|
|
1399
|
+
...(params.additionalDirectories ?? meta?.additionalRoots ?? []),
|
|
1267
1400
|
],
|
|
1268
1401
|
disableBuiltInTools: meta?.disableBuiltInTools,
|
|
1269
1402
|
outputFormat,
|
|
@@ -1275,6 +1408,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1275
1408
|
enrichmentDeps: this.enrichment?.deps,
|
|
1276
1409
|
enrichedReadCache: this.enrichedReadCache,
|
|
1277
1410
|
cloudMode: cloudRun,
|
|
1411
|
+
taskState,
|
|
1412
|
+
onTaskStateChange: async () => {
|
|
1413
|
+
await this.client.sessionUpdate({
|
|
1414
|
+
sessionId,
|
|
1415
|
+
update: {
|
|
1416
|
+
sessionUpdate: "plan",
|
|
1417
|
+
entries: taskStateToPlanEntries(taskState),
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
},
|
|
1278
1421
|
});
|
|
1279
1422
|
|
|
1280
1423
|
// Use the same abort controller that buildSessionOptions gave to the query
|
|
@@ -1307,6 +1450,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1307
1450
|
systemPrompt: estimateSystemPrompt(systemPrompt),
|
|
1308
1451
|
rules: estimateRulesTokens(readClaudeMdQuietly(cwd, this.logger)),
|
|
1309
1452
|
},
|
|
1453
|
+
taskState,
|
|
1310
1454
|
|
|
1311
1455
|
// Custom properties
|
|
1312
1456
|
cwd,
|
|
@@ -1359,7 +1503,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1359
1503
|
? withTimeout(q.initializationResult(), SESSION_VALIDATION_TIMEOUT_MS)
|
|
1360
1504
|
: undefined;
|
|
1361
1505
|
|
|
1362
|
-
const [
|
|
1506
|
+
const [rawModelOptions] = await Promise.all([
|
|
1363
1507
|
this.getModelConfigOptions(
|
|
1364
1508
|
settingsManager.getSettings().model || meta?.model || undefined,
|
|
1365
1509
|
),
|
|
@@ -1374,6 +1518,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1374
1518
|
: []),
|
|
1375
1519
|
]);
|
|
1376
1520
|
|
|
1521
|
+
// Restrict the model list to the user's `availableModels` allowlist
|
|
1522
|
+
// from settings.json so config UI and downstream resolution stay
|
|
1523
|
+
// consistent with what the user configured. The Default option is
|
|
1524
|
+
// always preserved per the Claude Code docs.
|
|
1525
|
+
const settingsAvailableModels =
|
|
1526
|
+
settingsManager.getSettings().availableModels;
|
|
1527
|
+
const modelOptions = Array.isArray(settingsAvailableModels)
|
|
1528
|
+
? applyAvailableModelsAllowlist(rawModelOptions, settingsAvailableModels)
|
|
1529
|
+
: rawModelOptions;
|
|
1530
|
+
|
|
1377
1531
|
if (initPromise) {
|
|
1378
1532
|
try {
|
|
1379
1533
|
const initResult = await initPromise;
|
|
@@ -1398,10 +1552,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1398
1552
|
}
|
|
1399
1553
|
}
|
|
1400
1554
|
|
|
1401
|
-
const
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1555
|
+
const resolvedModelId = resolveInitialModelId(modelOptions, [
|
|
1556
|
+
settingsManager.getSettings().model,
|
|
1557
|
+
meta?.model,
|
|
1558
|
+
]);
|
|
1405
1559
|
session.modelId = resolvedModelId;
|
|
1406
1560
|
session.lastContextWindowSize =
|
|
1407
1561
|
this.getContextWindowForModel(resolvedModelId);
|
|
@@ -1665,6 +1819,35 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1665
1819
|
};
|
|
1666
1820
|
}
|
|
1667
1821
|
|
|
1822
|
+
/**
|
|
1823
|
+
* Rebuild the in-memory taskState from JSONL and push a plan update so the
|
|
1824
|
+
* client's plan panel reflects pre-resume tasks. `loadSession` already covers
|
|
1825
|
+
* this via the full `replaySessionHistory` notification stream; resume
|
|
1826
|
+
* deliberately stays quiet (the client keeps its own message history) so we
|
|
1827
|
+
* walk the transcript here for state only.
|
|
1828
|
+
*/
|
|
1829
|
+
private async rehydrateTaskStateFromJsonl(sessionId: string): Promise<void> {
|
|
1830
|
+
try {
|
|
1831
|
+
const messages = await getSessionMessages(sessionId, {
|
|
1832
|
+
dir: this.session.cwd,
|
|
1833
|
+
});
|
|
1834
|
+
rehydrateTaskState(messages, this.session.taskState);
|
|
1835
|
+
if (this.session.taskState.size === 0) return;
|
|
1836
|
+
await this.client.sessionUpdate({
|
|
1837
|
+
sessionId,
|
|
1838
|
+
update: {
|
|
1839
|
+
sessionUpdate: "plan",
|
|
1840
|
+
entries: taskStateToPlanEntries(this.session.taskState),
|
|
1841
|
+
},
|
|
1842
|
+
});
|
|
1843
|
+
} catch (err) {
|
|
1844
|
+
this.logger.warn("Failed to rehydrate task state", {
|
|
1845
|
+
sessionId,
|
|
1846
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1668
1851
|
private async replaySessionHistory(sessionId: string): Promise<void> {
|
|
1669
1852
|
try {
|
|
1670
1853
|
const messages = await getSessionMessages(sessionId, {
|