@posthog/agent 2.3.259 → 2.3.261
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/agent.js +1 -1
- 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.d.ts +11 -0
- package/dist/server/agent-server.js +89 -16
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +98 -17
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +3 -3
- package/src/server/agent-server.test.ts +140 -7
- package/src/server/agent-server.ts +141 -25
- package/src/server/bin.ts +13 -0
- package/src/server/question-relay.test.ts +34 -5
- package/src/server/types.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.261",
|
|
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": {
|
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
"tsx": "^4.20.6",
|
|
83
83
|
"typescript": "^5.5.0",
|
|
84
84
|
"vitest": "^2.1.8",
|
|
85
|
-
"@posthog/
|
|
86
|
-
"@posthog/
|
|
85
|
+
"@posthog/git": "1.0.0",
|
|
86
|
+
"@posthog/shared": "1.0.0"
|
|
87
87
|
},
|
|
88
88
|
"dependencies": {
|
|
89
89
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
@@ -20,6 +20,7 @@ interface TestableServer {
|
|
|
20
20
|
detectAndAttachPrUrl(payload: unknown, update: unknown): void;
|
|
21
21
|
detectedPrUrl: string | null;
|
|
22
22
|
buildCloudSystemPrompt(prUrl?: string | null): string;
|
|
23
|
+
buildDetectedPrContext(prUrl: string): string;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
// The Claude Agent SDK has an internal readMessages() loop that rejects with
|
|
@@ -380,14 +381,17 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
380
381
|
});
|
|
381
382
|
|
|
382
383
|
describe("buildCloudSystemPrompt", () => {
|
|
383
|
-
it("returns
|
|
384
|
+
it("returns review-first prompt for existing PRs on non-Slack runs", () => {
|
|
384
385
|
const s = createServer();
|
|
385
386
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt(
|
|
386
387
|
"https://github.com/org/repo/pull/1",
|
|
387
388
|
);
|
|
388
|
-
expect(prompt).toContain("
|
|
389
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
389
390
|
expect(prompt).toContain("https://github.com/org/repo/pull/1");
|
|
390
|
-
expect(prompt).toContain(
|
|
391
|
+
expect(prompt).toContain(
|
|
392
|
+
"Do NOT create new commits, push to the branch, or update the pull request unless the user explicitly asks.",
|
|
393
|
+
);
|
|
394
|
+
expect(prompt).not.toContain("gh pr checkout");
|
|
391
395
|
expect(prompt).not.toContain("Create a draft pull request");
|
|
392
396
|
expect(prompt).toContain("Generated-By: PostHog Code");
|
|
393
397
|
expect(prompt).toContain("Task-Id: test-task-id");
|
|
@@ -396,12 +400,13 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
396
400
|
it("returns default prompt when no prUrl", () => {
|
|
397
401
|
const s = createServer();
|
|
398
402
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt();
|
|
399
|
-
expect(prompt).toContain("
|
|
400
|
-
expect(prompt).toContain(
|
|
401
|
-
|
|
403
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
404
|
+
expect(prompt).toContain(
|
|
405
|
+
"Do NOT create a branch, commit, push, or open a pull request unless the user explicitly asks.",
|
|
406
|
+
);
|
|
402
407
|
expect(prompt).toContain("Generated-By: PostHog Code");
|
|
403
408
|
expect(prompt).toContain("Task-Id: test-task-id");
|
|
404
|
-
expect(prompt).toContain("
|
|
409
|
+
expect(prompt).not.toContain("gh pr create --draft");
|
|
405
410
|
});
|
|
406
411
|
|
|
407
412
|
it("returns default prompt when prUrl is null", () => {
|
|
@@ -409,12 +414,41 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
409
414
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt(
|
|
410
415
|
null,
|
|
411
416
|
);
|
|
417
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("returns auto-PR prompt for Slack-origin runs", () => {
|
|
421
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
422
|
+
const s = createServer();
|
|
423
|
+
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt();
|
|
412
424
|
expect(prompt).toContain("posthog-code/");
|
|
413
425
|
expect(prompt).toContain("Create a draft pull request");
|
|
414
426
|
expect(prompt).toContain("gh pr create --draft");
|
|
427
|
+
expect(prompt).toContain("Generated-By: PostHog Code");
|
|
428
|
+
expect(prompt).toContain("Task-Id: test-task-id");
|
|
429
|
+
expect(prompt).toContain("Created with [PostHog Code]");
|
|
430
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("returns PR-update prompt for existing PRs on Slack-origin runs", () => {
|
|
434
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
435
|
+
const s = createServer();
|
|
436
|
+
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt(
|
|
437
|
+
"https://github.com/org/repo/pull/1",
|
|
438
|
+
);
|
|
439
|
+
expect(prompt).toContain(
|
|
440
|
+
"gh pr checkout https://github.com/org/repo/pull/1",
|
|
441
|
+
);
|
|
442
|
+
expect(prompt).toContain(
|
|
443
|
+
"Stage and commit all changes with a clear commit message",
|
|
444
|
+
);
|
|
445
|
+
expect(prompt).toContain("Push to the existing PR branch");
|
|
446
|
+
expect(prompt).not.toContain("Create a draft pull request");
|
|
447
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
415
448
|
});
|
|
416
449
|
|
|
417
450
|
it("includes --base flag when baseBranch is configured", () => {
|
|
451
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
418
452
|
server = new AgentServer({
|
|
419
453
|
port,
|
|
420
454
|
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
@@ -433,13 +467,112 @@ describe("AgentServer HTTP Mode", () => {
|
|
|
433
467
|
expect(prompt).toContain(
|
|
434
468
|
"gh pr create --draft --base add-yolo-to-readme",
|
|
435
469
|
);
|
|
470
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
436
471
|
});
|
|
437
472
|
|
|
438
473
|
it("omits --base flag when baseBranch is not configured", () => {
|
|
474
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
439
475
|
const s = createServer();
|
|
440
476
|
const prompt = (s as unknown as TestableServer).buildCloudSystemPrompt();
|
|
441
477
|
expect(prompt).toContain("gh pr create --draft`");
|
|
442
478
|
expect(prompt).not.toContain("--base");
|
|
479
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("disables auto-publish for Slack-origin runs when createPr is false", () => {
|
|
483
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
484
|
+
server = new AgentServer({
|
|
485
|
+
port,
|
|
486
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
487
|
+
repositoryPath: repo.path,
|
|
488
|
+
apiUrl: "http://localhost:8000",
|
|
489
|
+
apiKey: "test-api-key",
|
|
490
|
+
projectId: 1,
|
|
491
|
+
mode: "interactive",
|
|
492
|
+
taskId: "test-task-id",
|
|
493
|
+
runId: "test-run-id",
|
|
494
|
+
createPr: false,
|
|
495
|
+
});
|
|
496
|
+
const prompt = (
|
|
497
|
+
server as unknown as TestableServer
|
|
498
|
+
).buildCloudSystemPrompt();
|
|
499
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
500
|
+
expect(prompt).not.toContain("gh pr create --draft");
|
|
501
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it("disables auto-publish for existing PRs when createPr is false", () => {
|
|
505
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
506
|
+
server = new AgentServer({
|
|
507
|
+
port,
|
|
508
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
509
|
+
repositoryPath: repo.path,
|
|
510
|
+
apiUrl: "http://localhost:8000",
|
|
511
|
+
apiKey: "test-api-key",
|
|
512
|
+
projectId: 1,
|
|
513
|
+
mode: "interactive",
|
|
514
|
+
taskId: "test-task-id",
|
|
515
|
+
runId: "test-run-id",
|
|
516
|
+
createPr: false,
|
|
517
|
+
});
|
|
518
|
+
const prompt = (
|
|
519
|
+
server as unknown as TestableServer
|
|
520
|
+
).buildCloudSystemPrompt("https://github.com/org/repo/pull/1");
|
|
521
|
+
expect(prompt).toContain("stop with local changes ready for review");
|
|
522
|
+
expect(prompt).not.toContain("gh pr checkout");
|
|
523
|
+
expect(prompt).not.toContain("Push to the existing PR branch");
|
|
524
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe("buildDetectedPrContext", () => {
|
|
529
|
+
const prUrl = "https://github.com/org/repo/pull/1";
|
|
530
|
+
|
|
531
|
+
it("returns review-first PR context for non-Slack runs", () => {
|
|
532
|
+
const s = createServer();
|
|
533
|
+
const context = (s as unknown as TestableServer).buildDetectedPrContext(
|
|
534
|
+
prUrl,
|
|
535
|
+
);
|
|
536
|
+
expect(context).toContain("stop with local changes ready for review");
|
|
537
|
+
expect(context).toContain(
|
|
538
|
+
"Do NOT create commits, push to the PR branch, update the pull request",
|
|
539
|
+
);
|
|
540
|
+
expect(context).not.toContain("gh pr checkout");
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("returns auto-update PR context for Slack-origin runs", () => {
|
|
544
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
545
|
+
const s = createServer();
|
|
546
|
+
const context = (s as unknown as TestableServer).buildDetectedPrContext(
|
|
547
|
+
prUrl,
|
|
548
|
+
);
|
|
549
|
+
expect(context).toContain(`gh pr checkout ${prUrl}`);
|
|
550
|
+
expect(context).toContain(
|
|
551
|
+
"Make changes, commit, and push to that branch",
|
|
552
|
+
);
|
|
553
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it("returns review-first PR context when createPr is false", () => {
|
|
557
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
558
|
+
server = new AgentServer({
|
|
559
|
+
port,
|
|
560
|
+
jwtPublicKey: TEST_PUBLIC_KEY,
|
|
561
|
+
repositoryPath: repo.path,
|
|
562
|
+
apiUrl: "http://localhost:8000",
|
|
563
|
+
apiKey: "test-api-key",
|
|
564
|
+
projectId: 1,
|
|
565
|
+
mode: "interactive",
|
|
566
|
+
taskId: "test-task-id",
|
|
567
|
+
runId: "test-run-id",
|
|
568
|
+
createPr: false,
|
|
569
|
+
});
|
|
570
|
+
const context = (
|
|
571
|
+
server as unknown as TestableServer
|
|
572
|
+
).buildDetectedPrContext(prUrl);
|
|
573
|
+
expect(context).toContain("stop with local changes ready for review");
|
|
574
|
+
expect(context).not.toContain("gh pr checkout");
|
|
575
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
443
576
|
});
|
|
444
577
|
});
|
|
445
578
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ContentBlock,
|
|
3
|
+
RequestPermissionRequest,
|
|
4
|
+
RequestPermissionResponse,
|
|
5
|
+
} from "@agentclientprotocol/sdk";
|
|
2
6
|
import {
|
|
3
7
|
ClientSideConnection,
|
|
4
8
|
ndJsonStream,
|
|
@@ -511,13 +515,9 @@ export class AgentServer {
|
|
|
511
515
|
prompt,
|
|
512
516
|
...(this.detectedPrUrl && {
|
|
513
517
|
_meta: {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
`You MUST:\n` +
|
|
518
|
-
`1. Check out the existing PR branch with \`gh pr checkout ${this.detectedPrUrl}\`\n` +
|
|
519
|
-
`2. Make changes, commit, and push to that branch\n` +
|
|
520
|
-
`You MUST NOT create a new branch, close the existing PR, or create a new PR.`,
|
|
518
|
+
// Keep the live-session PR override aligned with the startup
|
|
519
|
+
// prompt policy so non-Slack runs remain review-first.
|
|
520
|
+
prContext: this.buildDetectedPrContext(this.detectedPrUrl),
|
|
521
521
|
},
|
|
522
522
|
}),
|
|
523
523
|
});
|
|
@@ -1121,13 +1121,53 @@ export class AgentServer {
|
|
|
1121
1121
|
return { append: cloudAppend };
|
|
1122
1122
|
}
|
|
1123
1123
|
|
|
1124
|
+
private getCloudInteractionOrigin(): string | undefined {
|
|
1125
|
+
return (
|
|
1126
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN ??
|
|
1127
|
+
process.env.CODE_INTERACTION_ORIGIN ??
|
|
1128
|
+
process.env.TWIG_INTERACTION_ORIGIN
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Slack-origin cloud runs auto-publish by default. Every other origin is
|
|
1134
|
+
* review-first unless the user explicitly asks, and createPr=false always
|
|
1135
|
+
* disables publishing.
|
|
1136
|
+
*/
|
|
1137
|
+
private shouldAutoPublishCloudChanges(): boolean {
|
|
1138
|
+
return (
|
|
1139
|
+
this.getCloudInteractionOrigin() === "slack" &&
|
|
1140
|
+
this.config.createPr !== false
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
private buildDetectedPrContext(prUrl: string): string {
|
|
1145
|
+
if (!this.shouldAutoPublishCloudChanges()) {
|
|
1146
|
+
return (
|
|
1147
|
+
`An open pull request already exists: ${prUrl}\n` +
|
|
1148
|
+
`Use that PR as context if it is helpful, but stop with local changes ready for review.\n` +
|
|
1149
|
+
`Do NOT create commits, push to the PR branch, update the pull request, create a new branch, or create a new pull request unless the user explicitly asks.`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
return (
|
|
1154
|
+
`IMPORTANT — OVERRIDE PREVIOUS INSTRUCTIONS ABOUT CREATING BRANCHES/PRs.\n` +
|
|
1155
|
+
`You already have an open pull request: ${prUrl}\n` +
|
|
1156
|
+
`You MUST:\n` +
|
|
1157
|
+
`1. Check out the existing PR branch with \`gh pr checkout ${prUrl}\`\n` +
|
|
1158
|
+
`2. Make changes, commit, and push to that branch\n` +
|
|
1159
|
+
`You MUST NOT create a new branch, close the existing PR, or create a new PR.`
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1124
1163
|
private buildCloudSystemPrompt(prUrl?: string | null): string {
|
|
1125
1164
|
const taskId = this.config.taskId;
|
|
1165
|
+
const shouldAutoCreatePr = this.shouldAutoPublishCloudChanges();
|
|
1126
1166
|
const attributionInstructions = `
|
|
1127
1167
|
## Attribution
|
|
1128
1168
|
Do NOT use Claude Code's default attribution (no "Co-Authored-By" trailers, no "Generated with [Claude Code]" lines).
|
|
1129
1169
|
|
|
1130
|
-
|
|
1170
|
+
If you create a commit, add the following trailers to the commit message (after a blank line at the end):
|
|
1131
1171
|
Generated-By: PostHog Code
|
|
1132
1172
|
Task-Id: ${taskId}
|
|
1133
1173
|
|
|
@@ -1143,6 +1183,21 @@ EOF
|
|
|
1143
1183
|
\`\`\``;
|
|
1144
1184
|
|
|
1145
1185
|
if (prUrl) {
|
|
1186
|
+
if (!shouldAutoCreatePr) {
|
|
1187
|
+
return `
|
|
1188
|
+
# Cloud Task Execution
|
|
1189
|
+
|
|
1190
|
+
This task already has an open pull request: ${prUrl}
|
|
1191
|
+
|
|
1192
|
+
Do the requested work, but stop with local changes ready for review.
|
|
1193
|
+
|
|
1194
|
+
Important:
|
|
1195
|
+
- Do NOT create new commits, push to the branch, or update the pull request unless the user explicitly asks.
|
|
1196
|
+
- Do NOT create a new branch or a new pull request.
|
|
1197
|
+
${attributionInstructions}
|
|
1198
|
+
`;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1146
1201
|
return `
|
|
1147
1202
|
# Cloud Task Execution
|
|
1148
1203
|
|
|
@@ -1180,6 +1235,18 @@ Important:
|
|
|
1180
1235
|
`;
|
|
1181
1236
|
}
|
|
1182
1237
|
|
|
1238
|
+
if (!shouldAutoCreatePr) {
|
|
1239
|
+
return `
|
|
1240
|
+
# Cloud Task Execution
|
|
1241
|
+
|
|
1242
|
+
Do the requested work, but stop with local changes ready for review.
|
|
1243
|
+
|
|
1244
|
+
Important:
|
|
1245
|
+
- Do NOT create a branch, commit, push, or open a pull request unless the user explicitly asks.
|
|
1246
|
+
${attributionInstructions}
|
|
1247
|
+
`;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1183
1250
|
return `
|
|
1184
1251
|
# Cloud Task Execution
|
|
1185
1252
|
|
|
@@ -1304,19 +1371,64 @@ ${attributionInstructions}
|
|
|
1304
1371
|
});
|
|
1305
1372
|
}
|
|
1306
1373
|
|
|
1374
|
+
private buildSlackQuestionRelayResponse(
|
|
1375
|
+
payload: JwtPayload,
|
|
1376
|
+
toolMeta: Record<string, unknown> | null | undefined,
|
|
1377
|
+
): RequestPermissionResponse {
|
|
1378
|
+
this.relaySlackQuestion(payload, toolMeta);
|
|
1379
|
+
return {
|
|
1380
|
+
outcome: { outcome: "cancelled" as const },
|
|
1381
|
+
_meta: {
|
|
1382
|
+
message:
|
|
1383
|
+
"This question has been relayed to the Slack thread where this task originated. " +
|
|
1384
|
+
"The user will reply there. Do NOT re-ask the question or pick an answer yourself. " +
|
|
1385
|
+
"Simply let the user know you are waiting for their reply.",
|
|
1386
|
+
},
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
private shouldBlockPublishPermission(
|
|
1391
|
+
params: RequestPermissionRequest,
|
|
1392
|
+
): boolean {
|
|
1393
|
+
if (this.config.createPr !== false) {
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
const meta =
|
|
1398
|
+
params.toolCall?._meta &&
|
|
1399
|
+
typeof params.toolCall._meta === "object" &&
|
|
1400
|
+
!Array.isArray(params.toolCall._meta)
|
|
1401
|
+
? (params.toolCall._meta as Record<string, unknown>)
|
|
1402
|
+
: null;
|
|
1403
|
+
const rawInput =
|
|
1404
|
+
params.toolCall?.rawInput &&
|
|
1405
|
+
typeof params.toolCall.rawInput === "object" &&
|
|
1406
|
+
!Array.isArray(params.toolCall.rawInput)
|
|
1407
|
+
? (params.toolCall.rawInput as Record<string, unknown>)
|
|
1408
|
+
: null;
|
|
1409
|
+
const toolName = typeof meta?.toolName === "string" ? meta.toolName : null;
|
|
1410
|
+
const command =
|
|
1411
|
+
typeof rawInput?.command === "string" ? rawInput.command : null;
|
|
1412
|
+
|
|
1413
|
+
return Boolean(
|
|
1414
|
+
toolName &&
|
|
1415
|
+
(toolName === "Bash" || toolName.includes("bash")) &&
|
|
1416
|
+
command &&
|
|
1417
|
+
/\bgit\s+push\b|\bgh\s+pr\s+(create|edit|ready|merge)\b/.test(command),
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1307
1421
|
private createCloudClient(payload: JwtPayload) {
|
|
1308
1422
|
const mode = this.getEffectiveMode(payload);
|
|
1309
1423
|
const interactionOrigin =
|
|
1424
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN ??
|
|
1310
1425
|
process.env.CODE_INTERACTION_ORIGIN ??
|
|
1311
1426
|
process.env.TWIG_INTERACTION_ORIGIN;
|
|
1312
1427
|
|
|
1313
1428
|
return {
|
|
1314
|
-
requestPermission: async (
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
_meta?: Record<string, unknown> | null;
|
|
1318
|
-
};
|
|
1319
|
-
}) => {
|
|
1429
|
+
requestPermission: async (
|
|
1430
|
+
params: RequestPermissionRequest,
|
|
1431
|
+
): Promise<RequestPermissionResponse> => {
|
|
1320
1432
|
// Background mode: always auto-approve permissions
|
|
1321
1433
|
// Interactive mode: also auto-approve for now (user can monitor via SSE)
|
|
1322
1434
|
// Future: interactive mode could pause and wait for user approval via SSE
|
|
@@ -1335,19 +1447,23 @@ ${attributionInstructions}
|
|
|
1335
1447
|
if (interactionOrigin === "slack") {
|
|
1336
1448
|
const codeToolKind = params.toolCall?._meta?.codeToolKind;
|
|
1337
1449
|
if (codeToolKind === "question") {
|
|
1338
|
-
this.
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
message:
|
|
1343
|
-
"This question has been relayed to the Slack thread where this task originated. " +
|
|
1344
|
-
"The user will reply there. Do NOT re-ask the question or pick an answer yourself. " +
|
|
1345
|
-
"Simply let the user know you are waiting for their reply.",
|
|
1346
|
-
},
|
|
1347
|
-
};
|
|
1450
|
+
return this.buildSlackQuestionRelayResponse(
|
|
1451
|
+
payload,
|
|
1452
|
+
params.toolCall?._meta,
|
|
1453
|
+
);
|
|
1348
1454
|
}
|
|
1349
1455
|
}
|
|
1350
1456
|
|
|
1457
|
+
if (this.shouldBlockPublishPermission(params)) {
|
|
1458
|
+
return {
|
|
1459
|
+
outcome: { outcome: "cancelled" },
|
|
1460
|
+
_meta: {
|
|
1461
|
+
message:
|
|
1462
|
+
"This run is configured to stop before publishing. Do not push commits or create/update pull requests unless the user explicitly asks.",
|
|
1463
|
+
},
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1351
1467
|
return {
|
|
1352
1468
|
outcome: {
|
|
1353
1469
|
outcome: "selected" as const,
|
package/src/server/bin.ts
CHANGED
|
@@ -30,6 +30,16 @@ const envSchema = z.object({
|
|
|
30
30
|
|
|
31
31
|
const program = new Command();
|
|
32
32
|
|
|
33
|
+
function parseBooleanOption(
|
|
34
|
+
raw: string | undefined,
|
|
35
|
+
flag: string,
|
|
36
|
+
): boolean | undefined {
|
|
37
|
+
if (raw === undefined) return undefined;
|
|
38
|
+
if (raw === "true") return true;
|
|
39
|
+
if (raw === "false") return false;
|
|
40
|
+
program.error(`${flag} must be either "true" or "false"`);
|
|
41
|
+
}
|
|
42
|
+
|
|
33
43
|
function parseJsonOption<S extends z.ZodType>(
|
|
34
44
|
raw: string | undefined,
|
|
35
45
|
schema: S,
|
|
@@ -70,6 +80,7 @@ program
|
|
|
70
80
|
"--mcpServers <json>",
|
|
71
81
|
"MCP servers config as JSON array (ACP McpServer[] format)",
|
|
72
82
|
)
|
|
83
|
+
.option("--createPr <boolean>", "Whether this run may publish changes")
|
|
73
84
|
.option("--baseBranch <branch>", "Base branch for PR creation")
|
|
74
85
|
.option(
|
|
75
86
|
"--claudeCodeConfig <json>",
|
|
@@ -93,6 +104,7 @@ program
|
|
|
93
104
|
const env = envResult.data;
|
|
94
105
|
|
|
95
106
|
const mode = options.mode === "background" ? "background" : "interactive";
|
|
107
|
+
const createPr = parseBooleanOption(options.createPr, "--createPr");
|
|
96
108
|
|
|
97
109
|
const mcpServers = parseJsonOption(
|
|
98
110
|
options.mcpServers,
|
|
@@ -122,6 +134,7 @@ program
|
|
|
122
134
|
mode,
|
|
123
135
|
taskId: options.taskId,
|
|
124
136
|
runId: options.runId,
|
|
137
|
+
createPr,
|
|
125
138
|
mcpServers,
|
|
126
139
|
baseBranch: options.baseBranch,
|
|
127
140
|
claudeCode,
|
|
@@ -172,13 +172,13 @@ describe("Question relay", () => {
|
|
|
172
172
|
{ kind: "allow_once", optionId: "allow", name: "Allow" },
|
|
173
173
|
];
|
|
174
174
|
|
|
175
|
-
describe("with
|
|
175
|
+
describe("with POSTHOG_CODE_INTERACTION_ORIGIN=slack", () => {
|
|
176
176
|
beforeEach(() => {
|
|
177
|
-
process.env.
|
|
177
|
+
process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
afterEach(() => {
|
|
181
|
-
delete process.env.
|
|
181
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
it("returns cancelled with relay message for question tool", async () => {
|
|
@@ -220,9 +220,9 @@ describe("Question relay", () => {
|
|
|
220
220
|
});
|
|
221
221
|
});
|
|
222
222
|
|
|
223
|
-
describe("without
|
|
223
|
+
describe("without POSTHOG_CODE_INTERACTION_ORIGIN", () => {
|
|
224
224
|
beforeEach(() => {
|
|
225
|
-
delete process.env.
|
|
225
|
+
delete process.env.POSTHOG_CODE_INTERACTION_ORIGIN;
|
|
226
226
|
});
|
|
227
227
|
|
|
228
228
|
it("auto-approves question tools (no Slack relay)", async () => {
|
|
@@ -301,6 +301,35 @@ describe("Question relay", () => {
|
|
|
301
301
|
expect(appendRawLine).toHaveBeenCalledTimes(2);
|
|
302
302
|
});
|
|
303
303
|
});
|
|
304
|
+
|
|
305
|
+
describe("with createPr disabled", () => {
|
|
306
|
+
it("cancels publish commands", async () => {
|
|
307
|
+
server = new AgentServer({
|
|
308
|
+
port,
|
|
309
|
+
jwtPublicKey: "unused-in-unit-tests",
|
|
310
|
+
repositoryPath: repo.path,
|
|
311
|
+
apiUrl: "http://localhost:8000",
|
|
312
|
+
apiKey: "test-api-key",
|
|
313
|
+
projectId: 1,
|
|
314
|
+
mode: "interactive",
|
|
315
|
+
taskId: "test-task-id",
|
|
316
|
+
runId: "test-run-id",
|
|
317
|
+
createPr: false,
|
|
318
|
+
}) as unknown as TestableAgentServer;
|
|
319
|
+
|
|
320
|
+
const client = server.createCloudClient(TEST_PAYLOAD);
|
|
321
|
+
const result = await client.requestPermission({
|
|
322
|
+
options: ALLOW_OPTIONS,
|
|
323
|
+
toolCall: {
|
|
324
|
+
rawInput: { command: "git push origin my-branch" },
|
|
325
|
+
_meta: { toolName: "Bash" },
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(result.outcome.outcome).toBe("cancelled");
|
|
330
|
+
expect(result._meta?.message).toContain("stop before publishing");
|
|
331
|
+
});
|
|
332
|
+
});
|
|
304
333
|
});
|
|
305
334
|
|
|
306
335
|
describe("relayAgentResponse duplicate suppression", () => {
|