@posthog/agent 2.3.326 → 2.3.346

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 (65) 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 +6975 -613
  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.d.ts +5 -3
  22. package/dist/posthog-api.js +12 -22
  23. package/dist/posthog-api.js.map +1 -1
  24. package/dist/server/agent-server.d.ts +8 -1
  25. package/dist/server/agent-server.js +11362 -4894
  26. package/dist/server/agent-server.js.map +1 -1
  27. package/dist/server/bin.cjs +11423 -4954
  28. package/dist/server/bin.cjs.map +1 -1
  29. package/dist/types.d.ts +11 -1
  30. package/package.json +7 -6
  31. package/src/adapters/acp-connection.ts +14 -1
  32. package/src/adapters/claude/UPSTREAM.md +24 -4
  33. package/src/adapters/claude/claude-agent.ts +161 -14
  34. package/src/adapters/claude/conversion/acp-to-sdk.test.ts +49 -0
  35. package/src/adapters/claude/conversion/acp-to-sdk.ts +23 -6
  36. package/src/adapters/claude/conversion/sdk-to-acp.ts +14 -2
  37. package/src/adapters/claude/conversion/tool-use-to-acp.ts +18 -1
  38. package/src/adapters/claude/hooks.test.ts +189 -0
  39. package/src/adapters/claude/hooks.ts +93 -3
  40. package/src/adapters/claude/permissions/permission-handlers.ts +2 -1
  41. package/src/adapters/claude/permissions/permission-options.ts +5 -0
  42. package/src/adapters/claude/session/models.ts +11 -5
  43. package/src/adapters/claude/session/options.ts +19 -3
  44. package/src/adapters/claude/session/settings.ts +17 -9
  45. package/src/adapters/claude/tools.ts +1 -1
  46. package/src/adapters/claude/types.ts +8 -1
  47. package/src/adapters/codex/codex-agent.ts +15 -2
  48. package/src/adapters/codex/codex-client.test.ts +112 -0
  49. package/src/adapters/codex/codex-client.ts +14 -1
  50. package/src/adapters/reasoning-effort.ts +6 -1
  51. package/src/agent.ts +6 -0
  52. package/src/enrichment/file-enricher.test.ts +163 -0
  53. package/src/enrichment/file-enricher.ts +82 -0
  54. package/src/execution-mode.test.ts +1 -0
  55. package/src/execution-mode.ts +13 -11
  56. package/src/posthog-api.test.ts +32 -0
  57. package/src/posthog-api.ts +13 -30
  58. package/src/server/agent-server.test.ts +96 -0
  59. package/src/server/agent-server.ts +207 -11
  60. package/src/server/bin.ts +1 -1
  61. package/src/server/schemas.test.ts +10 -0
  62. package/src/server/schemas.ts +25 -6
  63. package/src/server/types.ts +1 -1
  64. package/src/test/mocks/msw-handlers.ts +4 -1
  65. package/src/types.ts +10 -1
