@posthog/agent 2.3.326 → 2.3.341

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.
Files changed (55) hide show
  1. package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +9 -0
  2. package/dist/adapters/claude/conversion/tool-use-to-acp.js +15 -1
  3. package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
  4. package/dist/adapters/claude/permissions/permission-options.js +18 -11
  5. package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
  6. package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -1
  7. package/dist/adapters/claude/session/models.d.ts +2 -2
  8. package/dist/adapters/claude/session/models.js +12 -6
  9. package/dist/adapters/claude/session/models.js.map +1 -1
  10. package/dist/adapters/claude/tools.js +15 -13
  11. package/dist/adapters/claude/tools.js.map +1 -1
  12. package/dist/adapters/reasoning-effort.d.ts +1 -1
  13. package/dist/adapters/reasoning-effort.js +11 -5
  14. package/dist/adapters/reasoning-effort.js.map +1 -1
  15. package/dist/agent.d.ts +2 -0
  16. package/dist/agent.js +6946 -587
  17. package/dist/agent.js.map +1 -1
  18. package/dist/execution-mode.d.ts +1 -1
  19. package/dist/execution-mode.js +14 -12
  20. package/dist/execution-mode.js.map +1 -1
  21. package/dist/posthog-api.js +4 -3
  22. package/dist/posthog-api.js.map +1 -1
  23. package/dist/server/agent-server.d.ts +1 -1
  24. package/dist/server/agent-server.js +9260 -2939
  25. package/dist/server/agent-server.js.map +1 -1
  26. package/dist/server/bin.cjs +11289 -4967
  27. package/dist/server/bin.cjs.map +1 -1
  28. package/dist/types.d.ts +8 -0
  29. package/package.json +7 -6
  30. package/src/adapters/acp-connection.ts +14 -1
  31. package/src/adapters/claude/UPSTREAM.md +24 -4
  32. package/src/adapters/claude/claude-agent.ts +161 -14
  33. package/src/adapters/claude/conversion/sdk-to-acp.ts +14 -2
  34. package/src/adapters/claude/conversion/tool-use-to-acp.ts +18 -1
  35. package/src/adapters/claude/hooks.test.ts +189 -0
  36. package/src/adapters/claude/hooks.ts +93 -3
  37. package/src/adapters/claude/permissions/permission-handlers.ts +2 -1
  38. package/src/adapters/claude/permissions/permission-options.ts +5 -0
  39. package/src/adapters/claude/session/models.ts +11 -5
  40. package/src/adapters/claude/session/options.ts +19 -3
  41. package/src/adapters/claude/session/settings.ts +17 -9
  42. package/src/adapters/claude/tools.ts +1 -1
  43. package/src/adapters/claude/types.ts +8 -1
  44. package/src/adapters/codex/codex-agent.ts +15 -2
  45. package/src/adapters/codex/codex-client.test.ts +112 -0
  46. package/src/adapters/codex/codex-client.ts +14 -1
  47. package/src/adapters/reasoning-effort.ts +6 -1
  48. package/src/agent.ts +6 -0
  49. package/src/enrichment/file-enricher.test.ts +163 -0
  50. package/src/enrichment/file-enricher.ts +82 -0
  51. package/src/execution-mode.test.ts +1 -0
  52. package/src/execution-mode.ts +13 -11
  53. package/src/server/bin.ts +1 -1
  54. package/src/server/types.ts +1 -1
  55. package/src/types.ts +6 -0
