@posthog/agent 2.3.425 → 2.3.449
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/permissions/permission-options.d.ts +1 -1
- package/dist/adapters/claude/permissions/permission-options.js +26 -7
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/agent.js +36 -10
- package/dist/agent.js.map +1 -1
- package/dist/posthog-api.js +1 -1
- package/dist/posthog-api.js.map +1 -1
- package/dist/server/agent-server.js +49 -18
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +49 -18
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/claude-agent.ts +7 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +2 -2
- package/src/adapters/claude/permissions/permission-options.test.ts +51 -0
- package/src/adapters/claude/permissions/permission-options.ts +33 -7
- package/src/adapters/claude/types.ts +1 -0
- package/src/server/agent-server.test.ts +38 -0
- package/src/server/agent-server.ts +15 -8
package/package.json
CHANGED
|
@@ -1047,6 +1047,9 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1047
1047
|
}
|
|
1048
1048
|
const previousMode = this.session.permissionMode;
|
|
1049
1049
|
this.session.permissionMode = modeId as CodeExecutionMode;
|
|
1050
|
+
if (modeId === "plan" && previousMode !== "plan") {
|
|
1051
|
+
this.session.modeBeforePlan = previousMode;
|
|
1052
|
+
}
|
|
1050
1053
|
try {
|
|
1051
1054
|
await this.session.query.setPermissionMode(modeId as CodeExecutionMode);
|
|
1052
1055
|
} catch (error) {
|
|
@@ -1343,7 +1346,11 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1343
1346
|
private createOnModeChange() {
|
|
1344
1347
|
return async (newMode: CodeExecutionMode) => {
|
|
1345
1348
|
if (this.session) {
|
|
1349
|
+
const previousMode = this.session.permissionMode;
|
|
1346
1350
|
this.session.permissionMode = newMode;
|
|
1351
|
+
if (newMode === "plan" && previousMode !== "plan") {
|
|
1352
|
+
this.session.modeBeforePlan = previousMode;
|
|
1353
|
+
}
|
|
1347
1354
|
}
|
|
1348
1355
|
await this.updateConfigOption("mode", newMode);
|
|
1349
1356
|
};
|
|
@@ -142,7 +142,7 @@ async function requestPlanApproval(
|
|
|
142
142
|
context: ToolHandlerContext,
|
|
143
143
|
updatedInput: Record<string, unknown>,
|
|
144
144
|
): Promise<RequestPermissionResponse> {
|
|
145
|
-
const { client, sessionId, toolUseID } = context;
|
|
145
|
+
const { client, sessionId, toolUseID, session } = context;
|
|
146
146
|
|
|
147
147
|
const toolInfo = toolInfoFromToolUse({
|
|
148
148
|
name: context.toolName,
|
|
@@ -150,7 +150,7 @@ async function requestPlanApproval(
|
|
|
150
150
|
});
|
|
151
151
|
|
|
152
152
|
return await client.requestPermission({
|
|
153
|
-
options: buildExitPlanModePermissionOptions(),
|
|
153
|
+
options: buildExitPlanModePermissionOptions(session.modeBeforePlan),
|
|
154
154
|
sessionId,
|
|
155
155
|
toolCall: {
|
|
156
156
|
toolCallId: toolUseID,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildExitPlanModePermissionOptions } from "./permission-options";
|
|
3
|
+
|
|
4
|
+
describe("buildExitPlanModePermissionOptions", () => {
|
|
5
|
+
it("does not relabel any option when no previous mode is provided", () => {
|
|
6
|
+
const options = buildExitPlanModePermissionOptions();
|
|
7
|
+
for (const opt of options) {
|
|
8
|
+
expect(opt.name).not.toMatch(/^Yes, continue/);
|
|
9
|
+
}
|
|
10
|
+
expect(options[options.length - 1].optionId).toBe("reject_with_feedback");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("promotes the previous mode to the first position with a continue label", () => {
|
|
14
|
+
const options = buildExitPlanModePermissionOptions("default");
|
|
15
|
+
expect(options[0]).toMatchObject({
|
|
16
|
+
optionId: "default",
|
|
17
|
+
name: "Yes, continue manually approving edits",
|
|
18
|
+
});
|
|
19
|
+
expect(options[options.length - 1].optionId).toBe("reject_with_feedback");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("relabels the auto option when it is the previous mode", () => {
|
|
23
|
+
const options = buildExitPlanModePermissionOptions("auto");
|
|
24
|
+
expect(options[0]).toMatchObject({
|
|
25
|
+
optionId: "auto",
|
|
26
|
+
name: 'Yes, continue in "auto" mode',
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("relabels the acceptEdits option when it is the previous mode", () => {
|
|
31
|
+
const options = buildExitPlanModePermissionOptions("acceptEdits");
|
|
32
|
+
expect(options[0]).toMatchObject({
|
|
33
|
+
optionId: "acceptEdits",
|
|
34
|
+
name: "Yes, continue auto-accepting edits",
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("ignores an unknown previous mode", () => {
|
|
39
|
+
const options = buildExitPlanModePermissionOptions("plan");
|
|
40
|
+
expect(options[0].name).toMatch(/^Yes, /);
|
|
41
|
+
expect(options[0].name).not.toMatch(/^Yes, continue/);
|
|
42
|
+
expect(options[options.length - 1].optionId).toBe("reject_with_feedback");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("always keeps the reject option last", () => {
|
|
46
|
+
for (const previousMode of ["auto", "acceptEdits", "default", undefined]) {
|
|
47
|
+
const options = buildExitPlanModePermissionOptions(previousMode);
|
|
48
|
+
expect(options[options.length - 1].optionId).toBe("reject_with_feedback");
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -92,7 +92,16 @@ export function buildPermissionOptions(
|
|
|
92
92
|
return permissionOptions("Yes, always allow");
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
const CONTINUE_LABELS: Record<string, string> = {
|
|
96
|
+
auto: 'Yes, continue in "auto" mode',
|
|
97
|
+
acceptEdits: "Yes, continue auto-accepting edits",
|
|
98
|
+
default: "Yes, continue manually approving edits",
|
|
99
|
+
bypassPermissions: "Yes, continue bypassing all permissions",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export function buildExitPlanModePermissionOptions(
|
|
103
|
+
previousMode?: string,
|
|
104
|
+
): PermissionOption[] {
|
|
96
105
|
const options: PermissionOption[] = [];
|
|
97
106
|
|
|
98
107
|
if (ALLOW_BYPASS) {
|
|
@@ -119,13 +128,30 @@ export function buildExitPlanModePermissionOptions(): PermissionOption[] {
|
|
|
119
128
|
name: "Yes, and manually approve edits",
|
|
120
129
|
optionId: "default",
|
|
121
130
|
},
|
|
122
|
-
{
|
|
123
|
-
kind: "reject_once",
|
|
124
|
-
name: "No, and tell the agent what to do differently",
|
|
125
|
-
optionId: "reject_with_feedback",
|
|
126
|
-
_meta: { customInput: true },
|
|
127
|
-
},
|
|
128
131
|
);
|
|
129
132
|
|
|
133
|
+
const previousIndex = previousMode
|
|
134
|
+
? options.findIndex((opt) => opt.optionId === previousMode)
|
|
135
|
+
: -1;
|
|
136
|
+
if (previousIndex > 0) {
|
|
137
|
+
const [previous] = options.splice(previousIndex, 1);
|
|
138
|
+
const continueLabel = CONTINUE_LABELS[previous.optionId];
|
|
139
|
+
options.unshift(
|
|
140
|
+
continueLabel ? { ...previous, name: continueLabel } : previous,
|
|
141
|
+
);
|
|
142
|
+
} else if (previousIndex === 0) {
|
|
143
|
+
const continueLabel = CONTINUE_LABELS[options[0].optionId];
|
|
144
|
+
if (continueLabel) {
|
|
145
|
+
options[0] = { ...options[0], name: continueLabel };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
options.push({
|
|
150
|
+
kind: "reject_once",
|
|
151
|
+
name: "No, and tell the agent what to do differently",
|
|
152
|
+
optionId: "reject_with_feedback",
|
|
153
|
+
_meta: { customInput: true },
|
|
154
|
+
});
|
|
155
|
+
|
|
130
156
|
return options;
|
|
131
157
|
}
|
|
@@ -202,6 +202,44 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
202
202
|
}, 30000);
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
describe("turn completion", () => {
|
|
206
|
+
it("persists structured turn completion notifications", () => {
|
|
207
|
+
const appendRawLine = vi.fn();
|
|
208
|
+
const testServer = new AgentServer({
|
|
209
|
+
port,
|
|
210
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
211
|
+
repositoryPath: repo.path,
|
|
212
|
+
apiUrl: "http://localhost:8000",
|
|
213
|
+
apiKey: "test-api-key",
|
|
214
|
+
projectId: 1,
|
|
215
|
+
mode: "interactive",
|
|
216
|
+
taskId: "test-task-id",
|
|
217
|
+
runId: "test-run-id",
|
|
218
|
+
}) as unknown as {
|
|
219
|
+
session: unknown;
|
|
220
|
+
broadcastTurnComplete(stopReason: string): void;
|
|
221
|
+
};
|
|
222
|
+
testServer.session = {
|
|
223
|
+
acpSessionId: "session-1",
|
|
224
|
+
payload: { run_id: "run-1" },
|
|
225
|
+
logWriter: { appendRawLine },
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
testServer.broadcastTurnComplete("end_turn");
|
|
229
|
+
|
|
230
|
+
expect(appendRawLine).toHaveBeenCalledTimes(1);
|
|
231
|
+
expect(appendRawLine.mock.calls[0][0]).toBe("run-1");
|
|
232
|
+
expect(JSON.parse(appendRawLine.mock.calls[0][1])).toEqual({
|
|
233
|
+
jsonrpc: "2.0",
|
|
234
|
+
method: "_posthog/turn_complete",
|
|
235
|
+
params: {
|
|
236
|
+
sessionId: "session-1",
|
|
237
|
+
stopReason: "end_turn",
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
205
243
|
describe("GET /events", () => {
|
|
206
244
|
it("returns 401 without authorization header", async () => {
|
|
207
245
|
await createServer().start();
|
|
@@ -2226,18 +2226,25 @@ ${attributionInstructions}
|
|
|
2226
2226
|
|
|
2227
2227
|
private broadcastTurnComplete(stopReason: string): void {
|
|
2228
2228
|
if (!this.session) return;
|
|
2229
|
+
const notification = {
|
|
2230
|
+
jsonrpc: "2.0",
|
|
2231
|
+
method: POSTHOG_NOTIFICATIONS.TURN_COMPLETE,
|
|
2232
|
+
params: {
|
|
2233
|
+
sessionId: this.session.acpSessionId,
|
|
2234
|
+
stopReason,
|
|
2235
|
+
},
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2229
2238
|
this.broadcastEvent({
|
|
2230
2239
|
type: "notification",
|
|
2231
2240
|
timestamp: new Date().toISOString(),
|
|
2232
|
-
notification
|
|
2233
|
-
jsonrpc: "2.0",
|
|
2234
|
-
method: POSTHOG_NOTIFICATIONS.TURN_COMPLETE,
|
|
2235
|
-
params: {
|
|
2236
|
-
sessionId: this.session.acpSessionId,
|
|
2237
|
-
stopReason,
|
|
2238
|
-
},
|
|
2239
|
-
},
|
|
2241
|
+
notification,
|
|
2240
2242
|
});
|
|
2243
|
+
|
|
2244
|
+
this.session.logWriter.appendRawLine(
|
|
2245
|
+
this.session.payload.run_id,
|
|
2246
|
+
JSON.stringify(notification),
|
|
2247
|
+
);
|
|
2241
2248
|
}
|
|
2242
2249
|
|
|
2243
2250
|
private broadcastEvent(event: Record<string, unknown>): void {
|