@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/agent.js
CHANGED
|
@@ -4030,7 +4030,7 @@ import { v7 as uuidv7 } from "uuid";
|
|
|
4030
4030
|
// package.json
|
|
4031
4031
|
var package_default = {
|
|
4032
4032
|
name: "@posthog/agent",
|
|
4033
|
-
version: "2.3.
|
|
4033
|
+
version: "2.3.508",
|
|
4034
4034
|
repository: "https://github.com/PostHog/code",
|
|
4035
4035
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
4036
4036
|
exports: {
|
|
@@ -8681,6 +8681,24 @@ function toolContent() {
|
|
|
8681
8681
|
return new ToolContentBuilder();
|
|
8682
8682
|
}
|
|
8683
8683
|
|
|
8684
|
+
// src/adapters/claude/permissions/posthog-exec-gate.ts
|
|
8685
|
+
var POSTHOG_EXEC_TOOL_RE = /^mcp__posthog(?:_[^_]+)*__exec$/;
|
|
8686
|
+
var POSTHOG_CALL_COMMAND_RE = /^\s*call\s+(?:--json\s+)?([a-zA-Z0-9_-]+)/;
|
|
8687
|
+
var POSTHOG_DESTRUCTIVE_SUBTOOL_RE = /(^|-)(partial-update|update|delete|destroy)(-|$)/i;
|
|
8688
|
+
function isPostHogExecTool(toolName) {
|
|
8689
|
+
return POSTHOG_EXEC_TOOL_RE.test(toolName);
|
|
8690
|
+
}
|
|
8691
|
+
function extractPostHogSubTool(toolInput) {
|
|
8692
|
+
if (!toolInput || typeof toolInput !== "object") return null;
|
|
8693
|
+
const command = toolInput.command;
|
|
8694
|
+
if (typeof command !== "string") return null;
|
|
8695
|
+
const match = command.match(POSTHOG_CALL_COMMAND_RE);
|
|
8696
|
+
return match ? match[1] ?? null : null;
|
|
8697
|
+
}
|
|
8698
|
+
function isPostHogDestructiveSubTool(subTool) {
|
|
8699
|
+
return POSTHOG_DESTRUCTIVE_SUBTOOL_RE.test(subTool);
|
|
8700
|
+
}
|
|
8701
|
+
|
|
8684
8702
|
// src/adapters/claude/hooks.ts
|
|
8685
8703
|
function extractTextFromToolResponse(response) {
|
|
8686
8704
|
if (typeof response === "string") return response;
|
|
@@ -8821,6 +8839,19 @@ var createPreToolUseHook = (settingsManager, logger) => async (input, _toolUseID
|
|
|
8821
8839
|
`[PreToolUseHook] Tool: ${toolName}, Decision: ${permissionCheck.decision}, Rule: ${permissionCheck.rule}`
|
|
8822
8840
|
);
|
|
8823
8841
|
}
|
|
8842
|
+
if (permissionCheck.decision === "allow" && isPostHogExecTool(toolName)) {
|
|
8843
|
+
const subTool = extractPostHogSubTool(toolInput);
|
|
8844
|
+
if (subTool && isPostHogDestructiveSubTool(subTool)) {
|
|
8845
|
+
return {
|
|
8846
|
+
continue: true,
|
|
8847
|
+
hookSpecificOutput: {
|
|
8848
|
+
hookEventName: "PreToolUse",
|
|
8849
|
+
permissionDecision: "ask",
|
|
8850
|
+
permissionDecisionReason: `Destructive PostHog sub-tool '${subTool}' requires explicit approval`
|
|
8851
|
+
}
|
|
8852
|
+
};
|
|
8853
|
+
}
|
|
8854
|
+
}
|
|
8824
8855
|
switch (permissionCheck.decision) {
|
|
8825
8856
|
case "allow":
|
|
8826
8857
|
return {
|
|
@@ -10415,6 +10446,12 @@ async function emitToolDenial(context, message) {
|
|
|
10415
10446
|
}
|
|
10416
10447
|
});
|
|
10417
10448
|
}
|
|
10449
|
+
async function buildDenialResult(context, response) {
|
|
10450
|
+
const feedback = response._meta?.customInput?.trim();
|
|
10451
|
+
const message = feedback ? `User refused permission to run tool with feedback: ${feedback}` : "User refused permission to run tool";
|
|
10452
|
+
await emitToolDenial(context, message);
|
|
10453
|
+
return { behavior: "deny", message, interrupt: !feedback };
|
|
10454
|
+
}
|
|
10418
10455
|
function getPlanFromFile(session, fileContentCache) {
|
|
10419
10456
|
return session.lastPlanContent || (session.lastPlanFilePath ? fileContentCache[session.lastPlanFilePath] : void 0);
|
|
10420
10457
|
}
|
|
@@ -10639,12 +10676,8 @@ async function handleDefaultPermissionFlow(context) {
|
|
|
10639
10676
|
behavior: "allow",
|
|
10640
10677
|
updatedInput: toolInput
|
|
10641
10678
|
};
|
|
10642
|
-
} else {
|
|
10643
|
-
const feedback = response._meta?.customInput?.trim();
|
|
10644
|
-
const message = feedback ? `User refused permission to run tool with feedback: ${feedback}` : "User refused permission to run tool";
|
|
10645
|
-
await emitToolDenial(context, message);
|
|
10646
|
-
return { behavior: "deny", message, interrupt: !feedback };
|
|
10647
10679
|
}
|
|
10680
|
+
return buildDenialResult(context, response);
|
|
10648
10681
|
}
|
|
10649
10682
|
function parseMcpToolName(toolName) {
|
|
10650
10683
|
const parts2 = toolName.split("__");
|
|
@@ -10707,10 +10740,61 @@ ${metadata2.description}` : "";
|
|
|
10707
10740
|
updatedInput: toolInput
|
|
10708
10741
|
};
|
|
10709
10742
|
}
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
|
|
10743
|
+
return buildDenialResult(context, response);
|
|
10744
|
+
}
|
|
10745
|
+
async function handlePostHogExecApprovalFlow(context, subTool) {
|
|
10746
|
+
const { toolName, toolInput, toolUseID, client, sessionId, session } = context;
|
|
10747
|
+
const response = await client.requestPermission({
|
|
10748
|
+
options: [
|
|
10749
|
+
{ kind: "allow_once", name: "Yes", optionId: "allow" },
|
|
10750
|
+
{
|
|
10751
|
+
kind: "allow_always",
|
|
10752
|
+
name: "Yes, always allow",
|
|
10753
|
+
optionId: "allow_always"
|
|
10754
|
+
},
|
|
10755
|
+
{
|
|
10756
|
+
kind: "reject_once",
|
|
10757
|
+
name: "Type here to tell the agent what to do differently",
|
|
10758
|
+
optionId: "reject",
|
|
10759
|
+
_meta: { customInput: true }
|
|
10760
|
+
}
|
|
10761
|
+
],
|
|
10762
|
+
sessionId,
|
|
10763
|
+
toolCall: {
|
|
10764
|
+
toolCallId: toolUseID,
|
|
10765
|
+
title: `The agent wants to run \`${subTool}\` on PostHog`,
|
|
10766
|
+
kind: "other",
|
|
10767
|
+
content: [
|
|
10768
|
+
{
|
|
10769
|
+
type: "content",
|
|
10770
|
+
content: text(
|
|
10771
|
+
"This will modify live PostHog data. Approve to run this sub-tool."
|
|
10772
|
+
)
|
|
10773
|
+
}
|
|
10774
|
+
],
|
|
10775
|
+
rawInput: { ...toolInput, toolName }
|
|
10776
|
+
}
|
|
10777
|
+
});
|
|
10778
|
+
if (context.signal?.aborted || response.outcome?.outcome === "cancelled") {
|
|
10779
|
+
throw new Error("Tool use aborted");
|
|
10780
|
+
}
|
|
10781
|
+
if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
|
|
10782
|
+
if (response.outcome.optionId === "allow_always") {
|
|
10783
|
+
try {
|
|
10784
|
+
await session.settingsManager.addPostHogExecApproval(subTool);
|
|
10785
|
+
} catch (error) {
|
|
10786
|
+
context.logger.warn(
|
|
10787
|
+
"[canUseTool] Failed to persist PostHog exec approval",
|
|
10788
|
+
{ error: error instanceof Error ? error.message : String(error) }
|
|
10789
|
+
);
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
return {
|
|
10793
|
+
behavior: "allow",
|
|
10794
|
+
updatedInput: toolInput
|
|
10795
|
+
};
|
|
10796
|
+
}
|
|
10797
|
+
return buildDenialResult(context, response);
|
|
10714
10798
|
}
|
|
10715
10799
|
function handlePlanFileException(context) {
|
|
10716
10800
|
const { session, toolName, toolInput } = context;
|
|
@@ -10792,6 +10876,24 @@ async function canUseTool(context) {
|
|
|
10792
10876
|
if (approvalState === "needs_approval") {
|
|
10793
10877
|
return handleMcpApprovalFlow(context);
|
|
10794
10878
|
}
|
|
10879
|
+
if (isPostHogExecTool(toolName)) {
|
|
10880
|
+
const subTool = extractPostHogSubTool(toolInput);
|
|
10881
|
+
if (subTool && isPostHogDestructiveSubTool(subTool)) {
|
|
10882
|
+
if (session.permissionMode === "auto" || session.permissionMode === "bypassPermissions") {
|
|
10883
|
+
return {
|
|
10884
|
+
behavior: "allow",
|
|
10885
|
+
updatedInput: toolInput
|
|
10886
|
+
};
|
|
10887
|
+
}
|
|
10888
|
+
if (session.settingsManager.hasPostHogExecApproval(subTool)) {
|
|
10889
|
+
return {
|
|
10890
|
+
behavior: "allow",
|
|
10891
|
+
updatedInput: toolInput
|
|
10892
|
+
};
|
|
10893
|
+
}
|
|
10894
|
+
return handlePostHogExecApprovalFlow(context, subTool);
|
|
10895
|
+
}
|
|
10896
|
+
}
|
|
10795
10897
|
}
|
|
10796
10898
|
if (isToolAllowedForMode(toolName, session.permissionMode)) {
|
|
10797
10899
|
return {
|
|
@@ -16318,6 +16420,7 @@ var SettingsManager = class {
|
|
|
16318
16420
|
ask: []
|
|
16319
16421
|
};
|
|
16320
16422
|
const merged = { permissions };
|
|
16423
|
+
const posthogApprovedExecTools = /* @__PURE__ */ new Set();
|
|
16321
16424
|
for (const settings of allSettings) {
|
|
16322
16425
|
if (settings.permissions) {
|
|
16323
16426
|
if (settings.permissions.allow) {
|
|
@@ -16345,6 +16448,14 @@ var SettingsManager = class {
|
|
|
16345
16448
|
if (settings.model) {
|
|
16346
16449
|
merged.model = settings.model;
|
|
16347
16450
|
}
|
|
16451
|
+
if (settings.posthogApprovedExecTools) {
|
|
16452
|
+
for (const tool of settings.posthogApprovedExecTools) {
|
|
16453
|
+
posthogApprovedExecTools.add(tool);
|
|
16454
|
+
}
|
|
16455
|
+
}
|
|
16456
|
+
}
|
|
16457
|
+
if (posthogApprovedExecTools.size > 0) {
|
|
16458
|
+
merged.posthogApprovedExecTools = Array.from(posthogApprovedExecTools);
|
|
16348
16459
|
}
|
|
16349
16460
|
this.mergedSettings = merged;
|
|
16350
16461
|
}
|
|
@@ -16409,6 +16520,39 @@ var SettingsManager = class {
|
|
|
16409
16520
|
const next = { ...existing, permissions };
|
|
16410
16521
|
await fs7.promises.mkdir(path11.dirname(filePath), { recursive: true });
|
|
16411
16522
|
await writeFileAtomic(filePath, `${JSON.stringify(next, null, 2)}
|
|
16523
|
+
`);
|
|
16524
|
+
this.localSettings = next;
|
|
16525
|
+
this.mergeAllSettings();
|
|
16526
|
+
} finally {
|
|
16527
|
+
this.writeMutex.release();
|
|
16528
|
+
}
|
|
16529
|
+
}
|
|
16530
|
+
hasPostHogExecApproval(subTool) {
|
|
16531
|
+
return this.mergedSettings.posthogApprovedExecTools?.includes(subTool) ?? false;
|
|
16532
|
+
}
|
|
16533
|
+
/**
|
|
16534
|
+
* Persists an approved PostHog MCP `exec` sub-tool (e.g. `experiment-update`)
|
|
16535
|
+
* to the local settings file so future calls skip the prompt. Mirrors
|
|
16536
|
+
* `addAllowRules` — serialised via `writeMutex`, atomic temp-file + rename.
|
|
16537
|
+
*/
|
|
16538
|
+
async addPostHogExecApproval(subTool) {
|
|
16539
|
+
if (!subTool) return;
|
|
16540
|
+
if (!this.initialized) await this.initialize();
|
|
16541
|
+
await this.writeMutex.acquire();
|
|
16542
|
+
try {
|
|
16543
|
+
const filePath = this.getLocalSettingsPath();
|
|
16544
|
+
const existing = await readSettingsFileForUpdate(filePath);
|
|
16545
|
+
const current2 = new Set(existing.posthogApprovedExecTools ?? []);
|
|
16546
|
+
if (current2.has(subTool)) {
|
|
16547
|
+
return;
|
|
16548
|
+
}
|
|
16549
|
+
current2.add(subTool);
|
|
16550
|
+
const next = {
|
|
16551
|
+
...existing,
|
|
16552
|
+
posthogApprovedExecTools: Array.from(current2)
|
|
16553
|
+
};
|
|
16554
|
+
await fs7.promises.mkdir(path11.dirname(filePath), { recursive: true });
|
|
16555
|
+
await writeFileAtomic(filePath, `${JSON.stringify(next, null, 2)}
|
|
16412
16556
|
`);
|
|
16413
16557
|
this.localSettings = next;
|
|
16414
16558
|
this.mergeAllSettings();
|