package/dist/types.d.ts CHANGED
@@ -112,6 +112,14 @@ interface AgentConfig {
112
112
  skipLogPersistence?: boolean;
113
113
  /** Local cache path for instant log loading (e.g., ~/.posthog-code) */
114
114
  localCachePath?: string;
115
+ /**
116
+ * Annotate files the agent reads with PostHog enrichment (event volume,
117
+ * flag rollout/staleness, experiment links). Defaults to enabled when
118
+ * `posthog` config is present; set `{ enabled: false }` to opt out.
119
+ */
120
+ enricher?: {
121
+ enabled?: boolean;
122
+ };
115
123
  debug?: boolean;
116
124
  onLog?: OnLogCallback;
117
125
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "2.3.326",
3
+ "version": "2.3.341",
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": {
@@ -86,14 +86,14 @@
86
86
  "tsx": "^4.20.6",
87
87
  "typescript": "^5.5.0",
88
88
  "vitest": "^2.1.8",
89
- "@posthog/shared": "1.0.0",
90
- "@posthog/git": "1.0.0"
89
+ "@posthog/git": "1.0.0",
90
+ "@posthog/shared": "1.0.0"
91
91
  },
92
92
  "dependencies": {
93
- "@agentclientprotocol/sdk": "0.16.1",
93
+ "@agentclientprotocol/sdk": "0.19.0",
94
94
  "ajv": "^8.17.1",
95
95
  "@anthropic-ai/claude-agent-sdk": "0.2.112",
96
- "@anthropic-ai/sdk": "^0.78.0",
96
+ "@anthropic-ai/sdk": "0.89.0",
97
97
  "@hono/node-server": "^1.19.9",
98
98
  "@opentelemetry/api-logs": "^0.208.0",
99
99
  "@opentelemetry/exporter-logs-otlp-http": "^0.208.0",
@@ -108,7 +108,8 @@
108
108
  "tar": "^7.5.0",
109
109
  "uuid": "13.0.0",
110
110
  "yoga-wasm-web": "^0.3.3",
111
- "zod": "^4.2.0"
111
+ "zod": "^4.2.0",
112
+ "@posthog/enricher": "1.0.0"
112
113
  },
