@posthog/agent 2.3.507 → 2.3.508

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.
@@ -8755,7 +8755,7 @@ var import_zod3 = require("zod");
8755
8755
  // package.json
8756
8756
  var package_default = {
8757
8757
  name: "@posthog/agent",
8758
- version: "2.3.507",
8758
+ version: "2.3.508",
8759
8759
  repository: "https://github.com/PostHog/code",
8760
8760
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
8761
8761
  exports: {
@@ -13612,6 +13612,24 @@ function toolContent() {
13612
13612
  return new ToolContentBuilder();
13613
13613
  }
13614
13614
 
13615
+ // src/adapters/claude/permissions/posthog-exec-gate.ts
13616
+ var POSTHOG_EXEC_TOOL_RE = /^mcp__posthog(?:_[^_]+)*__exec$/;
13617
+ var POSTHOG_CALL_COMMAND_RE = /^\s*call\s+(?:--json\s+)?([a-zA-Z0-9_-]+)/;
13618
+ var POSTHOG_DESTRUCTIVE_SUBTOOL_RE = /(^|-)(partial-update|update|delete|destroy)(-|$)/i;
13619
+ function isPostHogExecTool(toolName) {
13620
+ return POSTHOG_EXEC_TOOL_RE.test(toolName);
13621
+ }
13622
+ function extractPostHogSubTool(toolInput) {
13623
+ if (!toolInput || typeof toolInput !== "object") return null;
13624
+ const command = toolInput.command;
13625
+ if (typeof command !== "string") return null;
13626
+ const match = command.match(POSTHOG_CALL_COMMAND_RE);
13627
+ return match ? match[1] ?? null : null;
13628
+ }
13629
+ function isPostHogDestructiveSubTool(subTool) {
13630
+ return POSTHOG_DESTRUCTIVE_SUBTOOL_RE.test(subTool);
13631
+ }
13632
+
13615
13633
  // src/adapters/claude/hooks.ts
13616
13634
  function extractTextFromToolResponse(response) {
13617
13635
  if (typeof response === "string") return response;
@@ -13752,6 +13770,19 @@ var createPreToolUseHook = (settingsManager, logger) => async (input, _toolUseID
13752
13770
  `[PreToolUseHook] Tool: ${toolName}, Decision: ${permissionCheck.decision}, Rule: ${permissionCheck.rule}`
13753
13771
  );
13754
13772
  }
13773
+ if (permissionCheck.decision === "allow" && isPostHogExecTool(toolName)) {
13774
+ const subTool = extractPostHogSubTool(toolInput);
13775
+ if (subTool && isPostHogDestructiveSubTool(subTool)) {
13776
+ return {
13777
+ continue: true,
13778
+ hookSpecificOutput: {
13779
+ hookEventName: "PreToolUse",
13780
+ permissionDecision: "ask",
13781
+ permissionDecisionReason: `Destructive PostHog sub-tool '${subTool}' requires explicit approval`
13782
+ }
13783
+ };
13784
+ }
13785
+ }
13755
13786
  switch (permissionCheck.decision) {
13756
13787
  case "allow":
13757
13788
  return {
@@ -15346,6 +15377,12 @@ async function emitToolDenial(context, message) {
15346
15377
  }
15347
15378
  });
15348
15379
  }
15380
+ async function buildDenialResult(context, response) {
15381
+ const feedback = response._meta?.customInput?.trim();
15382
+ const message = feedback ? `User refused permission to run tool with feedback: ${feedback}` : "User refused permission to run tool";
15383
+ await emitToolDenial(context, message);
15384
+ return { behavior: "deny", message, interrupt: !feedback };
15385
+ }
15349
15386
  function getPlanFromFile(session, fileContentCache) {
15350
15387
  return session.lastPlanContent || (session.lastPlanFilePath ? fileContentCache[session.lastPlanFilePath] : void 0);
15351
15388
  }
@@ -15570,12 +15607,8 @@ async function handleDefaultPermissionFlow(context) {
15570
15607
  behavior: "allow",
15571
15608
  updatedInput: toolInput
15572
15609
  };
15573
- } else {
15574
- const feedback = response._meta?.customInput?.trim();
15575
- const message = feedback ? `User refused permission to run tool with feedback: ${feedback}` : "User refused permission to run tool";
15576
- await emitToolDenial(context, message);
15577
- return { behavior: "deny", message, interrupt: !feedback };
15578
15610
  }
15611
+ return buildDenialResult(context, response);
15579
15612
  }
