@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/agent",
3
- "version": "2.3.259",
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/shared": "1.0.0",
86
- "@posthog/git": "1.0.0"
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 PR-aware prompt when prUrl is provided", () => {
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("Do NOT create a new branch");
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("gh pr checkout");
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("posthog-code/");
400
- expect(prompt).toContain("Create a draft pull request");
401
- expect(prompt).toContain("gh pr create --draft");
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("Created with [PostHog Code]");
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 { ContentBlock } from "@agentclientprotocol/sdk";
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
- prContext:
515
- `IMPORTANT OVERRIDE PREVIOUS INSTRUCTIONS ABOUT CREATING BRANCHES/PRs.\n` +
516
- `You already have an open pull request: ${this.detectedPrUrl}\n` +
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
- Instead, add the following trailers to EVERY commit message (after a blank line at the end):
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 (params: {
1315
- options: Array<{ kind: string; optionId: string; name?: string }>;
1316
- toolCall?: {
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.relaySlackQuestion(payload, params.toolCall?._meta);
1339
- return {
1340
- outcome: { outcome: "cancelled" as const },
1341
- _meta: {
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 CODE_INTERACTION_ORIGIN=slack", () => {
175
+ describe("with POSTHOG_CODE_INTERACTION_ORIGIN=slack", () => {
176
176
  beforeEach(() => {
177
- process.env.CODE_INTERACTION_ORIGIN = "slack";
177
+ process.env.POSTHOG_CODE_INTERACTION_ORIGIN = "slack";
178
178
  });
179
179
 
180
180
  afterEach(() => {
181
- delete process.env.CODE_INTERACTION_ORIGIN;
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 CODE_INTERACTION_ORIGIN", () => {
223
+ describe("without POSTHOG_CODE_INTERACTION_ORIGIN", () => {
224
224
  beforeEach(() => {
225
- delete process.env.CODE_INTERACTION_ORIGIN;
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", () => {
@@ -18,6 +18,7 @@ export interface AgentServerConfig {
18
18
  mode: AgentMode;
19
19
  taskId: string;
20
20
  runId: string;
21
+ createPr?: boolean;
21
22
  version?: string;
22
23
  mcpServers?: RemoteMcpServer[];
23
24
  baseBranch?: string;