@posthog/agent 2.3.280 → 2.3.282
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 +3 -3
- package/dist/adapters/claude/permissions/permission-options.js.map +1 -1
- package/dist/adapters/claude/tools.js +3 -3
- package/dist/adapters/claude/tools.js.map +1 -1
- package/dist/agent.js +97 -21
- package/dist/agent.js.map +1 -1
- package/dist/execution-mode.d.ts +3 -1
- package/dist/execution-mode.js +12 -4
- 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.d.ts +1 -0
- package/dist/server/agent-server.js +106 -24
- package/dist/server/agent-server.js.map +1 -1
- package/dist/server/bin.cjs +106 -24
- package/dist/server/bin.cjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/codex/codex-agent.test.ts +117 -0
- package/src/adapters/codex/codex-agent.ts +90 -17
- package/src/adapters/codex/codex-client.ts +18 -4
- package/src/adapters/codex/session-state.ts +5 -5
- package/src/execution-mode.test.ts +21 -0
- package/src/execution-mode.ts +11 -4
- package/src/server/agent-server.ts +24 -11
package/package.json
CHANGED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Readable, Writable } from "node:stream";
|
|
2
|
+
import type {
|
|
3
|
+
AgentSideConnection,
|
|
4
|
+
LoadSessionResponse,
|
|
5
|
+
NewSessionResponse,
|
|
6
|
+
} from "@agentclientprotocol/sdk";
|
|
7
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
|
|
9
|
+
const mockCodexConnection = {
|
|
10
|
+
initialize: vi.fn(),
|
|
11
|
+
newSession: vi.fn(),
|
|
12
|
+
loadSession: vi.fn(),
|
|
13
|
+
setSessionMode: vi.fn(),
|
|
14
|
+
listSessions: vi.fn(),
|
|
15
|
+
prompt: vi.fn(),
|
|
16
|
+
setSessionConfigOption: vi.fn(),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const mockKill = vi.fn();
|
|
20
|
+
|
|
21
|
+
vi.mock("@agentclientprotocol/sdk", async () => {
|
|
22
|
+
const actual = await vi.importActual("@agentclientprotocol/sdk");
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...actual,
|
|
26
|
+
ClientSideConnection: vi.fn(() => mockCodexConnection),
|
|
27
|
+
ndJsonStream: vi.fn(() => ({}) as object),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
vi.mock("./spawn", () => ({
|
|
32
|
+
spawnCodexProcess: vi.fn(() => ({
|
|
33
|
+
process: { pid: 1234 },
|
|
34
|
+
stdin: new Writable({
|
|
35
|
+
write(_chunk, _encoding, callback) {
|
|
36
|
+
callback();
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
stdout: new Readable({
|
|
40
|
+
read() {},
|
|
41
|
+
}),
|
|
42
|
+
kill: mockKill,
|
|
43
|
+
})),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
vi.mock("./settings", () => ({
|
|
47
|
+
CodexSettingsManager: vi.fn().mockImplementation((cwd: string) => ({
|
|
48
|
+
initialize: vi.fn(),
|
|
49
|
+
dispose: vi.fn(),
|
|
50
|
+
getCwd: () => cwd,
|
|
51
|
+
setCwd: vi.fn(),
|
|
52
|
+
getSettings: () => ({}),
|
|
53
|
+
})),
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
import { CodexAcpAgent } from "./codex-agent";
|
|
57
|
+
|
|
58
|
+
describe("CodexAcpAgent", () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
vi.clearAllMocks();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function createAgent(): CodexAcpAgent {
|
|
64
|
+
const client = {
|
|
65
|
+
extNotification: vi.fn(),
|
|
66
|
+
} as unknown as AgentSideConnection;
|
|
67
|
+
|
|
68
|
+
return new CodexAcpAgent(client, {
|
|
69
|
+
codexProcessOptions: {
|
|
70
|
+
cwd: process.cwd(),
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
it("applies the requested initial mode for a new session", async () => {
|
|
76
|
+
const agent = createAgent();
|
|
77
|
+
mockCodexConnection.newSession.mockResolvedValue({
|
|
78
|
+
sessionId: "session-1",
|
|
79
|
+
modes: { currentModeId: "auto", availableModes: [] },
|
|
80
|
+
configOptions: [],
|
|
81
|
+
} satisfies Partial<NewSessionResponse>);
|
|
82
|
+
|
|
83
|
+
await agent.newSession({
|
|
84
|
+
cwd: process.cwd(),
|
|
85
|
+
_meta: { permissionMode: "read-only" },
|
|
86
|
+
} as never);
|
|
87
|
+
|
|
88
|
+
expect(mockCodexConnection.setSessionMode).toHaveBeenCalledWith({
|
|
89
|
+
sessionId: "session-1",
|
|
90
|
+
modeId: "read-only",
|
|
91
|
+
});
|
|
92
|
+
expect(
|
|
93
|
+
(agent as unknown as { sessionState: { permissionMode: string } })
|
|
94
|
+
.sessionState.permissionMode,
|
|
95
|
+
).toBe("read-only");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("preserves the live session mode when loading an existing session", async () => {
|
|
99
|
+
const agent = createAgent();
|
|
100
|
+
mockCodexConnection.loadSession.mockResolvedValue({
|
|
101
|
+
modes: { currentModeId: "read-only", availableModes: [] },
|
|
102
|
+
configOptions: [],
|
|
103
|
+
} satisfies Partial<LoadSessionResponse>);
|
|
104
|
+
|
|
105
|
+
await agent.loadSession({
|
|
106
|
+
sessionId: "session-1",
|
|
107
|
+
cwd: process.cwd(),
|
|
108
|
+
_meta: { permissionMode: "auto" },
|
|
109
|
+
} as never);
|
|
110
|
+
|
|
111
|
+
expect(mockCodexConnection.setSessionMode).not.toHaveBeenCalled();
|
|
112
|
+
expect(
|
|
113
|
+
(agent as unknown as { sessionState: { permissionMode: string } })
|
|
114
|
+
.sessionState.permissionMode,
|
|
115
|
+
).toBe("read-only");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -36,8 +36,11 @@ import {
|
|
|
36
36
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
37
37
|
import { POSTHOG_NOTIFICATIONS } from "../../acp-extensions";
|
|
38
38
|
import {
|
|
39
|
-
CODE_EXECUTION_MODES,
|
|
40
39
|
type CodeExecutionMode,
|
|
40
|
+
type CodexNativeMode,
|
|
41
|
+
isCodeExecutionMode,
|
|
42
|
+
isCodexNativeMode,
|
|
43
|
+
type PermissionMode,
|
|
41
44
|
} from "../../execution-mode";
|
|
42
45
|
import type { ProcessSpawnedCallback } from "../../types";
|
|
43
46
|
import { Logger } from "../../utils/logger";
|
|
@@ -83,20 +86,41 @@ type CodexSession = BaseSession & {
|
|
|
83
86
|
settingsManager: CodexSettingsManager;
|
|
84
87
|
};
|
|
85
88
|
|
|
86
|
-
function
|
|
87
|
-
if (mode && (
|
|
88
|
-
return mode
|
|
89
|
+
function toCodexPermissionMode(mode?: string): PermissionMode {
|
|
90
|
+
if (mode && (isCodexNativeMode(mode) || isCodeExecutionMode(mode))) {
|
|
91
|
+
return mode;
|
|
89
92
|
}
|
|
90
|
-
return "
|
|
93
|
+
return "auto";
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
const CODEX_NATIVE_MODE: Record<CodeExecutionMode,
|
|
94
|
-
default: "
|
|
95
|
-
acceptEdits: "
|
|
96
|
-
plan: "
|
|
97
|
-
bypassPermissions: "
|
|
96
|
+
const CODEX_NATIVE_MODE: Record<CodeExecutionMode, CodexNativeMode> = {
|
|
97
|
+
default: "auto",
|
|
98
|
+
acceptEdits: "auto",
|
|
99
|
+
plan: "read-only",
|
|
100
|
+
bypassPermissions: "full-access",
|
|
98
101
|
};
|
|
99
102
|
|
|
103
|
+
function toCodexNativeMode(mode?: string): CodexNativeMode {
|
|
104
|
+
if (mode && isCodexNativeMode(mode)) {
|
|
105
|
+
return mode;
|
|
106
|
+
}
|
|
107
|
+
if (mode && isCodeExecutionMode(mode)) {
|
|
108
|
+
return CODEX_NATIVE_MODE[mode];
|
|
109
|
+
}
|
|
110
|
+
return "auto";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getCurrentPermissionMode(
|
|
114
|
+
currentModeId?: string,
|
|
115
|
+
fallbackMode?: string,
|
|
116
|
+
): PermissionMode {
|
|
117
|
+
if (currentModeId && isCodexNativeMode(currentModeId)) {
|
|
118
|
+
return currentModeId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return toCodexPermissionMode(fallbackMode);
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
export class CodexAcpAgent extends BaseAcpAgent {
|
|
101
125
|
readonly adapterName = "codex";
|
|
102
126
|
declare session: CodexSession;
|
|
@@ -179,6 +203,7 @@ export class CodexAcpAgent extends BaseAcpAgent {
|
|
|
179
203
|
|
|
180
204
|
async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
|
|
181
205
|
const meta = params._meta as NewSessionMeta | undefined;
|
|
206
|
+
const requestedPermissionMode = toCodexPermissionMode(meta?.permissionMode);
|
|
182
207
|
|
|
183
208
|
const response = await this.codexConnection.newSession(params);
|
|
184
209
|
|
|
@@ -186,13 +211,19 @@ export class CodexAcpAgent extends BaseAcpAgent {
|
|
|
186
211
|
this.sessionState = createSessionState(response.sessionId, params.cwd, {
|
|
187
212
|
taskRunId: meta?.taskRunId,
|
|
188
213
|
taskId: meta?.taskId ?? meta?.persistence?.taskId,
|
|
189
|
-
modeId: response.modes?.currentModeId ?? "
|
|
214
|
+
modeId: response.modes?.currentModeId ?? "auto",
|
|
190
215
|
modelId: response.models?.currentModelId,
|
|
191
|
-
permissionMode:
|
|
216
|
+
permissionMode: requestedPermissionMode,
|
|
192
217
|
});
|
|
193
218
|
this.sessionId = response.sessionId;
|
|
194
219
|
this.sessionState.configOptions = response.configOptions ?? [];
|
|
195
220
|
|
|
221
|
+
await this.applyInitialPermissionMode(
|
|
222
|
+
response.sessionId,
|
|
223
|
+
meta?.permissionMode,
|
|
224
|
+
response.modes?.currentModeId,
|
|
225
|
+
);
|
|
226
|
+
|
|
196
227
|
// Emit _posthog/sdk_session so the app can track the session
|
|
197
228
|
if (meta?.taskRunId) {
|
|
198
229
|
await this.client.extNotification(POSTHOG_NOTIFICATIONS.SDK_SESSION, {
|
|
@@ -213,9 +244,14 @@ export class CodexAcpAgent extends BaseAcpAgent {
|
|
|
213
244
|
async loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse> {
|
|
214
245
|
const response = await this.codexConnection.loadSession(params);
|
|
215
246
|
const meta = params._meta as NewSessionMeta | undefined;
|
|
247
|
+
const currentPermissionMode = getCurrentPermissionMode(
|
|
248
|
+
response.modes?.currentModeId,
|
|
249
|
+
meta?.permissionMode,
|
|
250
|
+
);
|
|
216
251
|
|
|
217
252
|
this.sessionState = createSessionState(params.sessionId, params.cwd, {
|
|
218
|
-
|
|
253
|
+
modeId: response.modes?.currentModeId ?? "auto",
|
|
254
|
+
permissionMode: currentPermissionMode,
|
|
219
255
|
});
|
|
220
256
|
this.sessionId = params.sessionId;
|
|
221
257
|
this.sessionState.configOptions = response.configOptions ?? [];
|
|
@@ -234,10 +270,15 @@ export class CodexAcpAgent extends BaseAcpAgent {
|
|
|
234
270
|
});
|
|
235
271
|
|
|
236
272
|
const meta = params._meta as NewSessionMeta | undefined;
|
|
273
|
+
const currentPermissionMode = getCurrentPermissionMode(
|
|
274
|
+
loadResponse.modes?.currentModeId,
|
|
275
|
+
meta?.permissionMode,
|
|
276
|
+
);
|
|
237
277
|
this.sessionState = createSessionState(params.sessionId, params.cwd, {
|
|
238
278
|
taskRunId: meta?.taskRunId,
|
|
239
279
|
taskId: meta?.taskId ?? meta?.persistence?.taskId,
|
|
240
|
-
|
|
280
|
+
modeId: loadResponse.modes?.currentModeId ?? "auto",
|
|
281
|
+
permissionMode: currentPermissionMode,
|
|
241
282
|
});
|
|
242
283
|
this.sessionId = params.sessionId;
|
|
243
284
|
this.sessionState.configOptions = loadResponse.configOptions ?? [];
|
|
@@ -268,17 +309,49 @@ export class CodexAcpAgent extends BaseAcpAgent {
|
|
|
268
309
|
});
|
|
269
310
|
|
|
270
311
|
const meta = params._meta as NewSessionMeta | undefined;
|
|
312
|
+
const requestedPermissionMode = toCodexPermissionMode(meta?.permissionMode);
|
|
271
313
|
this.sessionState = createSessionState(newResponse.sessionId, params.cwd, {
|
|
272
314
|
taskRunId: meta?.taskRunId,
|
|
273
315
|
taskId: meta?.taskId ?? meta?.persistence?.taskId,
|
|
274
|
-
|
|
316
|
+
modeId: newResponse.modes?.currentModeId ?? "auto",
|
|
317
|
+
permissionMode: requestedPermissionMode,
|
|
275
318
|
});
|
|
276
319
|
this.sessionId = newResponse.sessionId;
|
|
277
320
|
this.sessionState.configOptions = newResponse.configOptions ?? [];
|
|
278
321
|
|
|
322
|
+
await this.applyInitialPermissionMode(
|
|
323
|
+
newResponse.sessionId,
|
|
324
|
+
meta?.permissionMode,
|
|
325
|
+
newResponse.modes?.currentModeId,
|
|
326
|
+
);
|
|
327
|
+
|
|
279
328
|
return newResponse;
|
|
280
329
|
}
|
|
281
330
|
|
|
331
|
+
private async applyInitialPermissionMode(
|
|
332
|
+
sessionId: string,
|
|
333
|
+
permissionMode?: string,
|
|
334
|
+
currentModeId?: string,
|
|
335
|
+
): Promise<void> {
|
|
336
|
+
if (!permissionMode) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const nativeMode = toCodexNativeMode(permissionMode);
|
|
341
|
+
if (nativeMode === currentModeId) {
|
|
342
|
+
this.sessionState.modeId = nativeMode;
|
|
343
|
+
this.sessionState.permissionMode = toCodexPermissionMode(permissionMode);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await this.codexConnection.setSessionMode({
|
|
348
|
+
sessionId,
|
|
349
|
+
modeId: nativeMode,
|
|
350
|
+
});
|
|
351
|
+
this.sessionState.modeId = nativeMode;
|
|
352
|
+
this.sessionState.permissionMode = toCodexPermissionMode(permissionMode);
|
|
353
|
+
}
|
|
354
|
+
|
|
282
355
|
async listSessions(
|
|
283
356
|
params: ListSessionsRequest,
|
|
284
357
|
): Promise<ListSessionsResponse> {
|
|
@@ -347,8 +420,8 @@ export class CodexAcpAgent extends BaseAcpAgent {
|
|
|
347
420
|
async setSessionMode(
|
|
348
421
|
params: SetSessionModeRequest,
|
|
349
422
|
): Promise<SetSessionModeResponse> {
|
|
350
|
-
const requestedMode =
|
|
351
|
-
const nativeMode =
|
|
423
|
+
const requestedMode = toCodexPermissionMode(params.modeId);
|
|
424
|
+
const nativeMode = toCodexNativeMode(params.modeId);
|
|
352
425
|
|
|
353
426
|
const response = await this.codexConnection.setSessionMode({
|
|
354
427
|
...params,
|
|
@@ -29,7 +29,7 @@ import type {
|
|
|
29
29
|
WriteTextFileRequest,
|
|
30
30
|
WriteTextFileResponse,
|
|
31
31
|
} from "@agentclientprotocol/sdk";
|
|
32
|
-
import type {
|
|
32
|
+
import type { PermissionMode } from "../../execution-mode";
|
|
33
33
|
import type { Logger } from "../../utils/logger";
|
|
34
34
|
import type { CodexSessionState } from "./session-state";
|
|
35
35
|
|
|
@@ -38,7 +38,7 @@ export interface CodexClientCallbacks {
|
|
|
38
38
|
onUsageUpdate?: (update: Record<string, unknown>) => void;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const AUTO_APPROVED_KINDS: Record<
|
|
41
|
+
const AUTO_APPROVED_KINDS: Record<PermissionMode, Set<ToolKind>> = {
|
|
42
42
|
default: new Set(["read", "search", "fetch", "think"]),
|
|
43
43
|
acceptEdits: new Set(["read", "edit", "search", "fetch", "think"]),
|
|
44
44
|
plan: new Set(["read", "search", "fetch", "think"]),
|
|
@@ -54,13 +54,27 @@ const AUTO_APPROVED_KINDS: Record<CodeExecutionMode, Set<ToolKind>> = {
|
|
|
54
54
|
"switch_mode",
|
|
55
55
|
"other",
|
|
56
56
|
]),
|
|
57
|
+
auto: new Set(["read", "search", "fetch", "think"]),
|
|
58
|
+
"read-only": new Set(["read", "search", "fetch", "think"]),
|
|
59
|
+
"full-access": new Set([
|
|
60
|
+
"read",
|
|
61
|
+
"edit",
|
|
62
|
+
"delete",
|
|
63
|
+
"move",
|
|
64
|
+
"search",
|
|
65
|
+
"execute",
|
|
66
|
+
"think",
|
|
67
|
+
"fetch",
|
|
68
|
+
"switch_mode",
|
|
69
|
+
"other",
|
|
70
|
+
]),
|
|
57
71
|
};
|
|
58
72
|
|
|
59
73
|
function shouldAutoApprove(
|
|
60
|
-
mode:
|
|
74
|
+
mode: PermissionMode,
|
|
61
75
|
kind: ToolKind | null | undefined,
|
|
62
76
|
): boolean {
|
|
63
|
-
if (mode === "bypassPermissions") return true;
|
|
77
|
+
if (mode === "bypassPermissions" || mode === "full-access") return true;
|
|
64
78
|
if (!kind) return false;
|
|
65
79
|
return AUTO_APPROVED_KINDS[mode]?.has(kind) ?? false;
|
|
66
80
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
|
|
7
|
-
import type {
|
|
7
|
+
import type { PermissionMode } from "../../execution-mode";
|
|
8
8
|
|
|
9
9
|
export interface CodexUsage {
|
|
10
10
|
inputTokens: number;
|
|
@@ -22,7 +22,7 @@ export interface CodexSessionState {
|
|
|
22
22
|
accumulatedUsage: CodexUsage;
|
|
23
23
|
contextSize?: number;
|
|
24
24
|
contextUsed?: number;
|
|
25
|
-
permissionMode:
|
|
25
|
+
permissionMode: PermissionMode;
|
|
26
26
|
taskRunId?: string;
|
|
27
27
|
taskId?: string;
|
|
28
28
|
}
|
|
@@ -35,13 +35,13 @@ export function createSessionState(
|
|
|
35
35
|
taskId?: string;
|
|
36
36
|
modeId?: string;
|
|
37
37
|
modelId?: string;
|
|
38
|
-
permissionMode?:
|
|
38
|
+
permissionMode?: PermissionMode;
|
|
39
39
|
},
|
|
40
40
|
): CodexSessionState {
|
|
41
41
|
return {
|
|
42
42
|
sessionId,
|
|
43
43
|
cwd,
|
|
44
|
-
modeId: opts?.modeId ?? "
|
|
44
|
+
modeId: opts?.modeId ?? "auto",
|
|
45
45
|
modelId: opts?.modelId,
|
|
46
46
|
configOptions: [],
|
|
47
47
|
accumulatedUsage: {
|
|
@@ -50,7 +50,7 @@ export function createSessionState(
|
|
|
50
50
|
cachedReadTokens: 0,
|
|
51
51
|
cachedWriteTokens: 0,
|
|
52
52
|
},
|
|
53
|
-
permissionMode: opts?.permissionMode ?? "
|
|
53
|
+
permissionMode: opts?.permissionMode ?? "auto",
|
|
54
54
|
taskRunId: opts?.taskRunId,
|
|
55
55
|
taskId: opts?.taskId,
|
|
56
56
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getAvailableCodexModes, getAvailableModes } from "./execution-mode";
|
|
3
|
+
|
|
4
|
+
describe("execution modes", () => {
|
|
5
|
+
it("includes auto-accept permissions for claude sessions", () => {
|
|
6
|
+
expect(getAvailableModes().map((mode) => mode.id)).toEqual([
|
|
7
|
+
"default",
|
|
8
|
+
"acceptEdits",
|
|
9
|
+
"plan",
|
|
10
|
+
"bypassPermissions",
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("includes full access for codex sessions", () => {
|
|
15
|
+
expect(getAvailableCodexModes().map((mode) => mode.id)).toEqual([
|
|
16
|
+
"read-only",
|
|
17
|
+
"auto",
|
|
18
|
+
"full-access",
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
});
|
package/src/execution-mode.ts
CHANGED
|
@@ -35,8 +35,8 @@ const availableModes: ModeInfo[] = [
|
|
|
35
35
|
if (ALLOW_BYPASS) {
|
|
36
36
|
availableModes.push({
|
|
37
37
|
id: "bypassPermissions",
|
|
38
|
-
name: "
|
|
39
|
-
description: "
|
|
38
|
+
name: "Auto-accept Permissions",
|
|
39
|
+
description: "Auto-accept all permission requests",
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -51,8 +51,11 @@ export const CODE_EXECUTION_MODES = [
|
|
|
51
51
|
|
|
52
52
|
export type CodeExecutionMode = (typeof CODE_EXECUTION_MODES)[number];
|
|
53
53
|
|
|
54
|
+
export function isCodeExecutionMode(mode: string): mode is CodeExecutionMode {
|
|
55
|
+
return (CODE_EXECUTION_MODES as readonly string[]).includes(mode);
|
|
56
|
+
}
|
|
57
|
+
|
|
54
58
|
export function getAvailableModes(): ModeInfo[] {
|
|
55
|
-
// When IS_ROOT, do not allow bypassPermissions
|
|
56
59
|
return IS_ROOT
|
|
57
60
|
? availableModes.filter((m) => m.id !== "bypassPermissions")
|
|
58
61
|
: availableModes;
|
|
@@ -67,6 +70,10 @@ export type CodexNativeMode = (typeof CODEX_NATIVE_MODES)[number];
|
|
|
67
70
|
/** Union of all permission mode IDs across adapters */
|
|
68
71
|
export type PermissionMode = CodeExecutionMode | CodexNativeMode;
|
|
69
72
|
|
|
73
|
+
export function isCodexNativeMode(mode: string): mode is CodexNativeMode {
|
|
74
|
+
return (CODEX_NATIVE_MODES as readonly string[]).includes(mode);
|
|
75
|
+
}
|
|
76
|
+
|
|
70
77
|
const codexModes: ModeInfo[] = [
|
|
71
78
|
{
|
|
72
79
|
id: "read-only",
|
|
@@ -84,7 +91,7 @@ if (ALLOW_BYPASS) {
|
|
|
84
91
|
codexModes.push({
|
|
85
92
|
id: "full-access",
|
|
86
93
|
name: "Full Access",
|
|
87
|
-
description: "
|
|
94
|
+
description: "Auto-accept all permission requests",
|
|
88
95
|
});
|
|
89
96
|
}
|
|
90
97
|
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type InProcessAcpConnection,
|
|
19
19
|
} from "../adapters/acp-connection";
|
|
20
20
|
import { selectRecentTurns } from "../adapters/claude/session/jsonl-hydration";
|
|
21
|
-
import type {
|
|
21
|
+
import type { PermissionMode } from "../execution-mode";
|
|
22
22
|
import { DEFAULT_CODEX_MODEL } from "../gateway-models";
|
|
23
23
|
import { PostHogAPIClient } from "../posthog-api";
|
|
24
24
|
import {
|
|
@@ -164,7 +164,7 @@ interface ActiveSession {
|
|
|
164
164
|
deviceInfo: DeviceInfo;
|
|
165
165
|
logWriter: SessionLogWriter;
|
|
166
166
|
/** Current permission mode, tracked for relay decisions */
|
|
167
|
-
permissionMode:
|
|
167
|
+
permissionMode: PermissionMode;
|
|
168
168
|
/** Whether a desktop client has ever connected via SSE during this session */
|
|
169
169
|
hasDesktopConnected: boolean;
|
|
170
170
|
}
|
|
@@ -265,8 +265,16 @@ export class AgentServer {
|
|
|
265
265
|
return payload.mode ?? this.config.mode;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
private getSessionPermissionMode():
|
|
269
|
-
|
|
268
|
+
private getSessionPermissionMode(): PermissionMode {
|
|
269
|
+
if (this.session?.permissionMode) {
|
|
270
|
+
return this.session.permissionMode;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return this.getRuntimeAdapter() === "codex" ? "auto" : "default";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private shouldRelayPermissionToClient(mode: PermissionMode): boolean {
|
|
277
|
+
return mode === "default" || mode === "auto";
|
|
270
278
|
}
|
|
271
279
|
|
|
272
280
|
private createApp(): Hono {
|
|
@@ -839,12 +847,15 @@ export class AgentServer {
|
|
|
839
847
|
});
|
|
840
848
|
|
|
841
849
|
const runState = preTaskRun?.state as Record<string, unknown> | undefined;
|
|
842
|
-
//
|
|
843
|
-
//
|
|
844
|
-
|
|
850
|
+
// Preserve native Codex modes for cloud runs so they behave the same as
|
|
851
|
+
// local sessions. Claude keeps the historical auto-approved default when
|
|
852
|
+
// PostHog Code has not explicitly selected a mode.
|
|
853
|
+
const initialPermissionMode: PermissionMode =
|
|
845
854
|
typeof runState?.initial_permission_mode === "string"
|
|
846
|
-
? (runState.initial_permission_mode as
|
|
847
|
-
: "
|
|
855
|
+
? (runState.initial_permission_mode as PermissionMode)
|
|
856
|
+
: runtimeAdapter === "codex"
|
|
857
|
+
? "auto"
|
|
858
|
+
: "bypassPermissions";
|
|
848
859
|
const sessionResponse = await clientConnection.newSession({
|
|
849
860
|
cwd: this.config.repositoryPath ?? "/tmp/workspace",
|
|
850
861
|
mcpServers: this.config.mcpServers ?? [],
|
|
@@ -1588,7 +1599,9 @@ ${attributionInstructions}
|
|
|
1588
1599
|
const isQuestion = codeToolKind === "question";
|
|
1589
1600
|
const sessionPermissionMode = this.getSessionPermissionMode();
|
|
1590
1601
|
const needsRelay =
|
|
1591
|
-
isQuestion ||
|
|
1602
|
+
isQuestion ||
|
|
1603
|
+
isPlanApproval ||
|
|
1604
|
+
this.shouldRelayPermissionToClient(sessionPermissionMode);
|
|
1592
1605
|
|
|
1593
1606
|
if (needsRelay && this.session?.hasDesktopConnected) {
|
|
1594
1607
|
this.logger.info("Relaying permission to connected client", {
|
|
@@ -1634,7 +1647,7 @@ ${attributionInstructions}
|
|
|
1634
1647
|
this.session
|
|
1635
1648
|
) {
|
|
1636
1649
|
this.session.permissionMode = params.update
|
|
1637
|
-
.currentModeId as
|
|
1650
|
+
.currentModeId as PermissionMode;
|
|
1638
1651
|
this.logger.info("Permission mode updated", {
|
|
1639
1652
|
mode: params.update.currentModeId,
|
|
1640
1653
|
});
|