@@ -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;
@@ -0,0 +1,189 @@
1
+ import type { HookInput } from "@anthropic-ai/claude-agent-sdk";
2
+ import { describe, expect, test, vi } from "vitest";
3
+ import type { FileEnrichmentDeps } from "../../enrichment/file-enricher";
4
+
5
+ const enrichFileMock = vi.hoisted(() => vi.fn());
6
+ vi.mock("../../enrichment/file-enricher", () => ({
7
+ enrichFileForAgent: enrichFileMock,
8
+ }));
9
+
10
+ import { createReadEnrichmentHook, type EnrichedReadCache } from "./hooks";
11
+
12
+ const stubDeps = {} as FileEnrichmentDeps;
13
+
14
+ function buildReadHookInput(
15
+ overrides: Partial<HookInput> & {
16
+ file_path?: string;
17
+ tool_response?: unknown;
18
+ } = {},
19
+ ): HookInput {
20
+ return {
21
+ session_id: "test-session",
22
+ transcript_path: "/tmp/transcript",
23
+ cwd: "/tmp",
24
+ hook_event_name: "PostToolUse",
25
+ tool_name: "Read",
26
+ tool_use_id: "toolu_1",
27
+ tool_input: { file_path: overrides.file_path ?? "/tmp/code.ts" },
28
+ tool_response: overrides.tool_response ?? "raw-content",
29
+ ...overrides,
30
+ } as HookInput;
31
+ }
32
+
33
+ describe("createReadEnrichmentHook", () => {
34
+ test("returns { continue: true } for non-PostToolUse events", async () => {
35
+ enrichFileMock.mockReset();
36
+ const cache: EnrichedReadCache = new Map();
37
+ const hook = createReadEnrichmentHook(stubDeps, cache);
38
+ const result = await hook(
39
+ { hook_event_name: "PreToolUse" } as HookInput,
40
+ undefined,
41
+ { signal: new AbortController().signal },
42
+ );
43
+ expect(result).toEqual({ continue: true });
44
+ expect(enrichFileMock).not.toHaveBeenCalled();
45
+ });
46
+
47
+ test("returns { continue: true } for non-Read tools", async () => {
48
+ enrichFileMock.mockReset();
49
+ const cache: EnrichedReadCache = new Map();
50
+ const hook = createReadEnrichmentHook(stubDeps, cache);
51
+ const result = await hook(
52
+ buildReadHookInput({ tool_name: "Bash" }),
53
+ undefined,
54
+ { signal: new AbortController().signal },
55
+ );
56
+ expect(result).toEqual({ continue: true });
57
+ expect(enrichFileMock).not.toHaveBeenCalled();
58
+ });
59
+
60
+ test("passes stripped content and file_path into enricher", async () => {
61
+ enrichFileMock.mockReset();
62
+ enrichFileMock.mockResolvedValueOnce(null);
63
+
64
+ const cache: EnrichedReadCache = new Map();
65
+ const hook = createReadEnrichmentHook(stubDeps, cache);
66
+ await hook(
67
+ buildReadHookInput({
68
+ file_path: "/tmp/app.ts",
69
+ tool_response: " 1\tconst x = 1;\n 2\tposthog.capture('x');",
70
+ }),
71
+ undefined,
72
+ { signal: new AbortController().signal },
73
+ );
74
+
75
+ expect(enrichFileMock).toHaveBeenCalledTimes(1);
76
+ const [, filePath, content] = enrichFileMock.mock.calls[0];
77
+ expect(filePath).toBe("/tmp/app.ts");
78
+ expect(content).toBe("const x = 1;\nposthog.capture('x');");
79
+ });
80
+
81
+ test("returns additionalContext when enricher produces annotations", async () => {
82
+ enrichFileMock.mockReset();
83
+ enrichFileMock.mockResolvedValueOnce(
84
+ "posthog.capture('x'); // [PostHog] Event: \"x\"",
85
+ );
86
+
87
+ const cache: EnrichedReadCache = new Map();
88
+ const hook = createReadEnrichmentHook(stubDeps, cache);
89
+ const result = await hook(
90
+ buildReadHookInput({ file_path: "/tmp/app.ts" }),
91
+ undefined,
92
+ {
93
+ signal: new AbortController().signal,
94
+ },
95
+ );
96
+
97
+ expect(result).toEqual({
98
+ continue: true,
99
+ hookSpecificOutput: {
100
+ hookEventName: "PostToolUse",
101
+ additionalContext: expect.stringContaining(
102
+ "posthog.capture('x'); // [PostHog] Event: \"x\"",
103
+ ),
104
+ },
105
+ });
106
+ const context = (
107
+ result as {
108
+ hookSpecificOutput: { additionalContext: string };
109
+ }
110
+ ).hookSpecificOutput.additionalContext;
111
+ expect(context).toContain("/tmp/app.ts");
112
+ });
113
+
114
+ test("writes enriched content to cache keyed by tool_use_id", async () => {
115
+ enrichFileMock.mockReset();
116
+ enrichFileMock.mockResolvedValueOnce(
117
+ "posthog.capture('x'); // [PostHog] Event: \"x\"",
118
+ );
119
+
120
+ const cache: EnrichedReadCache = new Map();
121
+ const hook = createReadEnrichmentHook(stubDeps, cache);
122
+ await hook(buildReadHookInput({ file_path: "/tmp/app.ts" }), undefined, {
123
+ signal: new AbortController().signal,
124
+ });
125
+
126
+ expect(cache.get("toolu_1")).toContain('// [PostHog] Event: "x"');
127
+ });
128
+
129
+ test("does not write to cache when tool_use_id is missing", async () => {
130
+ enrichFileMock.mockReset();
131
+ enrichFileMock.mockResolvedValueOnce("enriched");
132
+
133
+ const cache: EnrichedReadCache = new Map();
134
+ const hook = createReadEnrichmentHook(stubDeps, cache);
135
+ await hook(
136
+ buildReadHookInput({ file_path: "/tmp/app.ts", tool_use_id: undefined }),
137
+ undefined,
138
+ { signal: new AbortController().signal },
139
+ );
140
+
141
+ expect(cache.size).toBe(0);
142
+ });
143
+
144
+ test("handles {type:'text', file:{content}} Read tool_response shape", async () => {
145
+ enrichFileMock.mockReset();
146
+ enrichFileMock.mockResolvedValueOnce("enriched");
147
+
148
+ const cache: EnrichedReadCache = new Map();
149
+ const hook = createReadEnrichmentHook(stubDeps, cache);
150
+ await hook(
151
+ buildReadHookInput({
152
+ file_path: "/tmp/app.ts",
153
+ tool_response: {
154
+ type: "text",
155
+ file: {
156
+ filePath: "/tmp/app.ts",
157
+ content: "posthog.capture('x');\n",
158
+ numLines: 1,
159
+ startLine: 1,
160
+ totalLines: 1,
161
+ },
162
+ },
163
+ }),
164
+ undefined,
165
+ { signal: new AbortController().signal },
166
+ );
167
+
168
+ const [, , content] = enrichFileMock.mock.calls[0];
169
+ expect(content).toBe("posthog.capture('x');\n");
170
+ });
171
+
172
+ test("handles wrapped [{type:'text', text:'...'}] tool_response shape", async () => {
173
+ enrichFileMock.mockReset();
174
+ enrichFileMock.mockResolvedValueOnce("enriched");
175
+
176
+ const cache: EnrichedReadCache = new Map();
177
+ const hook = createReadEnrichmentHook(stubDeps, cache);
178
+ await hook(
179
+ buildReadHookInput({
180
+ tool_response: [{ type: "text", text: " 1\tfoo" }],
181
+ }),
182
+ undefined,
183
+ { signal: new AbortController().signal },
184
+ );
185
+
186
+ const [, , content] = enrichFileMock.mock.calls[0];
187
+ expect(content).toBe("foo");
188
+ });
189
+ });
@@ -1,8 +1,101 @@
1
1
  import type { HookCallback, HookInput } from "@anthropic-ai/claude-agent-sdk";