15580
15613
  function parseMcpToolName(toolName) {
15581
15614
  const parts2 = toolName.split("__");
@@ -15638,10 +15671,61 @@ ${metadata2.description}` : "";
15638
15671
  updatedInput: toolInput
15639
15672
  };
15640
15673
  }
15641
- const feedback = response._meta?.customInput?.trim();
15642
- const message = feedback ? `User refused permission to run tool with feedback: ${feedback}` : "User refused permission to run tool";
15643
- await emitToolDenial(context, message);
15644
- return { behavior: "deny", message, interrupt: !feedback };
15674
+ return buildDenialResult(context, response);
15675
+ }
15676
+ async function handlePostHogExecApprovalFlow(context, subTool) {
15677
+ const { toolName, toolInput, toolUseID, client, sessionId, session } = context;
15678
+ const response = await client.requestPermission({
15679
+ options: [
15680
+ { kind: "allow_once", name: "Yes", optionId: "allow" },
15681
+ {
15682
+ kind: "allow_always",
15683
+ name: "Yes, always allow",
15684
+ optionId: "allow_always"
15685
+ },
15686
+ {
15687
+ kind: "reject_once",
15688
+ name: "Type here to tell the agent what to do differently",
15689
+ optionId: "reject",
15690
+ _meta: { customInput: true }
15691
+ }
15692
+ ],
15693
+ sessionId,
15694
+ toolCall: {
15695
+ toolCallId: toolUseID,
15696
+ title: `The agent wants to run \`${subTool}\` on PostHog`,
15697
+ kind: "other",
15698
+ content: [
15699
+ {
15700
+ type: "content",
15701
+ content: text(
15702
+ "This will modify live PostHog data. Approve to run this sub-tool."
15703
+ )
15704
+ }
15705
+ ],
15706
+ rawInput: { ...toolInput, toolName }
15707
+ }
15708
+ });
15709
+ if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
15710
+ throw new Error("Tool use aborted");
15711
+ }
15712
+ if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
15713
+ if (response.outcome.optionId === "allow_always") {
15714
+ try {
15715
+ await session.settingsManager.addPostHogExecApproval(subTool);
15716
+ } catch (error) {
15717
+ context.logger.warn(
15718
+ "[canUseTool] Failed to persist PostHog exec approval",
15719
+ { error: error instanceof Error ? error.message : String(error) }
15720
+ );
15721
+ }
15722
+ }
15723
+ return {
15724
+ behavior: "allow",
15725
+ updatedInput: toolInput
15726
+ };
15727
+ }
15728
+ return buildDenialResult(context, response);
15645
15729
  }
15646
15730
  function handlePlanFileException(context) {
15647
15731
  const { session, toolName, toolInput } = context;
@@ -15723,6 +15807,24 @@ async function canUseTool(context) {
15723
15807
  if (approvalState === "needs_approval") {
15724
15808
  return handleMcpApprovalFlow(context);
15725
15809
  }
15810
+ if (isPostHogExecTool(toolName)) {
15811
+ const subTool = extractPostHogSubTool(toolInput);
15812
+ if (subTool && isPostHogDestructiveSubTool(subTool)) {
15813
+ if (session.permissionMode === "auto" || session.permissionMode === "bypassPermissions") {
15814
+ return {
15815
+ behavior: "allow",
15816
+ updatedInput: toolInput
15817
+ };
15818
+ }
15819
+ if (session.settingsManager.hasPostHogExecApproval(subTool)) {
15820
+ return {
15821
+ behavior: "allow",
15822
+ updatedInput: toolInput
15823
+ };
15824
+ }
15825
+ return handlePostHogExecApprovalFlow(context, subTool);
15826
+ }
15827
+ }
15726
15828
  }
15727
15829
  if (isToolAllowedForMode(toolName, session.permissionMode)) {
15728
15830
  return {
@@ -16351,6 +16453,7 @@ var SettingsManager = class {
16351
16453
  ask: []
16352
16454
  };
16353
16455
  const merged = { permissions };
16456
+ const posthogApprovedExecTools = /* @__PURE__ */ new Set();
16354
16457
  for (const settings of allSettings) {
16355
16458
  if (settings.permissions) {
16356
16459
  if (settings.permissions.allow) {
@@ -16378,6 +16481,14 @@ var SettingsManager = class {
16378
16481
  if (settings.model) {
16379
16482
  merged.model = settings.model;
16380
16483
  }
16484
+ if (settings.posthogApprovedExecTools) {
16485
+ for (const tool of settings.posthogApprovedExecTools) {
16486
+ posthogApprovedExecTools.add(tool);
16487
+ }
16488
+ }
16489
+ }
16490
+ if (posthogApprovedExecTools.size > 0) {
16491
+ merged.posthogApprovedExecTools = Array.from(posthogApprovedExecTools);
16381
16492
  }
16382
16493
  this.mergedSettings = merged;
16383
16494
  }
@@ -16442,6 +16553,39 @@ var SettingsManager = class {
16442
16553
  const next = { ...existing, permissions };
16443
16554
  await fs7.promises.mkdir(path9.dirname(filePath), { recursive: true });
16444
16555
  await writeFileAtomic(filePath, `${JSON.stringify(next, null, 2)}
16556
+ `);
16557
+ this.localSettings = next;
16558
+ this.mergeAllSettings();
16559
+ } finally {
16560
+ this.writeMutex.release();
16561
+ }
16562
+ }
16563
+ hasPostHogExecApproval(subTool) {
16564
+ return this.mergedSettings.posthogApprovedExecTools?.includes(subTool) ?? false;
16565
+ }
16566
+ /**
16567
+ * Persists an approved PostHog MCP `exec` sub-tool (e.g. `experiment-update`)
16568
+ * to the local settings file so future calls skip the prompt. Mirrors
16569
+ * `addAllowRules` — serialised via `writeMutex`, atomic temp-file + rename.
16570
+ */
16571
+ async addPostHogExecApproval(subTool) {
16572
+ if (!subTool) return;
16573
+ if (!this.initialized) await this.initialize();
16574
+ await this.writeMutex.acquire();
16575
+ try {
16576
+ const filePath = this.getLocalSettingsPath();
16577
+ const existing = await readSettingsFileForUpdate(filePath);
16578
+ const current2 = new Set(existing.posthogApprovedExecTools ?? []);
16579
+ if (current2.has(subTool)) {
16580
+ return;
16581
+ }
16582
+ current2.add(subTool);
16583
+ const next = {
16584
+ ...existing,
16585
+ posthogApprovedExecTools: Array.from(current2)
16586
+ };
16587
+ await fs7.promises.mkdir(path9.dirname(filePath), { recursive: true });
16588
+ await writeFileAtomic(filePath, `${JSON.stringify(next, null, 2)}
16445
16589
  `);
16446
16590
  this.localSettings = next;
16447
16591
  this.mergeAllSettings();