@posthog/agent 2.3.351 → 2.3.353
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/adapters/claude/permissions/permission-options.d.ts +1 -1
- package/dist/adapters/claude/permissions/permission-options.js +3 -3
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/agent.js +5915 -129
- 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 +176 -49
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +174 -47
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude/permissions/permission-handlers.ts +53 -10
- package/src/adapters/claude/permissions/permission-options.ts +3 -3
- package/src/adapters/claude/session/repo-path.ts +22 -0
- package/src/adapters/claude/session/settings.test.ts +159 -0
- package/src/adapters/claude/session/settings.ts +92 -6
package/dist/server/bin.cjs
CHANGED
|
@@ -8688,6 +8688,32 @@ async function getCurrentBranch(baseDir, options) {
|
|
|
8688
8688
|
return branch === "HEAD" ? null : branch;
|
|
8689
8689
|
}, { signal: options?.abortSignal });
|
|
8690
8690
|
}
|
|
8691
|
+
async function listWorktrees(baseDir, options) {
|
|
8692
|
+
const manager = getGitOperationManager();
|
|
8693
|
+
return manager.executeRead(baseDir, async (git) => {
|
|
8694
|
+
const output = await git.raw(["worktree", "list", "--porcelain"]);
|
|
8695
|
+
const worktrees = [];
|
|
8696
|
+
let current2 = {};
|
|
8697
|
+
for (const line of output.split("\n")) {
|
|
8698
|
+
if (line.startsWith("worktree ")) {
|
|
8699
|
+
if (current2.path) {
|
|
8700
|
+
worktrees.push(current2);
|
|
8701
|
+
}
|
|
8702
|
+
current2 = { path: line.slice(9), branch: null };
|
|
8703
|
+
} else if (line.startsWith("HEAD ")) {
|
|
8704
|
+
current2.head = line.slice(5);
|
|
8705
|
+
} else if (line.startsWith("branch ")) {
|
|
8706
|
+
current2.branch = line.slice(7).replace("refs/heads/", "");
|
|
8707
|
+
} else if (line === "detached") {
|
|
8708
|
+
current2.branch = null;
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
if (current2.path) {
|
|
8712
|
+
worktrees.push(current2);
|
|
8713
|
+
}
|
|
8714
|
+
return worktrees;
|
|
8715
|
+
}, { signal: options?.abortSignal });
|
|
8716
|
+
}
|
|
8691
8717
|
async function getHeadSha(baseDir, options) {
|
|
8692
8718
|
const manager = getGitOperationManager();
|
|
8693
8719
|
return manager.executeRead(baseDir, (git) => git.revparse(["HEAD"]), {
|
|
@@ -8701,7 +8727,7 @@ var import_hono = require("hono");
|
|
|
8701
8727
|
// package.json
|
|
8702
8728
|
var package_default = {
|
|
8703
8729
|
name: "@posthog/agent",
|
|
8704
|
-
version: "2.3.
|
|
8730
|
+
version: "2.3.353",
|
|
8705
8731
|
repository: "https://github.com/PostHog/code",
|
|
8706
8732
|
description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
8707
8733
|
exports: {
|
|
@@ -14231,16 +14257,16 @@ function permissionOptions(allowAlwaysLabel) {
|
|
|
14231
14257
|
}
|
|
14232
14258
|
];
|
|
14233
14259
|
}
|
|
14234
|
-
function buildPermissionOptions(toolName, toolInput,
|
|
14260
|
+
function buildPermissionOptions(toolName, toolInput, repoRoot, suggestions) {
|
|
14235
14261
|
if (BASH_TOOLS.has(toolName)) {
|
|
14236
14262
|
const rawRuleContent = suggestions?.flatMap((s) => "rules" in s ? s.rules : []).find((r) => r.toolName === "Bash" && r.ruleContent)?.ruleContent;
|
|
14237
14263
|
const ruleContent = rawRuleContent?.replace(/:?\*$/, "");
|
|
14238
14264
|
const command = toolInput?.command;
|
|
14239
14265
|
const cmdName = command?.split(/\s+/)[0] ?? "this command";
|
|
14240
|
-
const
|
|
14266
|
+
const scopeLabel = repoRoot ? ` in ${repoRoot}` : "";
|
|
14241
14267
|
const label = ruleContent ?? `\`${cmdName}\` commands`;
|
|
14242
14268
|
return permissionOptions(
|
|
14243
|
-
`Yes, and don't ask again for ${label}${
|
|
14269
|
+
`Yes, and don't ask again for ${label}${scopeLabel}`
|
|
14244
14270
|
);
|
|
14245
14271
|
}
|
|
14246
14272
|
if (toolName === "BashOutput") {
|
|
@@ -14526,7 +14552,7 @@ async function handleDefaultPermissionFlow(context) {
|
|
|
14526
14552
|
const options = buildPermissionOptions(
|
|
14527
14553
|
toolName,
|
|
14528
14554
|
toolInput,
|
|
14529
|
-
session
|
|
14555
|
+
session.settingsManager.getRepoRoot(),
|
|
14530
14556
|
suggestions
|
|
14531
14557
|
);
|
|
14532
14558
|
const response = await client.requestPermission({
|
|
@@ -14546,17 +14572,19 @@ async function handleDefaultPermissionFlow(context) {
|
|
|
14546
14572
|
}
|
|
14547
14573
|
if (response.outcome?.outcome === "selected" && (response.outcome.optionId === "allow" || response.outcome.optionId === "allow_always")) {
|
|
14548
14574
|
if (response.outcome.optionId === "allow_always") {
|
|
14575
|
+
const rules = extractAllowRules(suggestions, toolName);
|
|
14576
|
+
try {
|
|
14577
|
+
await session.settingsManager.addAllowRules(rules);
|
|
14578
|
+
} catch (error) {
|
|
14579
|
+
context.logger.warn(
|
|
14580
|
+
"[canUseTool] Failed to persist allow rules to repository settings",
|
|
14581
|
+
{ error: error instanceof Error ? error.message : String(error) }
|
|
14582
|
+
);
|
|
14583
|
+
}
|
|
14549
14584
|
return {
|
|
14550
14585
|
behavior: "allow",
|
|
14551
14586
|
updatedInput: toolInput,
|
|
14552
|
-
updatedPermissions: suggestions
|
|
14553
|
-
{
|
|
14554
|
-
type: "addRules",
|
|
14555
|
-
rules: [{ toolName }],
|
|
14556
|
-
behavior: "allow",
|
|
14557
|
-
destination: "localSettings"
|
|
14558
|
-
}
|
|
14559
|
-
]
|
|
14587
|
+
updatedPermissions: buildSessionPermissions(suggestions, rules)
|
|
14560
14588
|
};
|
|
14561
14589
|
}
|
|
14562
14590
|
return {
|
|
@@ -14589,6 +14617,26 @@ function handlePlanFileException(context) {
|
|
|
14589
14617
|
updatedInput: toolInput
|
|
14590
14618
|
};
|
|
14591
14619
|
}
|
|
14620
|
+
function extractAllowRules(suggestions, toolName) {
|
|
14621
|
+
if (!suggestions || suggestions.length === 0) {
|
|
14622
|
+
return [{ toolName }];
|
|
14623
|
+
}
|
|
14624
|
+
return suggestions.filter(
|
|
14625
|
+
(update) => update.type === "addRules" && update.behavior === "allow"
|
|
14626
|
+
).flatMap((update) => "rules" in update ? update.rules : []);
|
|
14627
|
+
}
|
|
14628
|
+
function buildSessionPermissions(suggestions, rules) {
|
|
14629
|
+
const passthrough = (suggestions ?? []).filter(
|
|
14630
|
+
(update) => !(update.type === "addRules" && update.behavior === "allow")
|
|
14631
|
+
).map((update) => ({ ...update, destination: "session" }));
|
|
14632
|
+
if (rules.length === 0) {
|
|
14633
|
+
return passthrough;
|
|
14634
|
+
}
|
|
14635
|
+
return [
|
|
14636
|
+
{ type: "addRules", rules, behavior: "allow", destination: "session" },
|
|
14637
|
+
...passthrough
|
|
14638
|
+
];
|
|
14639
|
+
}
|
|
14592
14640
|
function extractDomainFromUrl(url) {
|
|
14593
14641
|
try {
|
|
14594
14642
|
return new URL(url).hostname;
|
|
@@ -14991,6 +15039,47 @@ var fs7 = __toESM(require("fs"), 1);
|
|
|
14991
15039
|
var os3 = __toESM(require("os"), 1);
|
|
14992
15040
|
var path9 = __toESM(require("path"), 1);
|
|
14993
15041
|
var import_minimatch = require("minimatch");
|
|
15042
|
+
|
|
15043
|
+
// src/utils/async-mutex.ts
|
|
15044
|
+
var AsyncMutex = class {
|
|
15045
|
+
locked = false;
|
|
15046
|
+
queue = [];
|
|
15047
|
+
async acquire() {
|
|
15048
|
+
if (!this.locked) {
|
|
15049
|
+
this.locked = true;
|
|
15050
|
+
return;
|
|
15051
|
+
}
|
|
15052
|
+
return new Promise((resolve4) => {
|
|
15053
|
+
this.queue.push(resolve4);
|
|
15054
|
+
});
|
|
15055
|
+
}
|
|
15056
|
+
release() {
|
|
15057
|
+
const next = this.queue.shift();
|
|
15058
|
+
if (next) {
|
|
15059
|
+
next();
|
|
15060
|
+
} else {
|
|
15061
|
+
this.locked = false;
|
|
15062
|
+
}
|
|
15063
|
+
}
|
|
15064
|
+
isLocked() {
|
|
15065
|
+
return this.locked;
|
|
15066
|
+
}
|
|
15067
|
+
get queueLength() {
|
|
15068
|
+
return this.queue.length;
|
|
15069
|
+
}
|
|
15070
|
+
};
|
|
15071
|
+
|
|
15072
|
+
// src/adapters/claude/session/repo-path.ts
|
|
15073
|
+
async function resolveMainRepoPath(cwd) {
|
|
15074
|
+
try {
|
|
15075
|
+
const worktrees = await listWorktrees(cwd);
|
|
15076
|
+
return worktrees[0]?.path ?? cwd;
|
|
15077
|
+
} catch {
|
|
15078
|
+
return cwd;
|
|
15079
|
+
}
|
|
15080
|
+
}
|
|
15081
|
+
|
|
15082
|
+
// src/adapters/claude/session/settings.ts
|
|
14994
15083
|
var ACP_TOOL_NAME_PREFIX = "mcp__acp__";
|
|
14995
15084
|
var acpToolNames = {
|
|
14996
15085
|
read: `${ACP_TOOL_NAME_PREFIX}Read`,
|
|
@@ -15047,7 +15136,7 @@ function matchesGlob(pattern, filePath, cwd) {
|
|
|
15047
15136
|
});
|
|
15048
15137
|
}
|
|
15049
15138
|
function matchesRule(rule, toolName, toolInput, cwd) {
|
|
15050
|
-
const ruleAppliesToTool = rule.toolName === "Bash" && toolName === acpToolNames.bash || rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName) || rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName);
|
|
15139
|
+
const ruleAppliesToTool = rule.toolName === "Bash" && toolName === acpToolNames.bash || rule.toolName === "Edit" && FILE_EDITING_TOOLS.includes(toolName) || rule.toolName === "Read" && FILE_READING_TOOLS.includes(toolName) || rule.toolName === toolName && !rule.argument;
|
|
15051
15140
|
if (!ruleAppliesToTool) {
|
|
15052
15141
|
return false;
|
|
15053
15142
|
}
|
|
@@ -15077,6 +15166,19 @@ function matchesRule(rule, toolName, toolInput, cwd) {
|
|
|
15077
15166
|
}
|
|
15078
15167
|
return matchesGlob(rule.argument, actualArg, cwd);
|
|
15079
15168
|
}
|
|
15169
|
+
function formatRule(rule) {
|
|
15170
|
+
return rule.ruleContent ? `${rule.toolName}(${rule.ruleContent})` : rule.toolName;
|
|
15171
|
+
}
|
|
15172
|
+
async function writeFileAtomic(filePath, data) {
|
|
15173
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
15174
|
+
await fs7.promises.writeFile(tmpPath, data);
|
|
15175
|
+
try {
|
|
15176
|
+
await fs7.promises.rename(tmpPath, filePath);
|
|
15177
|
+
} catch (error) {
|
|
15178
|
+
await fs7.promises.rm(tmpPath, { force: true });
|
|
15179
|
+
throw error;
|
|
15180
|
+
}
|
|
15181
|
+
}
|
|
15080
15182
|
async function loadSettingsFile(filePath) {
|
|
15081
15183
|
if (!filePath) {
|
|
15082
15184
|
return {};
|
|
@@ -15095,6 +15197,17 @@ async function loadSettingsFile(filePath) {
|
|
|
15095
15197
|
return {};
|
|
15096
15198
|
}
|
|
15097
15199
|
}
|
|
15200
|
+
async function readSettingsFileForUpdate(filePath) {
|
|
15201
|
+
try {
|
|
15202
|
+
const content = await fs7.promises.readFile(filePath, "utf-8");
|
|
15203
|
+
return JSON.parse(content);
|
|
15204
|
+
} catch (error) {
|
|
15205
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
15206
|
+
return {};
|
|
15207
|
+
}
|
|
15208
|
+
throw error;
|
|
15209
|
+
}
|
|
15210
|
+
}
|
|
15098
15211
|
function getManagedSettingsPath() {
|
|
15099
15212
|
switch (process.platform) {
|
|
15100
15213
|
case "darwin":
|
|
@@ -15109,6 +15222,7 @@ function getManagedSettingsPath() {
|
|
|
15109
15222
|
}
|
|
15110
15223
|
var SettingsManager = class {
|
|
15111
15224
|
cwd;
|
|
15225
|
+
repoRoot;
|
|
15112
15226
|
userSettings = {};
|
|
15113
15227
|
projectSettings = {};
|
|
15114
15228
|
localSettings = {};
|
|
@@ -15116,8 +15230,10 @@ var SettingsManager = class {
|
|
|
15116
15230
|
mergedSettings = {};
|
|
15117
15231
|
initialized = false;
|
|
15118
15232
|
initPromise = null;
|
|
15233
|
+
writeMutex = new AsyncMutex();
|
|
15119
15234
|
constructor(cwd) {
|
|
15120
15235
|
this.cwd = cwd;
|
|
15236
|
+
this.repoRoot = cwd;
|
|
15121
15237
|
}
|
|
15122
15238
|
async initialize() {
|
|
15123
15239
|
if (this.initialized) return;
|
|
@@ -15135,10 +15251,16 @@ var SettingsManager = class {
|
|
|
15135
15251
|
getProjectSettingsPath() {
|
|
15136
15252
|
return path9.join(this.cwd, ".claude", "settings.json");
|
|
15137
15253
|
}
|
|
15254
|
+
/**
|
|
15255
|
+
* Local settings are anchored to the primary worktree so every worktree of
|
|
15256
|
+
* the same repository shares a single `.claude/settings.local.json`. This
|
|
15257
|
+
* avoids re-prompting for the same permission in every worktree.
|
|
15258
|
+
*/
|
|
15138
15259
|
getLocalSettingsPath() {
|
|
15139
|
-
return path9.join(this.
|
|
15260
|
+
return path9.join(this.repoRoot, ".claude", "settings.local.json");
|
|
15140
15261
|
}
|
|
15141
15262
|
async loadAllSettings() {
|
|
15263
|
+
this.repoRoot = await resolveMainRepoPath(this.cwd);
|
|
15142
15264
|
const [userSettings, projectSettings, localSettings, enterpriseSettings] = await Promise.all([
|
|
15143
15265
|
loadSettingsFile(this.getUserSettingsPath()),
|
|
15144
15266
|
loadSettingsFile(this.getProjectSettingsPath()),
|
|
@@ -15195,9 +15317,6 @@ var SettingsManager = class {
|
|
|
15195
15317
|
this.mergedSettings = merged;
|
|
15196
15318
|
}
|
|
15197
15319
|
checkPermission(toolName, toolInput) {
|
|
15198
|
-
if (!toolName.startsWith(ACP_TOOL_NAME_PREFIX)) {
|
|
15199
|
-
return { decision: "ask" };
|
|
15200
|
-
}
|
|
15201
15320
|
const permissions = this.mergedSettings.permissions;
|
|
15202
15321
|
if (!permissions) {
|
|
15203
15322
|
return { decision: "ask" };
|
|
@@ -15228,6 +15347,43 @@ var SettingsManager = class {
|
|
|
15228
15347
|
getCwd() {
|
|
15229
15348
|
return this.cwd;
|
|
15230
15349
|
}
|
|
15350
|
+
getRepoRoot() {
|
|
15351
|
+
return this.repoRoot;
|
|
15352
|
+
}
|
|
15353
|
+
/**
|
|
15354
|
+
* Persists allow rules to `<primary-worktree>/.claude/settings.local.json`.
|
|
15355
|
+
* Because local settings are resolved against the primary worktree, every
|
|
15356
|
+
* worktree of the same repository picks up the new rule on next load.
|
|
15357
|
+
*
|
|
15358
|
+
* Writes are serialised via `writeMutex` to prevent concurrent callers from
|
|
15359
|
+
* clobbering each other, and use a temp-file + rename to keep the file
|
|
15360
|
+
* consistent if the process dies mid-write.
|
|
15361
|
+
*/
|
|
15362
|
+
async addAllowRules(rules) {
|
|
15363
|
+
if (rules.length === 0) return;
|
|
15364
|
+
if (!this.initialized) await this.initialize();
|
|
15365
|
+
await this.writeMutex.acquire();
|
|
15366
|
+
try {
|
|
15367
|
+
const filePath = this.getLocalSettingsPath();
|
|
15368
|
+
const existing = await readSettingsFileForUpdate(filePath);
|
|
15369
|
+
const permissions = {
|
|
15370
|
+
...existing.permissions ?? {}
|
|
15371
|
+
};
|
|
15372
|
+
const current2 = new Set(permissions.allow ?? []);
|
|
15373
|
+
for (const rule of rules) {
|
|
15374
|
+
current2.add(formatRule(rule));
|
|
15375
|
+
}
|
|
15376
|
+
permissions.allow = Array.from(current2);
|
|
15377
|
+
const next = { ...existing, permissions };
|
|
15378
|
+
await fs7.promises.mkdir(path9.dirname(filePath), { recursive: true });
|
|
15379
|
+
await writeFileAtomic(filePath, `${JSON.stringify(next, null, 2)}
|
|
15380
|
+
`);
|
|
15381
|
+
this.localSettings = next;
|
|
15382
|
+
this.mergeAllSettings();
|
|
15383
|
+
} finally {
|
|
15384
|
+
this.writeMutex.release();
|
|
15385
|
+
}
|
|
15386
|
+
}
|
|
15231
15387
|
async setCwd(cwd) {
|
|
15232
15388
|
if (this.cwd === cwd) return;
|
|
15233
15389
|
if (this.initPromise) await this.initPromise;
|
|
@@ -18813,35 +18969,6 @@ var SessionLogWriter = class _SessionLogWriter {
|
|
|
18813
18969
|
}
|
|
18814
18970
|
};
|
|
18815
18971
|
|
|
18816
|
-
// src/utils/async-mutex.ts
|
|
18817
|
-
var AsyncMutex = class {
|
|
18818
|
-
locked = false;
|
|
18819
|
-
queue = [];
|
|
18820
|
-
async acquire() {
|
|
18821
|
-
if (!this.locked) {
|
|
18822
|
-
this.locked = true;
|
|
18823
|
-
return;
|
|
18824
|
-
}
|
|
18825
|
-
return new Promise((resolve4) => {
|
|
18826
|
-
this.queue.push(resolve4);
|
|
18827
|
-
});
|
|
18828
|
-
}
|
|
18829
|
-
release() {
|
|
18830
|
-
const next = this.queue.shift();
|
|
18831
|
-
if (next) {
|
|
18832
|
-
next();
|
|
18833
|
-
} else {
|
|
18834
|
-
this.locked = false;
|
|
18835
|
-
}
|
|
18836
|
-
}
|
|
18837
|
-
isLocked() {
|
|
18838
|
-
return this.locked;
|
|
18839
|
-
}
|
|
18840
|
-
get queueLength() {
|
|
18841
|
-
return this.queue.length;
|
|
18842
|
-
}
|
|
18843
|
-
};
|
|
18844
|
-
|
|
18845
18972
|
// src/server/cloud-prompt.ts
|
|
18846
18973
|
function normalizeCloudPromptContent(content) {
|
|
18847
18974
|
if (typeof content === "string") {
|