@posthog/agent 2.3.259 → 2.3.263

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.263",
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": {
@@ -63,6 +63,9 @@ export const POSTHOG_NOTIFICATIONS = {
63
63
 
64
64
  /** Token usage update for a session turn */
65
65
  USAGE_UPDATE: "_posthog/usage_update",
66
+
67
+ /** Response to a relayed permission request (plan approval, question) */
68
+ PERMISSION_RESPONSE: "_posthog/permission_response",
66
69
  } as const;
67
70
 
68
71
  type NotificationMethod =
@@ -490,11 +490,17 @@ export async function canUseTool(
490
490
  return planFileResult;
491
491
  }
492
492
 
493
- // if (session.permissionMode === "dontAsk") {
494
- // const message = "Tool not pre-approved. Denied by dontAsk mode.";
495
- // await emitToolDenial(context, message);
496
- // return { behavior: "deny", message, interrupt: false };
497
- // }
493
+ // In plan mode, deny tools that aren't in the allowed set. The agent must
494
+ // write its plan to ~/.claude/plans/ and call ExitPlanMode before it can
495
+ // use write or bash tools. Without this guard, cloud runs auto-approve
496
+ // restricted tools and the agent skips planning entirely.
497
+ if (session.permissionMode === "plan") {
498
+ const message =
499
+ "This tool is not available in plan mode. Write your plan " +
500
+ `to a file in ${getClaudePlansDir()} and call ExitPlanMode when ready.`;
501
+ await emitToolDenial(context, message);
502
+ return { behavior: "deny", message, interrupt: false };
503
+ }
498
504
 
499
505
  return handleDefaultPermissionFlow(context);
500
506
  }
@@ -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
  });