113
114
  "files": [
114
115
  "dist/**/*",
@@ -1,6 +1,6 @@
1
1
  import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
2
2
  import type { SessionLogWriter } from "../session-log-writer";
3
- import type { ProcessSpawnedCallback } from "../types";
3
+ import type { PostHogAPIConfig, ProcessSpawnedCallback } from "../types";
4
4
  import { Logger } from "../utils/logger";
5
5
  import {
6
6
  createBidirectionalStreams,
@@ -26,6 +26,10 @@ export type AcpConnectionConfig = {
26
26
  allowedModelIds?: Set<string>;
27
27
  /** Callback invoked when the agent calls the create_output tool for structured output */
28
28
  onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
29
+ /** PostHog API config; when set, enables file-read enrichment unless disabled. */
30
+ posthogApiConfig?: PostHogAPIConfig;
31
+ /** Defaults to true when posthogApiConfig is set. Set to false to disable enrichment. */
32
+ enricherEnabled?: boolean;
29
33
  };
30
34
 
31
35
  export type AcpConnection = {
@@ -54,6 +58,13 @@ export function createAcpConnection(
54
58
  return createClaudeConnection(config);
55
59
  }
56
60
 
61
+ function resolveEnricherApiConfig(
62
+ config: AcpConnectionConfig,
63
+ ): PostHogAPIConfig | undefined {
64
+ const enabled = !!config.posthogApiConfig && config.enricherEnabled !== false;
65
+ return enabled ? config.posthogApiConfig : undefined;
66
+ }
67
+
57
68
  function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
58
69
  const logger =
59
70
  config.logger?.child("AcpConnection") ??
@@ -102,6 +113,7 @@ function createClaudeConnection(config: AcpConnectionConfig): AcpConnection {
102
113
  agent = new ClaudeAcpAgent(client, {
103
114
  ...config.processCallbacks,
104
115
  onStructuredOutput: config.onStructuredOutput,
116
+ posthogApiConfig: resolveEnricherApiConfig(config),
105
117
  });
106
118
  return agent;
107
119
  }, agentStream);
@@ -192,6 +204,7 @@ function createCodexConnection(config: AcpConnectionConfig): AcpConnection {
192
204
  agent = new CodexAcpAgent(client, {
193
205
  codexProcessOptions: config.codexOptions ?? {},
194
206
  processCallbacks: config.processCallbacks,
207
+ posthogApiConfig: resolveEnricherApiConfig(config),
195
208
  });
196
209
  return agent;
197
210
  }, agentStream);
@@ -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.22.2, commit `07db59e`, March 25 2026
9
- - **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.76, `@agentclientprotocol/sdk` 0.16.1
8
+ - **Last sync**: v0.30.0, commit `e9dd452`, April 20 2026
9
+ - **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.112 (0.2.114 breaks session init, see agentclientprotocol/claude-agent-acp#575), `@agentclientprotocol/sdk` 0.19.0
10
10
 
11
11
  ## File Mapping
12
12
 
@@ -50,11 +50,31 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
50
50
  | permissionMode | Hardcoded `"default"` | Reads from `meta.permissionMode` | More flexible mode selection |
51
51
  | Session storage | `this.sessions[sessionId]` (multi) | `this.session` (single) | Architectural choice |
52
52
  | bypassPermissions | `updatedPermissions` with `destination: "session"` | No `updatedPermissions` | Different permission persistence |
53
- | Auth methods | Always returns `claude-login` auth method | Returns empty `authMethods` | Auth handled externally |
53
+ | Auth methods | `claude-ai-login` + `console-login` | Returns empty `authMethods` | Auth handled externally |
54
+ | Session fingerprinting | Implicit teardown on cwd/mcp change | Explicit `refreshSession()` | Caller-initiated is more predictable |
55
+ | Shutdown on ACP close | Process exits | No standalone process | Agent is embedded in server |
56
+
57
+ ## Changes Ported in v0.30.0 Sync
58
+
59
+ - **SDK bumps**: claude-agent-sdk 0.2.112 -> 0.2.114, ACP SDK 0.16.1 -> 0.19.0, anthropic SDK -> 0.89.0
60
+ - **Null-safe usage tokens** (v0.29.2): Guard against null usage fields from SDK
61
+ - **SettingsManager race fix** (v0.25.0): `initPromise` prevents concurrent `initialize()`/`setCwd()` corruption
62
+ - **Malformed settings warning** (v0.25.0): Log warning for non-ENOENT settings file errors
63
+ - **Idle state end-of-turn** (v0.23.0): `CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1` + `session_state_changed` idle handler
64
+ - **Mid-stream usage updates** (v0.29.1): Fire `usage_update` from `message_start`/`message_delta` stream events
65
+ - **Raw SDK message relay** (v0.27.0): `emitRawSDKMessages` on `NewSessionMeta` for opt-in diagnostics
66
+ - **Effort level sync** (v0.25.x): `xhigh` level added, `applyFlagSettings` on effort change
67
+ - **Auto permission mode** (v0.25.0): Added to `CODE_EXECUTION_MODES`, available modes, ExitPlanMode options
68
+
69
+ ## Skipped in v0.30.0 Sync
70
+
71
+ - **Separate auth methods** (v0.25.0): PostHog returns empty authMethods
72
+ - **Session fingerprinting** (v0.25.3): PostHog uses explicit `refreshSession()` instead
73
+ - **Process exit on ACP close** (v0.27.0): PostHog embeds agent in server
54
74
 
55
75
  ## Next Sync
56
76
 
57
- 1. Check upstream changelog since v0.22.2
77
+ 1. Check upstream changelog since v0.30.0
58
78
  2. Diff upstream source against PostHog Code using the file mapping above
59
79
  3. Port in phases: bug fixes first, then features
60
80
  4. After each phase: `pnpm --filter agent typecheck && pnpm --filter agent build && pnpm lint`
@@ -51,6 +51,12 @@ import {
51
51
  POSTHOG_METHODS,
52
52
  POSTHOG_NOTIFICATIONS,
53
53
  } from "../../acp-extensions";
54
+ import {
55
+ createEnrichment,
56
+ type Enrichment,
57
+ type FileEnrichmentDeps,
58
+ } from "../../enrichment/file-enricher";
59
+ import type { PostHogAPIConfig } from "../../types";
54
60
  import { unreachable, withTimeout } from "../../utils/common";
55
61
  import { Logger } from "../../utils/logger";
56
62
  import { Pushable } from "../../utils/streams";
@@ -62,6 +68,7 @@ import {
62
68
  handleSystemMessage,
63
69
  handleUserAssistantMessage,
64
70
  } from "./conversion/sdk-to-acp";
71
+ import type { EnrichedReadCache } from "./hooks";
65
72
  import {
66
73
  fetchMcpToolMetadata,
67
74
  getConnectedMcpServerNames,
@@ -92,6 +99,7 @@ import type {
92
99
  BackgroundTerminal,
93
100
  EffortLevel,
94
101
  NewSessionMeta,
102
+ SDKMessageFilter,
95
103
  Session,
96
104
  ToolUseCache,
97
105
  } from "./types";
@@ -111,11 +119,25 @@ function sanitizeTitle(text: string): string {
111
119
  return `${sanitized.slice(0, MAX_TITLE_LENGTH - 1)}…`;
112
120
  }
113
121
 
122
+ function shouldEmitRawMessage(
123
+ config: boolean | SDKMessageFilter[],
124
+ message: { type: string; subtype?: string },
125
+ ): boolean {
126
+ if (config === true) return true;
127
+ if (config === false) return false;
128
+ return config.some(
129
+ (f) =>
130
+ f.type === message.type &&
131
+ (f.subtype === undefined || f.subtype === message.subtype),
132
+ );
133
+ }
134
+
114
135
  export interface ClaudeAcpAgentOptions {
115
136
  onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
116
137
  onProcessExited?: (pid: number) => void;
117
138
  onMcpServersReady?: (serverNames: string[]) => void;
118
139
  onStructuredOutput?: (output: Record<string, unknown>) => Promise<void>;
140
+ posthogApiConfig?: PostHogAPIConfig;
119
141
  }
120
142
 
121
143
  export class ClaudeAcpAgent extends BaseAcpAgent {
@@ -125,12 +147,29 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
125
147
  backgroundTerminals: { [key: string]: BackgroundTerminal } = {};
126
148
  clientCapabilities?: ClientCapabilities;
127
149
  private options?: ClaudeAcpAgentOptions;
150
+ private enrichment?: Enrichment;
151
+ private enrichedReadCache: EnrichedReadCache = new Map();
128
152
 
129
153
  constructor(client: AgentSideConnection, options?: ClaudeAcpAgentOptions) {
130
154
  super(client);
131
155
  this.options = options;
132
156
  this.toolUseCache = {};
133
157
  this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
158
+ this.enrichment = createEnrichment(options?.posthogApiConfig, this.logger);
159
+ }
160
+
161
+ protected getEnrichmentDeps(): FileEnrichmentDeps | undefined {
162
+ return this.enrichment?.deps;
163
+ }
164
+
165
+ override async closeSession(): Promise<void> {
166
+ try {
167
+ await super.closeSession();
168
+ } finally {
169
+ this.enrichment?.dispose();
170
+ this.enrichment = undefined;
171
+ this.enrichedReadCache.clear();
172
+ }
134
173
  }
135
174
 
136
175
  async initialize(request: InitializeRequest): Promise<InitializeResponse> {
@@ -331,6 +370,12 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
331
370
  this.session.promptRunning = true;
332
371
  let handedOff = false;
333
372
  let lastAssistantTotalUsage: number | null = null;
373
+ let lastStreamUsage = {
374
+ input_tokens: 0,
375
+ output_tokens: 0,
376
+ cache_read_input_tokens: 0,
377
+ cache_creation_input_tokens: 0,
378
+ };
334
379
  if (this.session.lastContextWindowSize == null) {
335
380
  this.session.lastContextWindowSize = this.getContextWindowForModel(
336
381
  this.session.modelId ?? "",
@@ -355,6 +400,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
355
400
  client: this.client,
356
401
  toolUseCache: this.toolUseCache,
357
402
  fileContentCache: this.fileContentCache,
403
+ enrichedReadCache: this.enrichedReadCache,
358
404
  logger: this.logger,
359
405
  supportsTerminalOutput,
360
406
  };
@@ -375,6 +421,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
375
421
  break;
376
422
  }
377
423
 
424
+ if (
425
+ this.session.emitRawSDKMessages &&
426
+ shouldEmitRawMessage(this.session.emitRawSDKMessages, message)
427
+ ) {
428
+ await this.client.extNotification("_claude/sdkMessage", {
429
+ sessionId: params.sessionId,
430
+ message: message as Record<string, unknown>,
431
+ });
432
+ }
433
+
378
434
  switch (message.type) {
379
435
  case "system":
380
436
  if (message.subtype === "compact_boundary") {
@@ -394,6 +450,35 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
394
450
  if (message.subtype === "local_command_output") {
395
451
  promptReplayed = true;
396
452
  }
453
+ if (
454
+ message.subtype === "session_state_changed" &&
455
+ (message as Record<string, unknown>).state === "idle"
456
+ ) {
457
+ if (!promptReplayed) {
458
+ this.logger.debug("Skipping idle state before prompt replay", {
459
+ sessionId: params.sessionId,
460
+ });
461
+ break;
462
+ }
463
+
464
+ const acc = this.session.accumulatedUsage;
465
+ const totalUsed =
466
+ acc.inputTokens +
467
+ acc.outputTokens +
468
+ acc.cachedReadTokens +
469
+ acc.cachedWriteTokens;
470
+
471
+ await this.client.sessionUpdate({
472
+ sessionId: params.sessionId,
473
+ update: {
474
+ sessionUpdate: "usage_update",
475
+ used: totalUsed,
476
+ size: lastContextWindowSize,
477
+ },
478
+ });
479
+
480
+ return { stopReason: "end_turn" };
481
+ }
397
482
  await handleSystemMessage(message, context);
398
483
  break;
399
484
 
@@ -411,15 +496,15 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
411
496
  return { stopReason: "cancelled" };
412
497
  }
413
498
 
414
- // Accumulate usage from this result
499
+ // Accumulate usage from this result (guard against null from SDK)
415
500
  this.session.accumulatedUsage.inputTokens +=
416
- message.usage.input_tokens;
501
+ message.usage.input_tokens ?? 0;
417
502
  this.session.accumulatedUsage.outputTokens +=
418
- message.usage.output_tokens;
503
+ message.usage.output_tokens ?? 0;
419
504
  this.session.accumulatedUsage.cachedReadTokens +=
420
- message.usage.cache_read_input_tokens;
505
+ message.usage.cache_read_input_tokens ?? 0;
421
506
  this.session.accumulatedUsage.cachedWriteTokens +=
422
- message.usage.cache_creation_input_tokens;
507
+ message.usage.cache_creation_input_tokens ?? 0;
423
508
 
424
509
  // SDK can underreport context window (e.g. 200k for 1M models).
425
510
  // Use SDK value only if it's larger than what gateway reported.
@@ -514,9 +599,56 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
514
599
  return { stopReason: result.stopReason ?? "end_turn", usage };
515
600
  }
516
601
 
517
- case "stream_event":
602
+ case "stream_event": {
603
+ if (
604
+ message.parent_tool_use_id === null &&
605
+ (message.event.type === "message_start" ||
606
+ message.event.type === "message_delta")
607
+ ) {
608
+ if (message.event.type === "message_start") {
609
+ const u = message.event.message.usage;
610
+ lastStreamUsage = {
611
+ input_tokens: u.input_tokens ?? 0,
612
+ output_tokens: u.output_tokens ?? 0,
613
+ cache_read_input_tokens: u.cache_read_input_tokens ?? 0,
614
+ cache_creation_input_tokens:
615
+ u.cache_creation_input_tokens ?? 0,
616
+ };
617
+ } else {
618
+ const u = message.event.usage;
619
+ lastStreamUsage = {
620
+ input_tokens: u.input_tokens ?? lastStreamUsage.input_tokens,
621
+ output_tokens: u.output_tokens,
622
+ cache_read_input_tokens:
623
+ u.cache_read_input_tokens ??
624
+ lastStreamUsage.cache_read_input_tokens,
625
+ cache_creation_input_tokens:
626
+ u.cache_creation_input_tokens ??
627
+ lastStreamUsage.cache_creation_input_tokens,
628
+ };
629
+ }
630
+
631
+ const nextTotal =
632
+ lastStreamUsage.input_tokens +
633
+ lastStreamUsage.output_tokens +
634
+ lastStreamUsage.cache_read_input_tokens +
635
+ lastStreamUsage.cache_creation_input_tokens;
636
+
637
+ if (nextTotal !== lastAssistantTotalUsage) {
638
+ lastAssistantTotalUsage = nextTotal;
639
+ await this.client.sessionUpdate({
640
+ sessionId: params.sessionId,
641
+ update: {
642
+ sessionUpdate: "usage_update",
643
+ used: nextTotal,
644
+ size: lastContextWindowSize,
645
+ },
646
+ });
647
+ }
648
+ }
518
649
  await handleStreamEvent(message, context);
519
650
  break;
651
+ }
520
652
 
521
653
  case "user":
522
654
  case "assistant": {
@@ -565,16 +697,16 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
565
697
  const usage = (
566
698
  message.message as unknown as Record<string, unknown>
567
699
  ).usage as {
568
- input_tokens: number;
569
- output_tokens: number;
570
- cache_read_input_tokens: number;
571
- cache_creation_input_tokens: number;
700
+ input_tokens: number | null;
701
+ output_tokens: number | null;
702
+ cache_read_input_tokens: number | null;
703
+ cache_creation_input_tokens: number | null;
572
704
  };
573
705
  lastAssistantTotalUsage =
574
- usage.input_tokens +
575
- usage.output_tokens +
576
- usage.cache_read_input_tokens +
577
- usage.cache_creation_input_tokens;
706
+ (usage.input_tokens ?? 0) +
707
+ (usage.output_tokens ?? 0) +
708
+ (usage.cache_read_input_tokens ?? 0) +
709
+ (usage.cache_creation_input_tokens ?? 0);
578
710
 
579
711
  await this.client.sessionUpdate({
580
712
  sessionId: params.sessionId,
@@ -858,6 +990,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
858
990
  const newEffort = resolvedValue as EffortLevel;
859
991
  this.session.effort = newEffort;
860
992
  this.session.queryOptions.effort = newEffort;
993
+ await this.session.query.applyFlagSettings({
994
+ // @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it
995
+ effortLevel: newEffort,
996
+ });
861
997
  }
862
998
 
863
999
  this.session.configOptions = this.session.configOptions.map((o) =>
@@ -993,6 +1129,8 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
993
1129
  onProcessSpawned: this.options?.onProcessSpawned,
994
1130
  onProcessExited: this.options?.onProcessExited,
995
1131
  effort,
1132
+ enrichmentDeps: this.enrichment?.deps,
1133
+ enrichedReadCache: this.enrichedReadCache,
996
1134
  });
997
1135
 
998
1136
  // Use the same abort controller that buildSessionOptions gave to the query
@@ -1019,6 +1157,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
1019
1157
  promptRunning: false,
1020
1158
  pendingMessages: new Map(),
1021
1159
  nextPendingOrder: 0,
1160
+ emitRawSDKMessages: meta?.claudeCode?.emitRawSDKMessages ?? false,
1022
1161
 
1023
1162
  // Custom properties
1024
1163
  cwd,
@@ -1297,6 +1436,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
1297
1436
  if (this.session.effort) {
1298
1437
  this.session.effort = undefined;
1299
1438
  this.session.queryOptions.effort = undefined;
1439
+ void this.session.query.applyFlagSettings({
1440
+ effortLevel: undefined,
1441
+ });
1300
1442
  }
1301
1443
  return;
1302
1444
  }
@@ -1310,6 +1452,10 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
1310
1452
  if (resolvedValue !== currentValue && this.session.effort) {
1311
1453
  this.session.effort = resolvedValue as EffortLevel;
1312
1454
  this.session.queryOptions.effort = resolvedValue as EffortLevel;
1455
+ void this.session.query.applyFlagSettings({
1456
+ // @ts-expect-error SDK Settings.effortLevel omits "max" but runtime accepts it
1457
+ effortLevel: resolvedValue,
1458
+ });
1313
1459
  }
1314
1460
 
1315
1461
  const effortConfig: SessionConfigOption = {
@@ -1354,6 +1500,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
1354
1500
  client: this.client,
1355
1501
  toolUseCache: this.toolUseCache,
1356
1502
  fileContentCache: this.fileContentCache,
1503
+ enrichedReadCache: this.enrichedReadCache,
1357
1504
  logger: this.logger,
1358
1505
  registerHooks: false,
1359
1506
  };
@@ -21,7 +21,7 @@ import { POSTHOG_NOTIFICATIONS } from "@/acp-extensions";
21
21
  import { image, text } from "../../../utils/acp-content";
22
22
  import { unreachable } from "../../../utils/common";
23
23
  import type { Logger } from "../../../utils/logger";
24
- import { registerHookCallback } from "../hooks";
24
+ import { type EnrichedReadCache, registerHookCallback } from "../hooks";
25
25
  import type { Session, ToolUpdateMeta, ToolUseCache } from "../types";
26
26
  import {
27
27
  type ClaudePlanEntry,
@@ -51,6 +51,7 @@ type ChunkHandlerContext = {
51
51
  sessionId: string;
52
52
  toolUseCache: ToolUseCache;
53
53
  fileContentCache: { [key: string]: string };
54
+ enrichedReadCache?: EnrichedReadCache;
54
55
  client: AgentSideConnection;
55
56
  logger: Logger;
56
57
  parentToolCallId?: string;
@@ -67,6 +68,7 @@ export interface MessageHandlerContext {
67
68
  client: AgentSideConnection;
68
69
  toolUseCache: ToolUseCache;
69
70
  fileContentCache: { [key: string]: string };
71
+ enrichedReadCache?: EnrichedReadCache;
70
72
  logger: Logger;
71
73
  registerHooks?: boolean;
72
74
  supportsTerminalOutput?: boolean;
@@ -248,7 +250,7 @@ function extractTextFromContent(content: unknown): string | null {
248
250
  return null;
249
251
  }
250
252
 
251
- function stripCatLineNumbers(text: string): string {
253
+ export function stripCatLineNumbers(text: string): string {
252
254
  return text.replace(/^ *\d+[\t→]/gm, "");
253
255
  }
254
256
 
@@ -318,6 +320,7 @@ function handleToolResultChunk(
318
320
  supportsTerminalOutput: ctx.supportsTerminalOutput,
319
321
  toolUseId: chunk.tool_use_id,
320
322
  cachedFileContent: ctx.fileContentCache,
323
+ enrichedReadCache: ctx.enrichedReadCache,
321
324
  },
322
325
  );
323
326
 
@@ -448,6 +451,7 @@ function toAcpNotifications(
448
451
  supportsTerminalOutput?: boolean,
449
452
  cwd?: string,
450
453
  mcpToolUseResult?: Record<string, unknown>,
454
+ enrichedReadCache?: EnrichedReadCache,
451
455
  ): SessionNotification[] {
452
456
  if (typeof content === "string") {
453
457
  const update: SessionUpdate = {
@@ -468,6 +472,7 @@ function toAcpNotifications(
468
472
  sessionId,
469
473
  toolUseCache,
470
474
  fileContentCache,
475
+ enrichedReadCache,
471
476
  client,
472
477
  logger,
473
478
  parentToolCallId,
@@ -498,6 +503,7 @@ function streamEventToAcpNotifications(
498
503
  registerHooks?: boolean,
499
504
  supportsTerminalOutput?: boolean,
500
505
  cwd?: string,
506
+ enrichedReadCache?: EnrichedReadCache,
501
507
  ): SessionNotification[] {
502
508
  const event = message.event;
503
509
  switch (event.type) {
@@ -514,6 +520,8 @@ function streamEventToAcpNotifications(
514
520
  registerHooks,
515
521
  supportsTerminalOutput,
516
522
  cwd,
523
+ undefined,
524
+ enrichedReadCache,
517
525
  );
518
526
  case "content_block_delta":
519
527
  return toAcpNotifications(
@@ -528,6 +536,8 @@ function streamEventToAcpNotifications(
528
536
  registerHooks,
529
537
  supportsTerminalOutput,
530
538
  cwd,
539
+ undefined,
540
+ enrichedReadCache,
531
541
  );
532
542
  case "message_start":
533
543
  case "message_delta":
@@ -717,6 +727,7 @@ export async function handleStreamEvent(
717
727
  context.registerHooks,
718
728
  context.supportsTerminalOutput,
719
729
  context.session.cwd,
730
+ context.enrichedReadCache,
720
731
  )) {
721
732
  await client.sessionUpdate(notification);
722
733
  context.session.notificationHistory.push(notification);
@@ -840,6 +851,7 @@ export async function handleUserAssistantMessage(
840
851
  context.supportsTerminalOutput,
841
852
  session.cwd,
842
853
  mcpToolUseResult,
854
+ context.enrichedReadCache,
843
855
  )) {
844
856
  await client.sessionUpdate(notification);
845
857
  session.notificationHistory.push(notification);
@@ -31,6 +31,7 @@ function stripSystemReminders(value: string): string {
31
31
  }
32
32
 
33
33
  import { resourceLink, text, toolContent } from "../../../utils/acp-content";
34
+ import type { EnrichedReadCache } from "../hooks";
34
35
  import { getMcpToolMetadata } from "../mcp/tool-metadata";
35
36
 
36
37
  type ToolInfo = Pick<ToolCall, "title" | "kind" | "content" | "locations">;
@@ -526,6 +527,7 @@ export function toolUpdateFromToolResult(
526
527
  supportsTerminalOutput?: boolean;
527
528
  toolUseId?: string;
528
529
  cachedFileContent?: Record<string, string>;
530
+ enrichedReadCache?: EnrichedReadCache;
529
531
  },
530
532
  ): Pick<ToolCallUpdate, "title" | "content" | "locations" | "_meta"> {
531
533
  if (
@@ -538,7 +540,21 @@ export function toolUpdateFromToolResult(
538
540
  }
539
541
 
540
542
  switch (toolUse?.name) {
541
- case "Read":
543
+ case "Read": {
544
+ const cache = options?.enrichedReadCache;
545
+ const enriched =
546
+ cache && options?.toolUseId ? cache.get(options.toolUseId) : undefined;
547
+ if (enriched !== undefined && cache && options?.toolUseId) {
548
+ cache.delete(options.toolUseId);
549
+ return {
550
+ content: [
551
+ {
552
+ type: "content" as const,
553
+ content: text(markdownEscape(enriched)),
554
+ },
555
+ ],
556
+ };
557
+ }
542
558
  if (Array.isArray(toolResult.content) && toolResult.content.length > 0) {
543
559
  return {
544
560
  content: toolResult.content.map((item) => {
@@ -582,6 +598,7 @@ export function toolUpdateFromToolResult(
582
598
  };
583
599
  }
584
600
  return {};
601
+ }
585
602
 
586
603
  case "Bash": {
587
604
  const result = toolResult.content;