@posthog/agent 2.3.403 → 2.3.418
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.js +2 -3
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/tools.js +2 -2
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/agent.js +40 -19
- package/dist/agent.js.map +1 -1
- package/dist/execution-mode.js +3 -3
- package/dist/execution-mode.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 +118 -30
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +121 -33
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +2 -2
- package/src/adapters/claude/claude-agent.ts +14 -0
- package/src/adapters/claude/permissions/permission-handlers.ts +4 -15
- package/src/adapters/claude/permissions/permission-options.ts +1 -3
- package/src/adapters/claude/session/options.ts +1 -1
- package/src/adapters/codex/codex-agent.ts +14 -0
- package/src/execution-mode.ts +7 -10
- package/src/server/agent-server.ts +3 -1
- package/src/server/agentsh-runtime.test.ts +107 -0
- package/src/server/agentsh-runtime.ts +97 -0
- package/src/utils/common.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@posthog/agent",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.418",
|
|
4
4
|
"repository": "https://github.com/PostHog/code",
|
|
5
5
|
"description": "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
|
|
6
6
|
"exports": {
|
|
@@ -102,8 +102,8 @@
|
|
|
102
102
|
"tsx": "^4.20.6",
|
|
103
103
|
"typescript": "^5.5.0",
|
|
104
104
|
"vitest": "^2.1.8",
|
|
105
|
-
"@posthog/enricher": "1.0.0",
|
|
106
105
|
"@posthog/shared": "1.0.0",
|
|
106
|
+
"@posthog/enricher": "1.0.0",
|
|
107
107
|
"@posthog/git": "1.0.0"
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
@@ -1026,6 +1026,19 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1026
1026
|
configOptions: this.session.configOptions,
|
|
1027
1027
|
},
|
|
1028
1028
|
});
|
|
1029
|
+
|
|
1030
|
+
// Notify the agent-server so its cached permissionMode stays in sync.
|
|
1031
|
+
// Without this, cloud sessions that change mode via plan approval or
|
|
1032
|
+
// setSessionMode use a stale mode for relay decisions.
|
|
1033
|
+
if (configId === "mode") {
|
|
1034
|
+
await this.client.sessionUpdate({
|
|
1035
|
+
sessionId: this.sessionId,
|
|
1036
|
+
update: {
|
|
1037
|
+
sessionUpdate: "current_mode_update",
|
|
1038
|
+
currentModeId: value,
|
|
1039
|
+
},
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1029
1042
|
}
|
|
1030
1043
|
|
|
1031
1044
|
private async applySessionMode(modeId: string): Promise<void> {
|
|
@@ -1322,6 +1335,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent {
|
|
|
1322
1335
|
logger: this.logger,
|
|
1323
1336
|
updateConfigOption: (configId: string, value: string) =>
|
|
1324
1337
|
this.updateConfigOption(configId, value),
|
|
1338
|
+
applySessionMode: (modeId: string) => this.applySessionMode(modeId),
|
|
1325
1339
|
allowedDomains,
|
|
1326
1340
|
});
|
|
1327
1341
|
}
|
|
@@ -56,6 +56,7 @@ interface ToolHandlerContext {
|
|
|
56
56
|
fileContentCache: { [key: string]: string };
|
|
57
57
|
logger: Logger;
|
|
58
58
|
updateConfigOption: (configId: string, value: string) => Promise<void>;
|
|
59
|
+
applySessionMode: (modeId: string) => Promise<void>;
|
|
59
60
|
allowedDomains?: string[];
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -167,8 +168,6 @@ async function applyPlanApproval(
|
|
|
167
168
|
context: ToolHandlerContext,
|
|
168
169
|
updatedInput: Record<string, unknown>,
|
|
169
170
|
): Promise<ToolPermissionResult> {
|
|
170
|
-
const { session } = context;
|
|
171
|
-
|
|
172
171
|
if (
|
|
173
172
|
response.outcome?.outcome === "selected" &&
|
|
174
173
|
(response.outcome.optionId === "auto" ||
|
|
@@ -176,16 +175,7 @@ async function applyPlanApproval(
|
|
|
176
175
|
response.outcome.optionId === "acceptEdits" ||
|
|
177
176
|
response.outcome.optionId === "bypassPermissions")
|
|
178
177
|
) {
|
|
179
|
-
|
|
180
|
-
.optionId as typeof session.permissionMode;
|
|
181
|
-
await session.query.setPermissionMode(response.outcome.optionId);
|
|
182
|
-
await context.client.sessionUpdate({
|
|
183
|
-
sessionId: context.sessionId,
|
|
184
|
-
update: {
|
|
185
|
-
sessionUpdate: "current_mode_update",
|
|
186
|
-
currentModeId: response.outcome.optionId,
|
|
187
|
-
},
|
|
188
|
-
});
|
|
178
|
+
await context.applySessionMode(response.outcome.optionId);
|
|
189
179
|
await context.updateConfigOption("mode", response.outcome.optionId);
|
|
190
180
|
|
|
191
181
|
return {
|
|
@@ -215,10 +205,9 @@ async function applyPlanApproval(
|
|
|
215
205
|
async function handleEnterPlanModeTool(
|
|
216
206
|
context: ToolHandlerContext,
|
|
217
207
|
): Promise<ToolPermissionResult> {
|
|
218
|
-
const {
|
|
208
|
+
const { toolInput } = context;
|
|
219
209
|
|
|
220
|
-
|
|
221
|
-
await session.query.setPermissionMode("plan");
|
|
210
|
+
await context.applySessionMode("plan");
|
|
222
211
|
await context.updateConfigOption("mode", "plan");
|
|
223
212
|
|
|
224
213
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PermissionUpdate } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
-
import {
|
|
2
|
+
import { ALLOW_BYPASS } from "../../../utils/common";
|
|
3
3
|
import { BASH_TOOLS, READ_TOOLS, SEARCH_TOOLS, WRITE_TOOLS } from "../tools";
|
|
4
4
|
|
|
5
5
|
export interface PermissionOption {
|
|
@@ -92,8 +92,6 @@ export function buildPermissionOptions(
|
|
|
92
92
|
return permissionOptions("Yes, always allow");
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
const ALLOW_BYPASS = !IS_ROOT || !!process.env.IS_SANDBOX;
|
|
96
|
-
|
|
97
95
|
export function buildExitPlanModePermissionOptions(): PermissionOption[] {
|
|
98
96
|
const options: PermissionOption[] = [];
|
|
99
97
|
|
|
@@ -317,7 +317,7 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
|
|
|
317
317
|
stderr: (err) => params.logger.error(err),
|
|
318
318
|
cwd: params.cwd,
|
|
319
319
|
includePartialMessages: true,
|
|
320
|
-
allowDangerouslySkipPermissions: !IS_ROOT,
|
|
320
|
+
allowDangerouslySkipPermissions: !IS_ROOT || !!process.env.IS_SANDBOX,
|
|
321
321
|
permissionMode: params.permissionMode,
|
|
322
322
|
canUseTool: params.canUseTool,
|
|
323
323
|
executable: "node",
|
|
@@ -667,6 +667,20 @@ export class CodexAcpAgent extends BaseAcpAgent {
|
|
|
667
667
|
if (response.configOptions) {
|
|
668
668
|
this.sessionState.configOptions = response.configOptions;
|
|
669
669
|
}
|
|
670
|
+
if (params.configId === "mode" && typeof params.value === "string") {
|
|
671
|
+
// Signal the mode change to agent-server so its session.permissionMode
|
|
672
|
+
// cache (used by shouldRelayPermissionToClient) stays in sync with the
|
|
673
|
+
// real Codex mode. Claude emits the same signal from its equivalent
|
|
674
|
+
// handler; without it, the agent-server's relay decisions for cloud
|
|
675
|
+
// runs would use a stale mode and silently auto-approve tool calls.
|
|
676
|
+
await this.client.sessionUpdate({
|
|
677
|
+
sessionId: this.sessionId,
|
|
678
|
+
update: {
|
|
679
|
+
sessionUpdate: "current_mode_update",
|
|
680
|
+
currentModeId: params.value,
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
}
|
|
670
684
|
return response;
|
|
671
685
|
}
|
|
672
686
|
|
package/src/execution-mode.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ALLOW_BYPASS } from "./utils/common";
|
|
2
2
|
|
|
3
3
|
export interface ModeInfo {
|
|
4
4
|
id: string;
|
|
@@ -6,9 +6,6 @@ export interface ModeInfo {
|
|
|
6
6
|
description: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
// Helper constant that can easily be toggled for env/feature flag/etc
|
|
10
|
-
const ALLOW_BYPASS = !IS_ROOT;
|
|
11
|
-
|
|
12
9
|
const availableModes: ModeInfo[] = [
|
|
13
10
|
{
|
|
14
11
|
id: "default",
|
|
@@ -58,9 +55,9 @@ export function isCodeExecutionMode(mode: string): mode is CodeExecutionMode {
|
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
export function getAvailableModes(): ModeInfo[] {
|
|
61
|
-
return
|
|
62
|
-
? availableModes
|
|
63
|
-
: availableModes;
|
|
58
|
+
return ALLOW_BYPASS
|
|
59
|
+
? availableModes
|
|
60
|
+
: availableModes.filter((m) => m.id !== "bypassPermissions");
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
// --- Codex-native modes ---
|
|
@@ -98,7 +95,7 @@ if (ALLOW_BYPASS) {
|
|
|
98
95
|
}
|
|
99
96
|
|
|
100
97
|
export function getAvailableCodexModes(): ModeInfo[] {
|
|
101
|
-
return
|
|
102
|
-
? codexModes
|
|
103
|
-
: codexModes;
|
|
98
|
+
return ALLOW_BYPASS
|
|
99
|
+
? codexModes
|
|
100
|
+
: codexModes.filter((m) => m.id !== "full-access");
|
|
104
101
|
}
|
|
@@ -48,6 +48,7 @@ import { resourceLink } from "../utils/acp-content";
|
|
|
48
48
|
import { AsyncMutex } from "../utils/async-mutex";
|
|
49
49
|
import { getLlmGatewayUrl } from "../utils/gateway";
|
|
50
50
|
import { Logger } from "../utils/logger";
|
|
51
|
+
import { logAgentshRuntimeInfo } from "./agentsh-runtime";
|
|
51
52
|
import {
|
|
52
53
|
normalizeCloudPromptContent,
|
|
53
54
|
promptBlocksToText,
|
|
@@ -297,7 +298,7 @@ export class AgentServer {
|
|
|
297
298
|
}
|
|
298
299
|
|
|
299
300
|
private shouldRelayPermissionToClient(mode: PermissionMode): boolean {
|
|
300
|
-
return mode === "default" || mode === "auto";
|
|
301
|
+
return mode === "default" || mode === "auto" || mode === "read-only";
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
private createApp(): Hono {
|
|
@@ -954,6 +955,7 @@ export class AgentServer {
|
|
|
954
955
|
this.logger.debug(
|
|
955
956
|
`Agent version: ${this.config.version ?? packageJson.version}`,
|
|
956
957
|
);
|
|
958
|
+
await logAgentshRuntimeInfo(this.logger);
|
|
957
959
|
this.logger.debug(`Initial permission mode: ${initialPermissionMode}`);
|
|
958
960
|
|
|
959
961
|
// Signal in_progress so the UI can start polling for updates
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
logAgentshRuntimeInfo,
|
|
4
|
+
resolveAgentshRuntimeInfo,
|
|
5
|
+
} from "./agentsh-runtime";
|
|
6
|
+
|
|
7
|
+
describe("agentsh runtime detection", () => {
|
|
8
|
+
it("returns null when no agentsh session marker exists", async () => {
|
|
9
|
+
const getVersion = vi.fn();
|
|
10
|
+
const result = await resolveAgentshRuntimeInfo({
|
|
11
|
+
readSessionId: async () => {
|
|
12
|
+
const error = new Error("missing") as NodeJS.ErrnoException;
|
|
13
|
+
error.code = "ENOENT";
|
|
14
|
+
throw error;
|
|
15
|
+
},
|
|
16
|
+
getVersion,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(result).toBeNull();
|
|
20
|
+
expect(getVersion).not.toHaveBeenCalled();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("rethrows unexpected session marker read errors", async () => {
|
|
24
|
+
const error = new Error("permission denied") as NodeJS.ErrnoException;
|
|
25
|
+
error.code = "EACCES";
|
|
26
|
+
|
|
27
|
+
await expect(
|
|
28
|
+
resolveAgentshRuntimeInfo({
|
|
29
|
+
readSessionId: async () => {
|
|
30
|
+
throw error;
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
).rejects.toBe(error);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it.each([
|
|
37
|
+
{
|
|
38
|
+
name: "returns the agentsh session id and version",
|
|
39
|
+
getVersion: async () => ({
|
|
40
|
+
stdout: "agentsh version 0.16.7\n",
|
|
41
|
+
stderr: "",
|
|
42
|
+
}),
|
|
43
|
+
expected: {
|
|
44
|
+
sessionId: "session-123",
|
|
45
|
+
version: "agentsh version 0.16.7",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "keeps the agentsh signal when version lookup fails",
|
|
50
|
+
getVersion: async () => {
|
|
51
|
+
throw new Error("agentsh not found");
|
|
52
|
+
},
|
|
53
|
+
expected: {
|
|
54
|
+
sessionId: "session-123",
|
|
55
|
+
version: null,
|
|
56
|
+
versionLookupError: "agentsh not found",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
])("$name", async ({ getVersion, expected }) => {
|
|
60
|
+
const result = await resolveAgentshRuntimeInfo({
|
|
61
|
+
readSessionId: async () => "session-123\n",
|
|
62
|
+
getVersion,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(result).toEqual(expected);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("logs session id and version details", async () => {
|
|
69
|
+
const logger = { debug: vi.fn() };
|
|
70
|
+
|
|
71
|
+
await logAgentshRuntimeInfo(logger, {
|
|
72
|
+
readSessionId: async () => "session-123\n",
|
|
73
|
+
getVersion: async () => ({
|
|
74
|
+
stdout: "agentsh version 0.16.7\n",
|
|
75
|
+
stderr: "",
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
80
|
+
"Agentsh session ID: session-123",
|
|
81
|
+
);
|
|
82
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
83
|
+
"Agentsh hardening version: agentsh version 0.16.7",
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("logs version lookup failures", async () => {
|
|
88
|
+
const logger = { debug: vi.fn() };
|
|
89
|
+
|
|
90
|
+
await logAgentshRuntimeInfo(logger, {
|
|
91
|
+
readSessionId: async () => "session-123\n",
|
|
92
|
+
getVersion: async () => {
|
|
93
|
+
throw new Error("agentsh not found");
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
98
|
+
"Agentsh session ID: session-123",
|
|
99
|
+
);
|
|
100
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
101
|
+
"Agentsh hardening version: unknown",
|
|
102
|
+
);
|
|
103
|
+
expect(logger.debug).toHaveBeenCalledWith(
|
|
104
|
+
"Agentsh version lookup failed: agentsh not found",
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import type { Logger } from "../utils/logger";
|
|
5
|
+
|
|
6
|
+
export const AGENTSH_SESSION_ID_FILE = "/tmp/agentsh-session-id";
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
interface AgentshVersionOutput {
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ResolveAgentshRuntimeInfoOptions {
|
|
16
|
+
sessionIdPath?: string;
|
|
17
|
+
readSessionId?: (path: string) => Promise<string>;
|
|
18
|
+
getVersion?: () => Promise<AgentshVersionOutput>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AgentshRuntimeInfo {
|
|
22
|
+
sessionId: string;
|
|
23
|
+
version: string | null;
|
|
24
|
+
versionLookupError?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function errorMessage(error: unknown): string {
|
|
28
|
+
return error instanceof Error ? error.message : String(error);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseAgentshVersion(output: AgentshVersionOutput): string | null {
|
|
32
|
+
const version = `${output.stdout}\n${output.stderr}`
|
|
33
|
+
.split("\n")
|
|
34
|
+
.map((line) => line.trim())
|
|
35
|
+
.find(Boolean);
|
|
36
|
+
return version ?? null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function getAgentshVersion(): Promise<AgentshVersionOutput> {
|
|
40
|
+
const { stdout, stderr } = await execFileAsync("agentsh", ["--version"], {
|
|
41
|
+
timeout: 5_000,
|
|
42
|
+
});
|
|
43
|
+
return { stdout, stderr };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function resolveAgentshRuntimeInfo({
|
|
47
|
+
sessionIdPath = AGENTSH_SESSION_ID_FILE,
|
|
48
|
+
readSessionId = async (path: string) => readFile(path, "utf8"),
|
|
49
|
+
getVersion = getAgentshVersion,
|
|
50
|
+
}: ResolveAgentshRuntimeInfoOptions = {}): Promise<AgentshRuntimeInfo | null> {
|
|
51
|
+
let sessionId: string;
|
|
52
|
+
try {
|
|
53
|
+
sessionId = (await readSessionId(sessionIdPath)).trim();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
56
|
+
if (code === "ENOENT") {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!sessionId) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const output = await getVersion();
|
|
68
|
+
return {
|
|
69
|
+
sessionId,
|
|
70
|
+
version: parseAgentshVersion(output),
|
|
71
|
+
};
|
|
72
|
+
} catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
sessionId,
|
|
75
|
+
version: null,
|
|
76
|
+
versionLookupError: errorMessage(error),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function logAgentshRuntimeInfo(
|
|
82
|
+
logger: Pick<Logger, "debug">,
|
|
83
|
+
options?: ResolveAgentshRuntimeInfoOptions,
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
const agentsh = await resolveAgentshRuntimeInfo(options);
|
|
86
|
+
if (!agentsh) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
logger.debug(`Agentsh session ID: ${agentsh.sessionId}`);
|
|
91
|
+
logger.debug(`Agentsh hardening version: ${agentsh.version ?? "unknown"}`);
|
|
92
|
+
if (agentsh.versionLookupError) {
|
|
93
|
+
logger.debug(
|
|
94
|
+
`Agentsh version lookup failed: ${agentsh.versionLookupError}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/utils/common.ts
CHANGED
|
@@ -23,6 +23,8 @@ export const IS_ROOT =
|
|
|
23
23
|
typeof process !== "undefined" &&
|
|
24
24
|
(process.geteuid?.() ?? process.getuid?.()) === 0;
|
|
25
25
|
|
|
26
|
+
export const ALLOW_BYPASS = !IS_ROOT || !!process.env.IS_SANDBOX;
|
|
27
|
+
|
|
26
28
|
export function unreachable(value: never, logger: Logger): void {
|
|
27
29
|
let valueAsString: string;
|
|
28
30
|
try {
|