@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.
- package/dist/agent.js +154 -10
- 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.js +166 -12
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +166 -12
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +3 -3
- package/src/adapters/claude/hooks.test.ts +125 -1
- package/src/adapters/claude/hooks.ts +24 -0
- package/src/adapters/claude/permissions/permission-handlers.test.ts +152 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +109 -15
- package/src/adapters/claude/permissions/posthog-exec-gate.test.ts +84 -0
- package/src/adapters/claude/permissions/posthog-exec-gate.ts +30 -0
- package/src/adapters/claude/session/settings.test.ts +50 -0
- package/src/adapters/claude/session/settings.ts +48 -0
- package/src/server/agent-server.test.ts +43 -0
- package/src/server/agent-server.ts +16 -2
package/dist/server/bin.cjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
15642
|
-
|
|
15643
|
-
|
|
15644
|
-
|
|
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
|
-
-
|
|
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) {
|