2
+ import {
3
+ enrichFileForAgent,
4
+ type FileEnrichmentDeps,
5
+ } from "../../enrichment/file-enricher";
2
6
  import type { Logger } from "../../utils/logger";
7
+ import { stripCatLineNumbers } from "./conversion/sdk-to-acp";
3
8
  import type { SettingsManager } from "./session/settings";
4
9
  import type { CodeExecutionMode } from "./tools";
5
10
 
11
+ function extractTextFromToolResponse(response: unknown): string | null {
12
+ if (typeof response === "string") return response;
13
+ if (!response) return null;
14
+ if (Array.isArray(response)) {
15
+ const parts: string[] = [];
16
+ for (const part of response) {
17
+ if (typeof part === "string") {
18
+ parts.push(part);
19
+ } else if (
20
+ part &&
21
+ typeof part === "object" &&
22
+ "text" in part &&
23
+ typeof (part as { text?: unknown }).text === "string"
24
+ ) {
25
+ parts.push((part as { text: string }).text);
26
+ }
27
+ }
28
+ return parts.length > 0 ? parts.join("") : null;
29
+ }
30
+ if (typeof response === "object" && response !== null) {
31
+ const maybe = response as {
32
+ content?: unknown;
33
+ text?: unknown;
34
+ file?: { content?: unknown };
35
+ };
36
+ if (
37
+ maybe.file &&
38
+ typeof maybe.file === "object" &&
39
+ typeof maybe.file.content === "string"
40
+ ) {
41
+ return maybe.file.content;
42
+ }
43
+ if (typeof maybe.text === "string") return maybe.text;
44
+ if (maybe.content) return extractTextFromToolResponse(maybe.content);
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Per-toolUseId handoff from the PostToolUse hook to `toolUpdateFromToolResult`.
51
+ * Can't emit a standalone `tool_call_update` because the SDK emits its own
52
+ * when it processes the tool_result, and the renderer applies it via
53
+ * `Object.assign` — our earlier update would be overwritten.
54
+ */
55
+ export type EnrichedReadCache = Map<string, string>;
56
+
57
+ export const createReadEnrichmentHook =
58
+ (deps: FileEnrichmentDeps, cache: EnrichedReadCache): HookCallback =>
59
+ async (input: HookInput) => {
60
+ if (input.hook_event_name !== "PostToolUse") return { continue: true };
61
+ if (input.tool_name !== "Read") return { continue: true };
62
+
63
+ const toolInput = input.tool_input as { file_path?: string } | undefined;
64
+ const filePath = toolInput?.file_path;
65
+ if (!filePath) return { continue: true };
66
+
67
+ const raw = extractTextFromToolResponse(input.tool_response);
68
+ if (!raw) return { continue: true };
69
+
70
+ const enriched = await enrichFileForAgent(
71
+ deps,
72
+ filePath,
73
+ stripCatLineNumbers(raw),
74
+ );
75
+ if (!enriched) return { continue: true };
76
+
77
+ if (input.tool_use_id) {
78
+ cache.set(input.tool_use_id, enriched);
79
+ }
80
+
81
+ return {
82
+ continue: true,
83
+ hookSpecificOutput: {
84
+ hookEventName: "PostToolUse" as const,
85
+ additionalContext: [
86
+ `## PostHog metadata for ${filePath}`,
87
+ "",
88
+ "The file below is annotated with live data from the user's PostHog project:",
89
+ "flag type / rollout / staleness / linked experiment, and for events the verification status,",
90
+ "30-day volume, and unique-user count. Treat these as authoritative product context —",
91
+ "they describe what is actually running in production.",
92
+ "",
93
+ enriched,
94
+ ].join("\n"),
95
+ },
96
+ };
97
+ };
98
+
6
99
  const toolUseCallbacks: {
7
100
  [toolUseId: string]: {
8
101
  onPostToolUseHook?: (
@@ -61,9 +154,6 @@ export const createPostToolUseHook =
61
154
  );
62
155
  delete toolUseCallbacks[toolUseID];
63
156
  } else {
64
- logger?.error(
65
- `No onPostToolUseHook found for tool use ID: ${toolUseID}`,
66
- );
67
157
  delete toolUseCallbacks[toolUseID];
68
158
  }
69
159
  }
@@ -164,7 +164,8 @@ async function applyPlanApproval(
164
164
 
165
165
  if (
166
166
  response.outcome?.outcome === "selected" &&
167
- (response.outcome.optionId === "default" ||
167
+ (response.outcome.optionId === "auto" ||
168
+ response.outcome.optionId === "default" ||
168
169
  response.outcome.optionId === "acceptEdits" ||
169
170
  response.outcome.optionId === "bypassPermissions")
170
171
  ) {
@@ -106,6 +106,11 @@ export function buildExitPlanModePermissionOptions(): PermissionOption[] {
106
106
  }
107
107
 
108
108
  options.push(
109
+ {
110
+ kind: "allow_always",
111
+ name: 'Yes, and use "auto" mode',
112
+ optionId: "auto",
113
+ },
109
114
  {
110
115
  kind: "allow_always",
111
116
  name: "Yes, and auto-accept edits",
@@ -30,14 +30,17 @@ const MODELS_WITH_EFFORT = new Set([
30
30
  "claude-sonnet-4-6",
31
31
  ]);
32
32
 
33
- const MODELS_WITH_MAX_EFFORT = new Set(["claude-opus-4-6", "claude-opus-4-7"]);
33
+ const MODELS_WITH_XHIGH_EFFORT = new Set([
34
+ "claude-opus-4-6",
35
+ "claude-opus-4-7",
36
+ ]);
34
37
 
35
38
  export function supportsEffort(modelId: string): boolean {
36
39
  return MODELS_WITH_EFFORT.has(modelId);
37
40
  }
38
41
 
39
- export function supportsMaxEffort(modelId: string): boolean {
40
- return MODELS_WITH_MAX_EFFORT.has(modelId);
42
+ export function supportsXhighEffort(modelId: string): boolean {
43
+ return MODELS_WITH_XHIGH_EFFORT.has(modelId);
41
44
  }
42
45
 
43
46
  const MODELS_TO_EXCLUDE_MCP_TOOLS = new Set(["claude-haiku-4-5"]);
@@ -60,8 +63,11 @@ export function getEffortOptions(modelId: string): EffortOption[] | null {
60
63
  { value: "high", name: "High" },
61
64
  ];
62
65
 
63
- if (supportsMaxEffort(modelId)) {
64
- options.push({ value: "max", name: "Max" });
66
+ if (supportsXhighEffort(modelId)) {
67
+ options.push(
68
+ { value: "xhigh", name: "Extra High" },
69
+ { value: "max", name: "Max" },
70
+ );
65
71
  }
66
72
 
67
73
  return options;
@@ -10,12 +10,15 @@ import type {
10
10
  SpawnedProcess,
11
11
  SpawnOptions,
12
12
  } from "@anthropic-ai/claude-agent-sdk";
13
+ import type { FileEnrichmentDeps } from "../../../enrichment/file-enricher";
13
14
  import { IS_ROOT } from "../../../utils/common";
14
15
  import type { Logger } from "../../../utils/logger";
15
16
  import {
16
17
  createPostToolUseHook,
17
18
  createPreToolUseHook,
19
+ createReadEnrichmentHook,
18
20
  createSubagentRewriteHook,
21
+ type EnrichedReadCache,
19
22
  type OnModeChange,
20
23
  } from "../hooks";
21
24
  import type { CodeExecutionMode } from "../tools";
@@ -49,6 +52,8 @@ export interface BuildOptionsParams {
49
52
  onProcessSpawned?: (info: ProcessSpawnedInfo) => void;
50
53
  onProcessExited?: (pid: number) => void;
51
54
  effort?: EffortLevel;
55
+ enrichmentDeps?: FileEnrichmentDeps;
56
+ enrichedReadCache?: EnrichedReadCache;
52
57
  }
53
58
 
54
59
  export function buildSystemPrompt(
@@ -100,6 +105,8 @@ function buildEnvironment(): Record<string, string> {
100
105
  CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL: "true",
101
106
  // Offload all MCP tools by default
102
107
  ENABLE_TOOL_SEARCH: "auto:0",
108
+ // Enable idle state as end-of-turn signal (required for SDK 0.2.114+)
109
+ CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: "1",
103
110
  };
104
111
  }
105
112
 
@@ -108,14 +115,21 @@ function buildHooks(
108
115
  onModeChange: OnModeChange | undefined,
109
116
  settingsManager: SettingsManager,
110
117
  logger: Logger,
118
+ enrichmentDeps: FileEnrichmentDeps | undefined,
119
+ enrichedReadCache: EnrichedReadCache | undefined,
111
120
  ): Options["hooks"] {
121
+ const postToolUseHooks = [createPostToolUseHook({ onModeChange, logger })];
122
+ if (enrichmentDeps && enrichedReadCache) {
123
+ postToolUseHooks.push(
124
+ createReadEnrichmentHook(enrichmentDeps, enrichedReadCache),
125
+ );
126
+ }
127
+
112
128
  return {
113
129
  ...userHooks,
114
130
  PostToolUse: [
115
131
  ...(userHooks?.PostToolUse || []),
116
- {
117
- hooks: [createPostToolUseHook({ onModeChange, logger })],
118
- },
132
+ { hooks: postToolUseHooks },
119
133
  ],
120
134
  PreToolUse: [
121
135
  ...(userHooks?.PreToolUse || []),
@@ -269,6 +283,8 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
269
283
  params.onModeChange,
270
284
  params.settingsManager,
271
285
  params.logger,
286
+ params.enrichmentDeps,
287
+ params.enrichedReadCache,
272
288
  ),
273
289
  outputFormat: params.outputFormat,
274
290
  abortController: getAbortController(
@@ -132,7 +132,13 @@ async function loadSettingsFile(
132
132
  try {
133
133
  const content = await fs.promises.readFile(filePath, "utf-8");
134
134
  return JSON.parse(content) as ClaudeCodeSettings;
135
- } catch {
135
+ } catch (error) {
136
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
137
+ return {};
138
+ }
139
+ process.stderr.write(
140
+ `[SettingsManager] Failed to load settings from ${filePath}: ${error}\n`,
141
+ );
136
142
  return {};
137
143
  }
138
144
  }
@@ -179,17 +185,20 @@ export class SettingsManager {
179
185
  private enterpriseSettings: ClaudeCodeSettings = {};
180
186
  private mergedSettings: ClaudeCodeSettings = {};
181
187
  private initialized = false;
188
+ private initPromise: Promise<void> | null = null;
182
189
 
183
190
  constructor(cwd: string) {
184
191
  this.cwd = cwd;
185
192
  }
186
193
 
187
194
  async initialize(): Promise<void> {
188
- if (this.initialized) {
189
- return;
190
- }
191
- await this.loadAllSettings();
192
- this.initialized = true;
195
+ if (this.initialized) return;
196
+ if (this.initPromise) return this.initPromise;
197
+ this.initPromise = this.loadAllSettings().then(() => {
198
+ this.initialized = true;
199
+ this.initPromise = null;
200
+ });
201
+ return this.initPromise;
193
202
  }
194
203
 
195
204
  private getUserSettingsPath(): string {
@@ -311,9 +320,8 @@ export class SettingsManager {
311
320
  }
312
321
 
313
322
  async setCwd(cwd: string): Promise<void> {
314
- if (this.cwd === cwd) {
315
- return;
316
- }
323
+ if (this.cwd === cwd) return;
324
+ if (this.initPromise) await this.initPromise;
317
325
  this.dispose();
318
326
  this.cwd = cwd;
319
327
  this.initialized = false;
@@ -41,10 +41,10 @@ const BASE_ALLOWED_TOOLS = [
41
41
  ];
42
42
 
43
43
  const AUTO_ALLOWED_TOOLS: Record<string, Set<string>> = {
44
+ auto: new Set(BASE_ALLOWED_TOOLS),
44
45
  default: new Set(BASE_ALLOWED_TOOLS),
45
46
  acceptEdits: new Set([...BASE_ALLOWED_TOOLS, ...WRITE_TOOLS]),
46
47
  plan: new Set(BASE_ALLOWED_TOOLS),
47
- // dontAsk: new Set(BASE_ALLOWED_TOOLS),
48
48
  };
49
49
 
50
50
  export function isToolAllowedForMode(
@@ -13,7 +13,7 @@ import type { BaseSession } from "../base-acp-agent";
13
13
  import type { SettingsManager } from "./session/settings";
14
14
  import type { CodeExecutionMode } from "./tools";
15
15
 
16
- export type EffortLevel = "low" | "medium" | "high" | "max";
16
+ export type EffortLevel = "low" | "medium" | "high" | "xhigh" | "max";
17
17
 
18
18
  export type AccumulatedUsage = {
19
19
  inputTokens: number;
@@ -62,6 +62,7 @@ export type Session = BaseSession & {
62
62
  promptRunning: boolean;
63
63
  pendingMessages: Map<string, PendingMessage>;
64
64
  nextPendingOrder: number;
65
+ emitRawSDKMessages: boolean | SDKMessageFilter[];
65
66
  };
66
67
 
67
68
  export type ToolUseCache = {
@@ -99,6 +100,11 @@ export type ToolUpdateMeta = {
99
100
  terminal_exit?: TerminalExit;
100
101
  };
101
102
 
103
+ export type SDKMessageFilter = {
104
+ type: string;
105
+ subtype?: string;
106
+ };
107
+
102
108
  export type NewSessionMeta = {
103
109
  taskRunId?: string;
104
110
  disableBuiltInTools?: boolean;
@@ -113,5 +119,6 @@ export type NewSessionMeta = {
113
119
  jsonSchema?: Record<string, unknown> | null;
114
120
  claudeCode?: {
115
121
  options?: Options;
122
+ emitRawSDKMessages?: boolean | SDKMessageFilter[];
116
123
  };
117
124
  };
@@ -41,6 +41,10 @@ import {
41
41
  POSTHOG_METHODS,
42
42
  POSTHOG_NOTIFICATIONS,
43
43
  } from "../../acp-extensions";
44
+ import {
45
+ createEnrichment,
46
+ type Enrichment,
47
+ } from "../../enrichment/file-enricher";
44
48
  import {
45
49
  type CodeExecutionMode,
46
50
  type CodexNativeMode,
@@ -48,7 +52,7 @@ import {
48
52
  isCodexNativeMode,
49
53
  type PermissionMode,
50
54
  } from "../../execution-mode";
51
- import type { ProcessSpawnedCallback } from "../../types";
55
+ import type { PostHogAPIConfig, ProcessSpawnedCallback } from "../../types";
52
56
  import { Logger } from "../../utils/logger";
53
57
  import {
54
58
  nodeReadableToWebReadable,
@@ -86,6 +90,7 @@ interface NewSessionMeta {
86
90
  export interface CodexAcpAgentOptions {
87
91
  codexProcessOptions: CodexProcessOptions;
88
92
  processCallbacks?: ProcessSpawnedCallback;
93
+ posthogApiConfig?: PostHogAPIConfig;
89
94
  }
90
95
 
91
96
  type CodexSession = BaseSession & {
@@ -119,6 +124,7 @@ function prependPrContext(params: PromptRequest): PromptRequest {
119
124
  }
120
125
 
121
126
  const CODEX_NATIVE_MODE: Record<CodeExecutionMode, CodexNativeMode> = {
127
+ auto: "auto",
122
128
  default: "auto",
123
129
  acceptEdits: "auto",
124
130
  plan: "read-only",
@@ -168,6 +174,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
168
174
  // Snapshot of the initialize() request so refreshSession can replay the
169
175
  // same handshake against a respawned codex-acp subprocess.
170
176
  private lastInitRequest?: InitializeRequest;
177
+ private enrichment?: Enrichment;
171
178
 
172
179
  constructor(client: AgentSideConnection, options: CodexAcpAgentOptions) {
173
180
  super(client);
@@ -205,12 +212,16 @@ export class CodexAcpAgent extends BaseAcpAgent {
205
212
 
206
213
  this.sessionState = createSessionState("", cwd);
207
214
 
215
+ this.enrichment = createEnrichment(options.posthogApiConfig, this.logger);
216
+
208
217
  // Create the ClientSideConnection to codex-acp.
209
218
  // The Client handler delegates all requests from codex-acp to the upstream
210
219
  // PostHog Code client via our AgentSideConnection.
211
220
  this.codexConnection = new ClientSideConnection(
212
221
  (_agent) =>
213
- createCodexClient(this.client, this.logger, this.sessionState),
222
+ createCodexClient(this.client, this.logger, this.sessionState, {
223
+ enrichmentDeps: this.enrichment?.deps,
224
+ }),
214
225
  codexStream,
215
226
  );
216
227
  }
@@ -672,5 +683,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
672
683
  } catch (err) {
673
684
  this.logger.warn("Failed to kill codex-acp process", { error: err });
674
685
  }
686
+ this.enrichment?.dispose();
687
+ this.enrichment = undefined;
675
688
  }
676
689
  }