@posthog/agent 2.3.504 → 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.504",
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();
@@ -21753,6 +21897,15 @@ ${attributionInstructions}
21753
21897
  `;
21754
21898
  }
21755
21899
  if (!this.config.repositoryPath) {
21900
+ const publishInstructions = this.config.createPr === false ? `
21901
+ When the user asks for code changes:
21902
+ - You may clone a repository and make local edits in that clone
21903
+ - Do NOT create branches, commits, push changes, or open pull requests in this run` : `
21904
+ When the user explicitly asks to clone or work in a GitHub repository:
21905
+ - Clone the repository into /tmp/workspace/repos/<owner>/<repo> using \`gh repo clone <owner>/<repo> /tmp/workspace/repos/<owner>/<repo>\`
21906
+ - Work from inside that cloned repository for follow-up code changes
21907
+ - If the user explicitly asks you to open or update a pull request, create a branch, commit the requested changes, push it, and open a draft pull request from inside the clone
21908
+ - Do NOT create branches, commits, push changes, or open pull requests unless the user explicitly asks for that`;
21756
21909
  return `
21757
21910
  # Cloud Task Execution \u2014 No Repository Mode
21758
21911
 
@@ -21765,11 +21918,12 @@ When the user asks about analytics, data, metrics, events, funnels, dashboards,
21765
21918
 
21766
21919
  When the user asks for code changes or software engineering tasks:
21767
21920
  - Let them know you can help but don't have a repository connected for this session
21768
- - Offer to write code snippets, scripts, or provide guidance
21921
+ - If they have not specified a repository to clone, offer to write code snippets, scripts, or provide guidance
21922
+ ${publishInstructions}
21769
21923
 
21770
21924
  Important:
21771
- - Do NOT create branches, commits, or pull requests in this mode.
21772
21925
  - Prefer using MCP tools to answer questions with real data over giving generic advice.
21926
+ ${attributionInstructions}
21773
21927
  `;
21774
21928
  }
21775
21929
  if (!shouldAutoCreatePr) {