@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.
- package/dist/adapters/claude/conversion/tool-use-to-acp.d.ts +9 -0
- package/dist/adapters/claude/conversion/tool-use-to-acp.js +15 -1
- package/dist/adapters/claude/conversion/tool-use-to-acp.js.map +1 -1
- package/dist/adapters/claude/permissions/permission-options.js +18 -11
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/session/jsonl-hydration.js.map +1 -1
- package/dist/adapters/claude/session/models.d.ts +2 -2
- package/dist/adapters/claude/session/models.js +12 -6
- package/dist/adapters/claude/session/models.js.map +1 -1
- package/dist/adapters/claude/tools.js +15 -13
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/adapters/reasoning-effort.d.ts +1 -1
- package/dist/adapters/reasoning-effort.js +11 -5
- package/dist/adapters/reasoning-effort.js.map +1 -1
- package/dist/agent.d.ts +2 -0
- package/dist/agent.js +6946 -587
- package/dist/agent.js.map +1 -1
- package/dist/execution-mode.d.ts +1 -1
- package/dist/execution-mode.js +14 -12
- package/dist/execution-mode.js.map +1 -1
- package/dist/posthog-api.js +4 -3
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.d.ts +1 -1
- package/dist/server/agent-server.js +9260 -2939
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +11289 -4967
- package/dist/server/bin.cjs.map +1 -1
- package/dist/types.d.ts +8 -0
- package/package.json +7 -6
- package/src/adapters/acp-connection.ts +14 -1
- package/src/adapters/claude/UPSTREAM.md +24 -4
- package/src/adapters/claude/claude-agent.ts +161 -14
- package/src/adapters/claude/conversion/sdk-to-acp.ts +14 -2
- package/src/adapters/claude/conversion/tool-use-to-acp.ts +18 -1
- package/src/adapters/claude/hooks.test.ts +189 -0
- package/src/adapters/claude/hooks.ts +93 -3
- package/src/adapters/claude/permissions/permission-handlers.ts +2 -1
- package/src/adapters/claude/permissions/permission-options.ts +5 -0
- package/src/adapters/claude/session/models.ts +11 -5
- package/src/adapters/claude/session/options.ts +19 -3
- package/src/adapters/claude/session/settings.ts +17 -9
- package/src/adapters/claude/tools.ts +1 -1
- package/src/adapters/claude/types.ts +8 -1
- package/src/adapters/codex/codex-agent.ts +15 -2
- package/src/adapters/codex/codex-client.test.ts +112 -0
- package/src/adapters/codex/codex-client.ts +14 -1
- package/src/adapters/reasoning-effort.ts +6 -1
- package/src/agent.ts +6 -0
- package/src/enrichment/file-enricher.test.ts +163 -0
- package/src/enrichment/file-enricher.ts +82 -0
- package/src/execution-mode.test.ts +1 -0
- package/src/execution-mode.ts +13 -11
- package/src/server/bin.ts +1 -1
- package/src/server/types.ts +1 -1
- package/src/types.ts +6 -0
|
@@ -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 === "
|
|
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
|
|
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
|
|
40
|
-
return
|
|
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 (
|
|
64
|
-
options.push(
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
}
|