@junctionpanel/server 0.1.29 → 0.1.32
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/server/client/daemon-client.d.ts +4 -1
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +29 -0
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +2 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +63 -4
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +9 -2
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +19 -2
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +30 -30
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +33 -1
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/codex-config.d.ts +12 -0
- package/dist/server/server/agent/codex-config.d.ts.map +1 -0
- package/dist/server/server/agent/codex-config.js +42 -0
- package/dist/server/server/agent/codex-config.js.map +1 -0
- package/dist/server/server/agent/mcp-server.js +8 -8
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/provider-launch-config.d.ts +2 -2
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.js +32 -5
- package/dist/server/server/agent/provider-launch-config.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +10 -10
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/providers/claude/model-catalog.d.ts +17 -25
- package/dist/server/server/agent/providers/claude/model-catalog.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/model-catalog.js +228 -40
- package/dist/server/server/agent/providers/claude/model-catalog.js.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts +2 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +201 -36
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +38 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +540 -133
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts +17 -5
- package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.js +1040 -482
- package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/session.d.ts +1 -0
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +54 -3
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/shared/messages.d.ts +2165 -1353
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +35 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/package.json +3 -2
|
@@ -7,6 +7,7 @@ import { z } from "zod";
|
|
|
7
7
|
import { loadCodexPersistedTimeline } from "./codex-rollout-timeline.js";
|
|
8
8
|
import { mapCodexRolloutToolCall, mapCodexToolCallFromThreadItem, } from "./codex/tool-call-mapper.js";
|
|
9
9
|
import { applyProviderEnv, isProviderCommandAvailable, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
|
|
10
|
+
import { buildCodexRuntimeExtra, DEFAULT_CODEX_MODE_ID, isCodexPlanModeEnabled, normalizeCodexModeId, setCodexPlanModeEnabled, } from "../codex-config.js";
|
|
10
11
|
import { writeImageAttachment } from "./image-attachments.js";
|
|
11
12
|
const DEFAULT_TIMEOUT_MS = 14 * 24 * 60 * 60 * 1000;
|
|
12
13
|
const TURN_START_TIMEOUT_MS = 90 * 1000;
|
|
@@ -21,39 +22,39 @@ const CODEX_APP_SERVER_CAPABILITIES = {
|
|
|
21
22
|
};
|
|
22
23
|
const CODEX_MODES = [
|
|
23
24
|
{
|
|
24
|
-
id: "
|
|
25
|
-
label: "
|
|
26
|
-
description: "
|
|
25
|
+
id: "default",
|
|
26
|
+
label: "Ask",
|
|
27
|
+
description: "Trusted commands run automatically; untrusted commands and edits require approval.",
|
|
27
28
|
},
|
|
28
29
|
{
|
|
29
|
-
id: "
|
|
30
|
-
label: "Auto",
|
|
31
|
-
description: "
|
|
30
|
+
id: "acceptEdits",
|
|
31
|
+
label: "Auto Edit",
|
|
32
|
+
description: "Workspace edits and commands run automatically without approval.",
|
|
32
33
|
},
|
|
33
34
|
{
|
|
34
|
-
id: "
|
|
35
|
-
label: "
|
|
36
|
-
description: "
|
|
35
|
+
id: "bypassPermissions",
|
|
36
|
+
label: "Bypass",
|
|
37
|
+
description: "Full disk and network access with no approval prompts.",
|
|
37
38
|
},
|
|
38
39
|
];
|
|
39
|
-
const DEFAULT_CODEX_MODE_ID = "auto";
|
|
40
40
|
const MODE_PRESETS = {
|
|
41
|
-
|
|
42
|
-
approvalPolicy: "
|
|
43
|
-
sandbox: "
|
|
41
|
+
default: {
|
|
42
|
+
approvalPolicy: "untrusted",
|
|
43
|
+
sandbox: "workspace-write",
|
|
44
44
|
},
|
|
45
|
-
|
|
46
|
-
approvalPolicy: "
|
|
45
|
+
acceptEdits: {
|
|
46
|
+
approvalPolicy: "never",
|
|
47
47
|
sandbox: "workspace-write",
|
|
48
48
|
},
|
|
49
|
-
|
|
49
|
+
bypassPermissions: {
|
|
50
50
|
approvalPolicy: "never",
|
|
51
51
|
sandbox: "danger-full-access",
|
|
52
52
|
networkAccess: true,
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
55
|
function validateCodexMode(modeId) {
|
|
56
|
-
|
|
56
|
+
const normalizedModeId = normalizeCodexModeId(modeId);
|
|
57
|
+
if (typeof normalizedModeId !== "string" || !(normalizedModeId in MODE_PRESETS)) {
|
|
57
58
|
const validModes = Object.keys(MODE_PRESETS).join(", ");
|
|
58
59
|
throw new Error(`Invalid Codex mode "${modeId}". Valid modes are: ${validModes}`);
|
|
59
60
|
}
|
|
@@ -184,6 +185,13 @@ function toRecord(value) {
|
|
|
184
185
|
}
|
|
185
186
|
return value;
|
|
186
187
|
}
|
|
188
|
+
function toNonEmptyString(value) {
|
|
189
|
+
if (typeof value !== "string") {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const trimmed = value.trim();
|
|
193
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
194
|
+
}
|
|
187
195
|
function parseUpdatedQuestionAnswers(updatedInput) {
|
|
188
196
|
const parsed = {};
|
|
189
197
|
const root = toRecord(updatedInput);
|
|
@@ -218,6 +226,98 @@ function parseUpdatedQuestionAnswers(updatedInput) {
|
|
|
218
226
|
}
|
|
219
227
|
return parsed;
|
|
220
228
|
}
|
|
229
|
+
function normalizeCodexQuestionOption(value) {
|
|
230
|
+
if (typeof value === "string") {
|
|
231
|
+
const label = value.trim();
|
|
232
|
+
return label ? { label, value: label } : null;
|
|
233
|
+
}
|
|
234
|
+
const record = toRecord(value);
|
|
235
|
+
if (!record) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
const label = toNonEmptyString(record.label) ?? toNonEmptyString(record.value);
|
|
239
|
+
if (!label) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
label,
|
|
244
|
+
value: toNonEmptyString(record.value) ?? label,
|
|
245
|
+
description: toNonEmptyString(record.description) ?? undefined,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function normalizeCodexQuestionDescriptor(value, fallbackIndex) {
|
|
249
|
+
const record = toRecord(value);
|
|
250
|
+
if (!record) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
const id = toNonEmptyString(record.id) ??
|
|
254
|
+
toNonEmptyString(record.key) ??
|
|
255
|
+
`question_${fallbackIndex + 1}`;
|
|
256
|
+
const options = Array.isArray(record.options)
|
|
257
|
+
? record.options
|
|
258
|
+
.map((option) => normalizeCodexQuestionOption(option))
|
|
259
|
+
.filter((option) => option !== null)
|
|
260
|
+
: [];
|
|
261
|
+
return {
|
|
262
|
+
id,
|
|
263
|
+
header: toNonEmptyString(record.header) ?? undefined,
|
|
264
|
+
question: toNonEmptyString(record.question) ??
|
|
265
|
+
toNonEmptyString(record.prompt) ??
|
|
266
|
+
toNonEmptyString(record.text) ??
|
|
267
|
+
undefined,
|
|
268
|
+
isOther: Boolean(record.isOther ?? record.is_other),
|
|
269
|
+
isSecret: Boolean(record.isSecret ?? record.is_secret),
|
|
270
|
+
options,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function normalizeCodexQuestionDescriptors(value) {
|
|
274
|
+
if (!Array.isArray(value)) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
return value
|
|
278
|
+
.map((question, index) => normalizeCodexQuestionDescriptor(question, index))
|
|
279
|
+
.filter((question) => question !== null);
|
|
280
|
+
}
|
|
281
|
+
function extractExplicitDecision(updatedInput) {
|
|
282
|
+
const root = toRecord(updatedInput);
|
|
283
|
+
if (!root || !Object.prototype.hasOwnProperty.call(root, "decision")) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return root.decision ?? null;
|
|
287
|
+
}
|
|
288
|
+
function buildCodexPermissionsResponse(response, requestedPermissions) {
|
|
289
|
+
const root = toRecord(response.updatedInput);
|
|
290
|
+
const permissions = toRecord(root?.permissions);
|
|
291
|
+
const scope = root?.scope === "session" ? "session" : "turn";
|
|
292
|
+
if (permissions) {
|
|
293
|
+
return { permissions, scope };
|
|
294
|
+
}
|
|
295
|
+
if (response.behavior === "allow") {
|
|
296
|
+
return { permissions: toRecord(requestedPermissions) ?? {}, scope };
|
|
297
|
+
}
|
|
298
|
+
return { permissions: {}, scope };
|
|
299
|
+
}
|
|
300
|
+
function buildCodexElicitationResponse(response) {
|
|
301
|
+
const root = toRecord(response.updatedInput);
|
|
302
|
+
const explicitAction = root?.action === "accept" || root?.action === "decline" || root?.action === "cancel"
|
|
303
|
+
? root.action
|
|
304
|
+
: null;
|
|
305
|
+
if (response.behavior === "allow") {
|
|
306
|
+
return {
|
|
307
|
+
action: explicitAction ?? "accept",
|
|
308
|
+
content: root?.content ?? null,
|
|
309
|
+
_meta: root?._meta ?? null,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return {
|
|
313
|
+
action: explicitAction ?? (response.interrupt ? "cancel" : "decline"),
|
|
314
|
+
content: null,
|
|
315
|
+
_meta: root?._meta ?? null,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function toPendingPermissionId(requestId) {
|
|
319
|
+
return `permission-${String(requestId)}`;
|
|
320
|
+
}
|
|
221
321
|
async function listCodexCustomPrompts() {
|
|
222
322
|
const codexHome = resolveCodexHomeDir();
|
|
223
323
|
const promptsDir = path.join(codexHome, "prompts");
|
|
@@ -524,7 +624,7 @@ class CodexAppServerClient {
|
|
|
524
624
|
const request = msg;
|
|
525
625
|
const handler = this.requestHandlers.get(request.method);
|
|
526
626
|
try {
|
|
527
|
-
const result = handler ? await handler(request.params) : {};
|
|
627
|
+
const result = handler ? await handler(request.params, request.id) : {};
|
|
528
628
|
const response = { id: request.id, result };
|
|
529
629
|
this.child.stdin.write(`${JSON.stringify(response)}\n`);
|
|
530
630
|
}
|
|
@@ -581,12 +681,73 @@ function parsePlanTextToTodoItems(text) {
|
|
|
581
681
|
completed: false,
|
|
582
682
|
}));
|
|
583
683
|
}
|
|
684
|
+
function formatProposedPlanBlock(text) {
|
|
685
|
+
return `<proposed_plan>\n${text}\n</proposed_plan>`;
|
|
686
|
+
}
|
|
687
|
+
function formatProposedPlanChunk(text, options) {
|
|
688
|
+
const parts = [];
|
|
689
|
+
if (options?.open) {
|
|
690
|
+
parts.push("<proposed_plan>\n");
|
|
691
|
+
}
|
|
692
|
+
parts.push(text);
|
|
693
|
+
if (options?.close) {
|
|
694
|
+
parts.push("\n</proposed_plan>");
|
|
695
|
+
}
|
|
696
|
+
return parts.join("");
|
|
697
|
+
}
|
|
584
698
|
function planStepsToTodoItems(steps) {
|
|
585
699
|
return steps.map((entry) => ({
|
|
586
700
|
text: entry.step,
|
|
587
701
|
completed: entry.status === "completed",
|
|
588
702
|
}));
|
|
589
703
|
}
|
|
704
|
+
function supportsPlanCollaborationMode(response) {
|
|
705
|
+
const candidateArrays = [
|
|
706
|
+
Array.isArray(response) ? response : null,
|
|
707
|
+
Array.isArray(response?.data)
|
|
708
|
+
? (response.data)
|
|
709
|
+
: null,
|
|
710
|
+
Array.isArray(response?.modes)
|
|
711
|
+
? (response.modes)
|
|
712
|
+
: null,
|
|
713
|
+
Array.isArray(response?.collaborationModes)
|
|
714
|
+
? (response.collaborationModes)
|
|
715
|
+
: null,
|
|
716
|
+
Array.isArray(response?.items)
|
|
717
|
+
? (response.items)
|
|
718
|
+
: null,
|
|
719
|
+
];
|
|
720
|
+
for (const entries of candidateArrays) {
|
|
721
|
+
if (!entries)
|
|
722
|
+
continue;
|
|
723
|
+
for (const entry of entries) {
|
|
724
|
+
const record = toRecord(entry);
|
|
725
|
+
const modeName = (typeof record?.mode === "string" ? record.mode : null) ??
|
|
726
|
+
(typeof record?.name === "string" ? record.name : null) ??
|
|
727
|
+
(typeof record?.id === "string" ? record.id : null) ??
|
|
728
|
+
(typeof entry === "string" ? entry : null);
|
|
729
|
+
if (modeName?.trim().toLowerCase() === "plan") {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
function shouldRetryInitializeWithoutExperimentalApi(error) {
|
|
737
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
738
|
+
return (message.includes("experimentalapi") ||
|
|
739
|
+
message.includes("experimental api") ||
|
|
740
|
+
message.includes("capabilities") ||
|
|
741
|
+
message.includes("unknown field") ||
|
|
742
|
+
message.includes("invalid params"));
|
|
743
|
+
}
|
|
744
|
+
function shouldRetryTurnStartWithoutCollaborationMode(error) {
|
|
745
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
746
|
+
return (message.includes("collaborationmode") ||
|
|
747
|
+
message.includes("collaboration mode") ||
|
|
748
|
+
message.includes("experimentalapi") ||
|
|
749
|
+
message.includes("experimental api"));
|
|
750
|
+
}
|
|
590
751
|
function extractPatchLikeText(value) {
|
|
591
752
|
if (!value || typeof value !== "object") {
|
|
592
753
|
return undefined;
|
|
@@ -896,8 +1057,11 @@ function threadItemToTimeline(item, options) {
|
|
|
896
1057
|
}
|
|
897
1058
|
case "plan": {
|
|
898
1059
|
const text = normalizedItem.text ?? "";
|
|
1060
|
+
if (typeof text === "string" && text.trim().length > 0) {
|
|
1061
|
+
return { type: "assistant_message", text: formatProposedPlanBlock(text) };
|
|
1062
|
+
}
|
|
899
1063
|
const items = parsePlanTextToTodoItems(text);
|
|
900
|
-
return { type: "todo", items };
|
|
1064
|
+
return items.length > 0 ? { type: "todo", items } : null;
|
|
901
1065
|
}
|
|
902
1066
|
case "reasoning": {
|
|
903
1067
|
const summary = Array.isArray(normalizedItem.summary)
|
|
@@ -958,6 +1122,13 @@ const TurnPlanUpdatedNotificationSchema = z.object({
|
|
|
958
1122
|
})
|
|
959
1123
|
.passthrough()),
|
|
960
1124
|
}).passthrough();
|
|
1125
|
+
const ItemPlanDeltaNotificationSchema = z.object({
|
|
1126
|
+
itemId: z.string(),
|
|
1127
|
+
delta: z.string(),
|
|
1128
|
+
}).passthrough();
|
|
1129
|
+
const ServerRequestResolvedNotificationSchema = z.object({
|
|
1130
|
+
requestId: z.union([z.string(), z.number()]),
|
|
1131
|
+
}).passthrough();
|
|
961
1132
|
const TurnDiffUpdatedNotificationSchema = z.object({
|
|
962
1133
|
diff: z.string(),
|
|
963
1134
|
}).passthrough();
|
|
@@ -1097,6 +1268,12 @@ const CodexNotificationSchema = z.union([
|
|
|
1097
1268
|
})),
|
|
1098
1269
|
})),
|
|
1099
1270
|
z.object({ method: z.literal("turn/plan/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1271
|
+
z.object({ method: z.literal("item/plan/delta"), params: ItemPlanDeltaNotificationSchema }).transform(({ params }) => ({
|
|
1272
|
+
kind: "plan_delta",
|
|
1273
|
+
itemId: params.itemId,
|
|
1274
|
+
delta: params.delta,
|
|
1275
|
+
})),
|
|
1276
|
+
z.object({ method: z.literal("item/plan/delta"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1100
1277
|
z.object({ method: z.literal("turn/diff/updated"), params: TurnDiffUpdatedNotificationSchema }).transform(({ params }) => ({ kind: "diff_updated", diff: params.diff })),
|
|
1101
1278
|
z.object({ method: z.literal("turn/diff/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1102
1279
|
z.object({
|
|
@@ -1104,6 +1281,14 @@ const CodexNotificationSchema = z.union([
|
|
|
1104
1281
|
params: ThreadTokenUsageUpdatedNotificationSchema,
|
|
1105
1282
|
}).transform(({ params }) => ({ kind: "token_usage_updated", tokenUsage: params.tokenUsage })),
|
|
1106
1283
|
z.object({ method: z.literal("thread/tokenUsage/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1284
|
+
z.object({
|
|
1285
|
+
method: z.literal("serverRequest/resolved"),
|
|
1286
|
+
params: ServerRequestResolvedNotificationSchema,
|
|
1287
|
+
}).transform(({ params }) => ({
|
|
1288
|
+
kind: "server_request_resolved",
|
|
1289
|
+
requestId: String(params.requestId),
|
|
1290
|
+
})),
|
|
1291
|
+
z.object({ method: z.literal("serverRequest/resolved"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1107
1292
|
z.object({ method: z.literal("item/agentMessage/delta"), params: ItemTextDeltaNotificationSchema }).transform(({ params }) => ({
|
|
1108
1293
|
kind: "agent_message_delta",
|
|
1109
1294
|
itemId: params.itemId,
|
|
@@ -1303,6 +1488,22 @@ export async function codexAppServerTurnInputFromPrompt(prompt, logger) {
|
|
|
1303
1488
|
}
|
|
1304
1489
|
export const __codexAppServerInternals = {
|
|
1305
1490
|
mapCodexPatchNotificationToToolCall,
|
|
1491
|
+
supportsPlanCollaborationMode,
|
|
1492
|
+
shouldRetryInitializeWithoutExperimentalApi,
|
|
1493
|
+
shouldRetryTurnStartWithoutCollaborationMode,
|
|
1494
|
+
formatProposedPlanBlock,
|
|
1495
|
+
normalizeCodexQuestionDescriptors,
|
|
1496
|
+
parseUpdatedQuestionAnswers,
|
|
1497
|
+
buildCodexPermissionsResponse,
|
|
1498
|
+
buildCodexElicitationResponse,
|
|
1499
|
+
supportedRequestMethods: [
|
|
1500
|
+
"item/commandExecution/requestApproval",
|
|
1501
|
+
"item/fileChange/requestApproval",
|
|
1502
|
+
"tool/requestUserInput",
|
|
1503
|
+
"item/tool/requestUserInput",
|
|
1504
|
+
"item/permissions/requestApproval",
|
|
1505
|
+
"mcpServer/elicitation/request",
|
|
1506
|
+
],
|
|
1306
1507
|
};
|
|
1307
1508
|
class CodexAppServerAgentSession {
|
|
1308
1509
|
constructor(config, resumeHandle, logger, spawnAppServer) {
|
|
@@ -1332,16 +1533,21 @@ class CodexAppServerAgentSession {
|
|
|
1332
1533
|
this.warnedInvalidNotificationPayloads = new Set();
|
|
1333
1534
|
this.warnedIncompleteEditToolCallIds = new Set();
|
|
1334
1535
|
this.connected = false;
|
|
1335
|
-
this.
|
|
1336
|
-
this.
|
|
1536
|
+
this.nativePlanModeSupported = null;
|
|
1537
|
+
this.pendingPlanTexts = new Map();
|
|
1337
1538
|
this.cachedSkills = [];
|
|
1338
1539
|
this.logger = logger.child({ module: "agent", provider: CODEX_PROVIDER });
|
|
1339
1540
|
if (config.modeId === undefined) {
|
|
1340
1541
|
throw new Error("Codex agent requires modeId to be specified");
|
|
1341
1542
|
}
|
|
1342
|
-
validateCodexMode(config.modeId);
|
|
1343
|
-
this.currentMode = config.modeId;
|
|
1344
1543
|
this.config = config;
|
|
1544
|
+
const normalizedModeId = normalizeCodexModeId(config.modeId);
|
|
1545
|
+
if (typeof normalizedModeId !== "string") {
|
|
1546
|
+
throw new Error("Codex agent requires a valid modeId");
|
|
1547
|
+
}
|
|
1548
|
+
validateCodexMode(normalizedModeId);
|
|
1549
|
+
this.currentMode = normalizedModeId;
|
|
1550
|
+
this.config.modeId = normalizedModeId;
|
|
1345
1551
|
this.config.thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
|
1346
1552
|
if (this.resumeHandle?.sessionId) {
|
|
1347
1553
|
this.currentThreadId = this.resumeHandle.sessionId;
|
|
@@ -1358,13 +1564,27 @@ class CodexAppServerAgentSession {
|
|
|
1358
1564
|
this.client = new CodexAppServerClient(child, this.logger);
|
|
1359
1565
|
this.client.setNotificationHandler((method, params) => this.handleNotification(method, params));
|
|
1360
1566
|
this.registerRequestHandlers();
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1567
|
+
const clientInfo = {
|
|
1568
|
+
name: "junction",
|
|
1569
|
+
title: "Junction",
|
|
1570
|
+
version: "0.0.0",
|
|
1571
|
+
};
|
|
1572
|
+
try {
|
|
1573
|
+
await this.client.request("initialize", {
|
|
1574
|
+
clientInfo,
|
|
1575
|
+
capabilities: {
|
|
1576
|
+
experimentalApi: true,
|
|
1577
|
+
},
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
catch (error) {
|
|
1581
|
+
if (!shouldRetryInitializeWithoutExperimentalApi(error)) {
|
|
1582
|
+
throw error;
|
|
1583
|
+
}
|
|
1584
|
+
await this.client.request("initialize", { clientInfo });
|
|
1585
|
+
this.nativePlanModeSupported = false;
|
|
1586
|
+
setCodexPlanModeEnabled(this.config, false);
|
|
1587
|
+
}
|
|
1368
1588
|
this.client.notify("initialized", {});
|
|
1369
1589
|
await this.loadCollaborationModes();
|
|
1370
1590
|
await this.loadSkills();
|
|
@@ -1377,22 +1597,22 @@ class CodexAppServerAgentSession {
|
|
|
1377
1597
|
async loadCollaborationModes() {
|
|
1378
1598
|
if (!this.client)
|
|
1379
1599
|
return;
|
|
1600
|
+
if (this.nativePlanModeSupported === false) {
|
|
1601
|
+
setCodexPlanModeEnabled(this.config, false);
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1380
1604
|
try {
|
|
1381
|
-
const response =
|
|
1382
|
-
|
|
1383
|
-
this.
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
model: entry.model ?? null,
|
|
1387
|
-
reasoning_effort: entry.reasoning_effort ?? null,
|
|
1388
|
-
developer_instructions: entry.developer_instructions ?? null,
|
|
1389
|
-
}));
|
|
1605
|
+
const response = await this.client.request("collaborationMode/list", {});
|
|
1606
|
+
this.nativePlanModeSupported = supportsPlanCollaborationMode(response);
|
|
1607
|
+
if (this.nativePlanModeSupported === false) {
|
|
1608
|
+
setCodexPlanModeEnabled(this.config, false);
|
|
1609
|
+
}
|
|
1390
1610
|
}
|
|
1391
1611
|
catch (error) {
|
|
1392
1612
|
this.logger.trace({ error }, "Failed to load collaboration modes");
|
|
1393
|
-
this.
|
|
1613
|
+
this.nativePlanModeSupported = false;
|
|
1614
|
+
setCodexPlanModeEnabled(this.config, false);
|
|
1394
1615
|
}
|
|
1395
|
-
this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
|
|
1396
1616
|
}
|
|
1397
1617
|
async loadSkills() {
|
|
1398
1618
|
if (!this.client)
|
|
@@ -1422,59 +1642,15 @@ class CodexAppServerAgentSession {
|
|
|
1422
1642
|
this.cachedSkills = [];
|
|
1423
1643
|
}
|
|
1424
1644
|
}
|
|
1425
|
-
resolveCollaborationMode(modeId) {
|
|
1426
|
-
if (this.collaborationModes.length === 0)
|
|
1427
|
-
return null;
|
|
1428
|
-
const normalized = modeId.toLowerCase();
|
|
1429
|
-
const findByName = (predicate) => this.collaborationModes.find((entry) => predicate(entry.name.toLowerCase()));
|
|
1430
|
-
let match;
|
|
1431
|
-
if (normalized === "read-only") {
|
|
1432
|
-
// Prefer explicit plan collaboration modes over generic read-only modes.
|
|
1433
|
-
match =
|
|
1434
|
-
findByName((name) => name.includes("plan")) ??
|
|
1435
|
-
findByName((name) => name.includes("read"));
|
|
1436
|
-
}
|
|
1437
|
-
else if (normalized === "full-access") {
|
|
1438
|
-
match = findByName((name) => name.includes("full") || name.includes("exec"));
|
|
1439
|
-
}
|
|
1440
|
-
else {
|
|
1441
|
-
match = findByName((name) => name.includes("auto") || name.includes("code"));
|
|
1442
|
-
}
|
|
1443
|
-
if (!match) {
|
|
1444
|
-
match = this.collaborationModes[0] ?? null;
|
|
1445
|
-
}
|
|
1446
|
-
if (!match)
|
|
1447
|
-
return null;
|
|
1448
|
-
const settings = {};
|
|
1449
|
-
if (match.model)
|
|
1450
|
-
settings.model = match.model;
|
|
1451
|
-
if (match.reasoning_effort)
|
|
1452
|
-
settings.reasoning_effort = match.reasoning_effort;
|
|
1453
|
-
const modeSpecificInstruction = normalized === "read-only"
|
|
1454
|
-
? "Plan mode is enabled. Do not execute commands, edit files, or perform write operations. Provide analysis and a step-by-step plan only."
|
|
1455
|
-
: "";
|
|
1456
|
-
const developerInstructions = [
|
|
1457
|
-
modeSpecificInstruction,
|
|
1458
|
-
match.developer_instructions?.trim(),
|
|
1459
|
-
this.config.systemPrompt?.trim(),
|
|
1460
|
-
]
|
|
1461
|
-
.filter((entry) => typeof entry === "string" && entry.length > 0)
|
|
1462
|
-
.join("\n\n");
|
|
1463
|
-
if (developerInstructions)
|
|
1464
|
-
settings.developer_instructions = developerInstructions;
|
|
1465
|
-
if (this.config.model)
|
|
1466
|
-
settings.model = this.config.model;
|
|
1467
|
-
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
|
1468
|
-
if (thinkingOptionId)
|
|
1469
|
-
settings.reasoning_effort = thinkingOptionId;
|
|
1470
|
-
return { mode: match.mode ?? "code", settings, name: match.name };
|
|
1471
|
-
}
|
|
1472
1645
|
registerRequestHandlers() {
|
|
1473
1646
|
if (!this.client)
|
|
1474
1647
|
return;
|
|
1475
|
-
this.client.setRequestHandler("item/commandExecution/requestApproval", (params) => this.handleCommandApprovalRequest(params));
|
|
1476
|
-
this.client.setRequestHandler("item/fileChange/requestApproval", (params) => this.handleFileChangeApprovalRequest(params));
|
|
1477
|
-
this.client.setRequestHandler("tool/requestUserInput", (params) => this.handleToolApprovalRequest(params));
|
|
1648
|
+
this.client.setRequestHandler("item/commandExecution/requestApproval", (params, requestId) => this.handleCommandApprovalRequest("item/commandExecution/requestApproval", params, requestId));
|
|
1649
|
+
this.client.setRequestHandler("item/fileChange/requestApproval", (params, requestId) => this.handleFileChangeApprovalRequest("item/fileChange/requestApproval", params, requestId));
|
|
1650
|
+
this.client.setRequestHandler("tool/requestUserInput", (params, requestId) => this.handleToolApprovalRequest("tool/requestUserInput", params, requestId));
|
|
1651
|
+
this.client.setRequestHandler("item/tool/requestUserInput", (params, requestId) => this.handleToolApprovalRequest("item/tool/requestUserInput", params, requestId));
|
|
1652
|
+
this.client.setRequestHandler("item/permissions/requestApproval", (params, requestId) => this.handlePermissionsApprovalRequest("item/permissions/requestApproval", params, requestId));
|
|
1653
|
+
this.client.setRequestHandler("mcpServer/elicitation/request", (params, requestId) => this.handleMcpElicitationRequest("mcpServer/elicitation/request", params, requestId));
|
|
1478
1654
|
}
|
|
1479
1655
|
async loadPersistedHistory() {
|
|
1480
1656
|
if (!this.client || !this.currentThreadId)
|
|
@@ -1672,6 +1848,11 @@ class CodexAppServerAgentSession {
|
|
|
1672
1848
|
const preset = MODE_PRESETS[this.currentMode] ?? MODE_PRESETS[DEFAULT_CODEX_MODE_ID];
|
|
1673
1849
|
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
|
|
1674
1850
|
const sandboxPolicyType = this.config.sandboxMode ?? preset.sandbox;
|
|
1851
|
+
const explicitPlanMode = options?.extra?.codex?.planMode;
|
|
1852
|
+
const planModeRequested = explicitPlanMode === undefined
|
|
1853
|
+
? isCodexPlanModeEnabled(this.config)
|
|
1854
|
+
: explicitPlanMode === true;
|
|
1855
|
+
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
|
1675
1856
|
const params = {
|
|
1676
1857
|
threadId: this.currentThreadId,
|
|
1677
1858
|
input,
|
|
@@ -1683,16 +1864,9 @@ class CodexAppServerAgentSession {
|
|
|
1683
1864
|
if (this.config.model) {
|
|
1684
1865
|
params.model = this.config.model;
|
|
1685
1866
|
}
|
|
1686
|
-
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
|
1687
1867
|
if (thinkingOptionId) {
|
|
1688
1868
|
params.effort = thinkingOptionId;
|
|
1689
1869
|
}
|
|
1690
|
-
if (this.resolvedCollaborationMode) {
|
|
1691
|
-
params.collaborationMode = {
|
|
1692
|
-
mode: this.resolvedCollaborationMode.mode,
|
|
1693
|
-
settings: this.resolvedCollaborationMode.settings,
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
1870
|
if (this.config.cwd) {
|
|
1697
1871
|
params.cwd = this.config.cwd;
|
|
1698
1872
|
}
|
|
@@ -1706,7 +1880,39 @@ class CodexAppServerAgentSession {
|
|
|
1706
1880
|
if (codexConfig) {
|
|
1707
1881
|
params.config = codexConfig;
|
|
1708
1882
|
}
|
|
1709
|
-
|
|
1883
|
+
let downgradedFromPlanMode = false;
|
|
1884
|
+
if (this.nativePlanModeSupported !== false) {
|
|
1885
|
+
const collaborationMode = this.buildCollaborationModePayload(planModeRequested ? "plan" : "default");
|
|
1886
|
+
if (collaborationMode) {
|
|
1887
|
+
params.collaborationMode = collaborationMode;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
try {
|
|
1891
|
+
await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
|
|
1892
|
+
}
|
|
1893
|
+
catch (error) {
|
|
1894
|
+
const canRetryWithoutPlanMode = Object.prototype.hasOwnProperty.call(params, "collaborationMode") &&
|
|
1895
|
+
shouldRetryTurnStartWithoutCollaborationMode(error);
|
|
1896
|
+
if (!canRetryWithoutPlanMode) {
|
|
1897
|
+
throw error;
|
|
1898
|
+
}
|
|
1899
|
+
delete params.collaborationMode;
|
|
1900
|
+
this.nativePlanModeSupported = false;
|
|
1901
|
+
setCodexPlanModeEnabled(this.config, false);
|
|
1902
|
+
this.cachedRuntimeInfo = null;
|
|
1903
|
+
downgradedFromPlanMode = planModeRequested;
|
|
1904
|
+
await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
|
|
1905
|
+
}
|
|
1906
|
+
if (downgradedFromPlanMode) {
|
|
1907
|
+
this.emitEvent({
|
|
1908
|
+
type: "timeline",
|
|
1909
|
+
provider: CODEX_PROVIDER,
|
|
1910
|
+
item: {
|
|
1911
|
+
type: "assistant_message",
|
|
1912
|
+
text: "Plan mode is not supported by this Codex runtime. Sent as a normal turn instead.",
|
|
1913
|
+
},
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1710
1916
|
let sawTurnStarted = false;
|
|
1711
1917
|
for await (const event of queue) {
|
|
1712
1918
|
// Drop pre-start timeline noise that can leak from the previous turn.
|
|
@@ -1763,9 +1969,10 @@ class CodexAppServerAgentSession {
|
|
|
1763
1969
|
model: this.config.model ?? null,
|
|
1764
1970
|
thinkingOptionId: normalizeCodexThinkingOptionId(this.config.thinkingOptionId) ?? null,
|
|
1765
1971
|
modeId: this.currentMode ?? null,
|
|
1766
|
-
extra:
|
|
1767
|
-
|
|
1768
|
-
:
|
|
1972
|
+
extra: buildCodexRuntimeExtra({
|
|
1973
|
+
planModeSupported: this.nativePlanModeSupported,
|
|
1974
|
+
planModeEnabled: isCodexPlanModeEnabled(this.config),
|
|
1975
|
+
}),
|
|
1769
1976
|
};
|
|
1770
1977
|
this.cachedRuntimeInfo = info;
|
|
1771
1978
|
return { ...info };
|
|
@@ -1777,19 +1984,21 @@ class CodexAppServerAgentSession {
|
|
|
1777
1984
|
return this.currentMode ?? null;
|
|
1778
1985
|
}
|
|
1779
1986
|
async setMode(modeId) {
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1987
|
+
const normalizedModeId = normalizeCodexModeId(modeId);
|
|
1988
|
+
if (typeof normalizedModeId !== "string") {
|
|
1989
|
+
throw new Error(`Invalid Codex mode "${modeId}"`);
|
|
1990
|
+
}
|
|
1991
|
+
validateCodexMode(normalizedModeId);
|
|
1992
|
+
this.currentMode = normalizedModeId;
|
|
1993
|
+
this.config.modeId = normalizedModeId;
|
|
1783
1994
|
this.cachedRuntimeInfo = null;
|
|
1784
1995
|
}
|
|
1785
1996
|
async setModel(modelId) {
|
|
1786
1997
|
this.config.model = modelId ?? undefined;
|
|
1787
|
-
this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
|
|
1788
1998
|
this.cachedRuntimeInfo = null;
|
|
1789
1999
|
}
|
|
1790
2000
|
async setThinkingOption(thinkingOptionId) {
|
|
1791
2001
|
this.config.thinkingOptionId = normalizeCodexThinkingOptionId(thinkingOptionId);
|
|
1792
|
-
this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
|
|
1793
2002
|
this.cachedRuntimeInfo = null;
|
|
1794
2003
|
}
|
|
1795
2004
|
getPendingPermissions() {
|
|
@@ -1838,23 +2047,33 @@ class CodexAppServerAgentSession {
|
|
|
1838
2047
|
resolution: response,
|
|
1839
2048
|
});
|
|
1840
2049
|
if (pending.kind === "command") {
|
|
1841
|
-
const decision = response.
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2050
|
+
const decision = extractExplicitDecision(response.updatedInput) ??
|
|
2051
|
+
(response.behavior === "allow"
|
|
2052
|
+
? "accept"
|
|
2053
|
+
: response.interrupt
|
|
2054
|
+
? "cancel"
|
|
2055
|
+
: "decline");
|
|
1846
2056
|
pending.resolve({ decision });
|
|
1847
2057
|
return;
|
|
1848
2058
|
}
|
|
1849
2059
|
if (pending.kind === "file") {
|
|
1850
|
-
const decision = response.
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2060
|
+
const decision = extractExplicitDecision(response.updatedInput) ??
|
|
2061
|
+
(response.behavior === "allow"
|
|
2062
|
+
? "accept"
|
|
2063
|
+
: response.interrupt
|
|
2064
|
+
? "cancel"
|
|
2065
|
+
: "decline");
|
|
1855
2066
|
pending.resolve({ decision });
|
|
1856
2067
|
return;
|
|
1857
2068
|
}
|
|
2069
|
+
if (pending.kind === "permissions") {
|
|
2070
|
+
pending.resolve(buildCodexPermissionsResponse(response, pending.permissions));
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
if (pending.kind === "elicitation") {
|
|
2074
|
+
pending.resolve(buildCodexElicitationResponse(response));
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
1858
2077
|
// tool/requestUserInput
|
|
1859
2078
|
const answers = {};
|
|
1860
2079
|
const questions = pending.questions ?? [];
|
|
@@ -2015,10 +2234,29 @@ class CodexAppServerAgentSession {
|
|
|
2015
2234
|
innerConfig.mcp_servers = mcpServers;
|
|
2016
2235
|
}
|
|
2017
2236
|
if (this.config.extra?.codex) {
|
|
2018
|
-
|
|
2237
|
+
const { planModeEnabled: _planModeEnabled, ...codexExtra } = this.config.extra.codex;
|
|
2238
|
+
Object.assign(innerConfig, codexExtra);
|
|
2019
2239
|
}
|
|
2020
2240
|
return Object.keys(innerConfig).length > 0 ? innerConfig : null;
|
|
2021
2241
|
}
|
|
2242
|
+
buildCollaborationModePayload(mode) {
|
|
2243
|
+
if (this.nativePlanModeSupported !== true) {
|
|
2244
|
+
return null;
|
|
2245
|
+
}
|
|
2246
|
+
const model = normalizeCodexModelId(this.config.model);
|
|
2247
|
+
if (!model) {
|
|
2248
|
+
return null;
|
|
2249
|
+
}
|
|
2250
|
+
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId) ?? null;
|
|
2251
|
+
return {
|
|
2252
|
+
mode,
|
|
2253
|
+
settings: {
|
|
2254
|
+
model,
|
|
2255
|
+
reasoning_effort: thinkingOptionId,
|
|
2256
|
+
developer_instructions: null,
|
|
2257
|
+
},
|
|
2258
|
+
};
|
|
2259
|
+
}
|
|
2022
2260
|
async buildUserInput(prompt) {
|
|
2023
2261
|
if (typeof prompt === "string") {
|
|
2024
2262
|
return [{ type: "text", text: prompt }];
|
|
@@ -2073,6 +2311,7 @@ class CodexAppServerAgentSession {
|
|
|
2073
2311
|
this.emittedExecCommandCompletedCallIds.clear();
|
|
2074
2312
|
this.pendingCommandOutputDeltas.clear();
|
|
2075
2313
|
this.pendingFileChangeOutputDeltas.clear();
|
|
2314
|
+
this.pendingPlanTexts.clear();
|
|
2076
2315
|
this.warnedIncompleteEditToolCallIds.clear();
|
|
2077
2316
|
this.eventQueue?.end();
|
|
2078
2317
|
return;
|
|
@@ -2089,6 +2328,22 @@ class CodexAppServerAgentSession {
|
|
|
2089
2328
|
});
|
|
2090
2329
|
return;
|
|
2091
2330
|
}
|
|
2331
|
+
if (parsed.kind === "plan_delta") {
|
|
2332
|
+
const previous = this.pendingPlanTexts.get(parsed.itemId) ?? "";
|
|
2333
|
+
const next = previous + parsed.delta;
|
|
2334
|
+
this.pendingPlanTexts.set(parsed.itemId, next);
|
|
2335
|
+
this.emitEvent({
|
|
2336
|
+
type: "timeline",
|
|
2337
|
+
provider: CODEX_PROVIDER,
|
|
2338
|
+
item: {
|
|
2339
|
+
type: "assistant_message",
|
|
2340
|
+
text: formatProposedPlanChunk(parsed.delta, {
|
|
2341
|
+
open: previous.length === 0,
|
|
2342
|
+
}),
|
|
2343
|
+
},
|
|
2344
|
+
});
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2092
2347
|
if (parsed.kind === "diff_updated") {
|
|
2093
2348
|
// NOTE: Codex app-server emits frequent `turn/diff/updated` notifications
|
|
2094
2349
|
// containing a full accumulated unified diff for the *entire turn*.
|
|
@@ -2096,6 +2351,20 @@ class CodexAppServerAgentSession {
|
|
|
2096
2351
|
// We intentionally do NOT store every diff update in the timeline.
|
|
2097
2352
|
return;
|
|
2098
2353
|
}
|
|
2354
|
+
if (parsed.kind === "server_request_resolved") {
|
|
2355
|
+
const pendingRequestId = toPendingPermissionId(parsed.requestId);
|
|
2356
|
+
if (this.resolvedPermissionRequests.has(pendingRequestId)) {
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
this.pendingPermissions.delete(pendingRequestId);
|
|
2360
|
+
this.emitEvent({
|
|
2361
|
+
type: "permission_resolved",
|
|
2362
|
+
provider: CODEX_PROVIDER,
|
|
2363
|
+
requestId: pendingRequestId,
|
|
2364
|
+
resolution: { behavior: "allow" },
|
|
2365
|
+
});
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2099
2368
|
if (parsed.kind === "token_usage_updated") {
|
|
2100
2369
|
this.latestUsage = toAgentUsage(parsed.tokenUsage);
|
|
2101
2370
|
return;
|
|
@@ -2225,6 +2494,24 @@ class CodexAppServerAgentSession {
|
|
|
2225
2494
|
timelineItem.text = buffered;
|
|
2226
2495
|
}
|
|
2227
2496
|
}
|
|
2497
|
+
if (timelineItem.type === "assistant_message" &&
|
|
2498
|
+
normalizedItemType === "plan" &&
|
|
2499
|
+
itemId) {
|
|
2500
|
+
const bufferedPlanText = this.pendingPlanTexts.get(itemId) ?? "";
|
|
2501
|
+
const finalPlanText = typeof parsed.item.text === "string" ? parsed.item.text : "";
|
|
2502
|
+
if (bufferedPlanText.length > 0) {
|
|
2503
|
+
const trailingText = finalPlanText.startsWith(bufferedPlanText)
|
|
2504
|
+
? finalPlanText.slice(bufferedPlanText.length)
|
|
2505
|
+
: "";
|
|
2506
|
+
timelineItem.text = formatProposedPlanChunk(trailingText, {
|
|
2507
|
+
close: true,
|
|
2508
|
+
});
|
|
2509
|
+
this.pendingPlanTexts.delete(itemId);
|
|
2510
|
+
}
|
|
2511
|
+
else if (finalPlanText.trim().length > 0) {
|
|
2512
|
+
timelineItem.text = formatProposedPlanBlock(finalPlanText);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2228
2515
|
if (timelineItem.type === "reasoning" && itemId) {
|
|
2229
2516
|
const buffered = this.pendingReasoning.get(itemId);
|
|
2230
2517
|
if (buffered && buffered.length > 0) {
|
|
@@ -2240,6 +2527,7 @@ class CodexAppServerAgentSession {
|
|
|
2240
2527
|
this.emittedItemStartedIds.delete(itemId);
|
|
2241
2528
|
this.pendingCommandOutputDeltas.delete(itemId);
|
|
2242
2529
|
this.pendingFileChangeOutputDeltas.delete(itemId);
|
|
2530
|
+
this.pendingPlanTexts.delete(itemId);
|
|
2243
2531
|
}
|
|
2244
2532
|
}
|
|
2245
2533
|
return;
|
|
@@ -2336,7 +2624,7 @@ class CodexAppServerAgentSession {
|
|
|
2336
2624
|
payload,
|
|
2337
2625
|
}, "Codex edit tool call is missing diff/content fields");
|
|
2338
2626
|
}
|
|
2339
|
-
handleCommandApprovalRequest(params) {
|
|
2627
|
+
handleCommandApprovalRequest(requestMethod, params, rawRequestId) {
|
|
2340
2628
|
const parsed = params;
|
|
2341
2629
|
const commandPreview = mapCodexExecNotificationToToolCall({
|
|
2342
2630
|
callId: parsed.itemId,
|
|
@@ -2344,8 +2632,12 @@ class CodexAppServerAgentSession {
|
|
|
2344
2632
|
cwd: parsed.cwd ?? this.config.cwd ?? null,
|
|
2345
2633
|
running: true,
|
|
2346
2634
|
});
|
|
2347
|
-
const requestId =
|
|
2348
|
-
const title = parsed.command
|
|
2635
|
+
const requestId = toPendingPermissionId(rawRequestId ?? parsed.approvalId ?? parsed.itemId);
|
|
2636
|
+
const title = parsed.command
|
|
2637
|
+
? `Run command: ${parsed.command}`
|
|
2638
|
+
: parsed.networkApprovalContext
|
|
2639
|
+
? "Allow network access"
|
|
2640
|
+
: "Run command";
|
|
2349
2641
|
const request = {
|
|
2350
2642
|
id: requestId,
|
|
2351
2643
|
provider: CODEX_PROVIDER,
|
|
@@ -2366,9 +2658,18 @@ class CodexAppServerAgentSession {
|
|
|
2366
2658
|
output: null,
|
|
2367
2659
|
},
|
|
2368
2660
|
metadata: {
|
|
2661
|
+
requestMethod,
|
|
2369
2662
|
itemId: parsed.itemId,
|
|
2663
|
+
approvalId: parsed.approvalId ?? null,
|
|
2370
2664
|
threadId: parsed.threadId,
|
|
2371
2665
|
turnId: parsed.turnId,
|
|
2666
|
+
commandActions: parsed.commandActions ?? null,
|
|
2667
|
+
availableDecisions: parsed.availableDecisions ?? null,
|
|
2668
|
+
additionalPermissions: parsed.additionalPermissions ?? null,
|
|
2669
|
+
proposedExecpolicyAmendment: parsed.proposedExecpolicyAmendment ?? null,
|
|
2670
|
+
proposedNetworkPolicyAmendments: parsed.proposedNetworkPolicyAmendments ?? null,
|
|
2671
|
+
networkApprovalContext: parsed.networkApprovalContext ?? null,
|
|
2672
|
+
skillMetadata: parsed.skillMetadata ?? null,
|
|
2372
2673
|
},
|
|
2373
2674
|
};
|
|
2374
2675
|
this.pendingPermissions.set(requestId, request);
|
|
@@ -2377,9 +2678,9 @@ class CodexAppServerAgentSession {
|
|
|
2377
2678
|
this.pendingPermissionHandlers.set(requestId, { resolve, kind: "command" });
|
|
2378
2679
|
});
|
|
2379
2680
|
}
|
|
2380
|
-
handleFileChangeApprovalRequest(params) {
|
|
2681
|
+
handleFileChangeApprovalRequest(requestMethod, params, rawRequestId) {
|
|
2381
2682
|
const parsed = params;
|
|
2382
|
-
const requestId =
|
|
2683
|
+
const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
|
|
2383
2684
|
const request = {
|
|
2384
2685
|
id: requestId,
|
|
2385
2686
|
provider: CODEX_PROVIDER,
|
|
@@ -2391,13 +2692,16 @@ class CodexAppServerAgentSession {
|
|
|
2391
2692
|
type: "unknown",
|
|
2392
2693
|
input: {
|
|
2393
2694
|
reason: parsed.reason ?? null,
|
|
2695
|
+
grantRoot: parsed.grantRoot ?? null,
|
|
2394
2696
|
},
|
|
2395
2697
|
output: null,
|
|
2396
2698
|
},
|
|
2397
2699
|
metadata: {
|
|
2700
|
+
requestMethod,
|
|
2398
2701
|
itemId: parsed.itemId,
|
|
2399
2702
|
threadId: parsed.threadId,
|
|
2400
2703
|
turnId: parsed.turnId,
|
|
2704
|
+
grantRoot: parsed.grantRoot ?? null,
|
|
2401
2705
|
},
|
|
2402
2706
|
};
|
|
2403
2707
|
this.pendingPermissions.set(requestId, request);
|
|
@@ -2406,28 +2710,132 @@ class CodexAppServerAgentSession {
|
|
|
2406
2710
|
this.pendingPermissionHandlers.set(requestId, { resolve, kind: "file" });
|
|
2407
2711
|
});
|
|
2408
2712
|
}
|
|
2409
|
-
handleToolApprovalRequest(params) {
|
|
2713
|
+
handleToolApprovalRequest(requestMethod, params, rawRequestId) {
|
|
2410
2714
|
const parsed = params;
|
|
2411
|
-
const requestId =
|
|
2715
|
+
const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
|
|
2716
|
+
const questions = normalizeCodexQuestionDescriptors(parsed.questions);
|
|
2412
2717
|
const request = {
|
|
2413
2718
|
id: requestId,
|
|
2414
2719
|
provider: CODEX_PROVIDER,
|
|
2415
|
-
name: "
|
|
2416
|
-
kind: "
|
|
2417
|
-
title: "
|
|
2720
|
+
name: "CodexQuestion",
|
|
2721
|
+
kind: "question",
|
|
2722
|
+
title: "Answer question",
|
|
2418
2723
|
description: undefined,
|
|
2724
|
+
input: {
|
|
2725
|
+
questions,
|
|
2726
|
+
},
|
|
2419
2727
|
detail: {
|
|
2420
2728
|
type: "unknown",
|
|
2421
2729
|
input: {
|
|
2422
|
-
questions
|
|
2730
|
+
questions,
|
|
2423
2731
|
},
|
|
2424
2732
|
output: null,
|
|
2425
2733
|
},
|
|
2426
2734
|
metadata: {
|
|
2735
|
+
requestMethod,
|
|
2427
2736
|
itemId: parsed.itemId,
|
|
2428
2737
|
threadId: parsed.threadId,
|
|
2429
2738
|
turnId: parsed.turnId,
|
|
2430
|
-
questions
|
|
2739
|
+
questions,
|
|
2740
|
+
},
|
|
2741
|
+
};
|
|
2742
|
+
this.pendingPermissions.set(requestId, request);
|
|
2743
|
+
this.emitEvent({ type: "permission_requested", provider: CODEX_PROVIDER, request });
|
|
2744
|
+
return new Promise((resolve) => {
|
|
2745
|
+
this.pendingPermissionHandlers.set(requestId, {
|
|
2746
|
+
resolve,
|
|
2747
|
+
kind: "question",
|
|
2748
|
+
questions,
|
|
2749
|
+
});
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
handlePermissionsApprovalRequest(requestMethod, params, rawRequestId) {
|
|
2753
|
+
const parsed = params;
|
|
2754
|
+
const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
|
|
2755
|
+
const request = {
|
|
2756
|
+
id: requestId,
|
|
2757
|
+
provider: CODEX_PROVIDER,
|
|
2758
|
+
name: "CodexPermissionProfile",
|
|
2759
|
+
kind: "tool",
|
|
2760
|
+
title: "Grant additional permissions",
|
|
2761
|
+
description: parsed.reason ?? undefined,
|
|
2762
|
+
input: {
|
|
2763
|
+
permissions: parsed.permissions ?? null,
|
|
2764
|
+
},
|
|
2765
|
+
detail: {
|
|
2766
|
+
type: "unknown",
|
|
2767
|
+
input: {
|
|
2768
|
+
reason: parsed.reason ?? null,
|
|
2769
|
+
permissions: parsed.permissions ?? null,
|
|
2770
|
+
},
|
|
2771
|
+
output: null,
|
|
2772
|
+
},
|
|
2773
|
+
metadata: {
|
|
2774
|
+
requestMethod,
|
|
2775
|
+
itemId: parsed.itemId,
|
|
2776
|
+
threadId: parsed.threadId,
|
|
2777
|
+
turnId: parsed.turnId,
|
|
2778
|
+
permissions: parsed.permissions ?? null,
|
|
2779
|
+
},
|
|
2780
|
+
};
|
|
2781
|
+
this.pendingPermissions.set(requestId, request);
|
|
2782
|
+
this.emitEvent({ type: "permission_requested", provider: CODEX_PROVIDER, request });
|
|
2783
|
+
return new Promise((resolve) => {
|
|
2784
|
+
this.pendingPermissionHandlers.set(requestId, {
|
|
2785
|
+
resolve,
|
|
2786
|
+
kind: "permissions",
|
|
2787
|
+
permissions: parsed.permissions ?? null,
|
|
2788
|
+
});
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
handleMcpElicitationRequest(requestMethod, params, rawRequestId) {
|
|
2792
|
+
const parsed = params;
|
|
2793
|
+
const baseRequestId = rawRequestId ??
|
|
2794
|
+
parsed.elicitationId ??
|
|
2795
|
+
`${parsed.threadId}:${parsed.turnId ?? "none"}:${parsed.serverName ?? "mcp"}`;
|
|
2796
|
+
const requestId = toPendingPermissionId(baseRequestId);
|
|
2797
|
+
const title = parsed.mode === "url"
|
|
2798
|
+
? "Open MCP authorization link"
|
|
2799
|
+
: parsed.serverName
|
|
2800
|
+
? `Respond to ${parsed.serverName}`
|
|
2801
|
+
: "Respond to MCP server";
|
|
2802
|
+
const request = {
|
|
2803
|
+
id: requestId,
|
|
2804
|
+
provider: CODEX_PROVIDER,
|
|
2805
|
+
name: "CodexMcpElicitation",
|
|
2806
|
+
kind: "other",
|
|
2807
|
+
title,
|
|
2808
|
+
description: parsed.message ?? undefined,
|
|
2809
|
+
input: {
|
|
2810
|
+
mode: parsed.mode ?? null,
|
|
2811
|
+
requestedSchema: parsed.requestedSchema ?? null,
|
|
2812
|
+
url: parsed.url ?? null,
|
|
2813
|
+
},
|
|
2814
|
+
detail: {
|
|
2815
|
+
type: "unknown",
|
|
2816
|
+
input: {
|
|
2817
|
+
requestMethod,
|
|
2818
|
+
serverName: parsed.serverName ?? null,
|
|
2819
|
+
mode: parsed.mode ?? null,
|
|
2820
|
+
message: parsed.message ?? null,
|
|
2821
|
+
requestedSchema: parsed.requestedSchema ?? null,
|
|
2822
|
+
url: parsed.url ?? null,
|
|
2823
|
+
elicitationId: parsed.elicitationId ?? null,
|
|
2824
|
+
_meta: parsed._meta ?? null,
|
|
2825
|
+
},
|
|
2826
|
+
output: null,
|
|
2827
|
+
},
|
|
2828
|
+
metadata: {
|
|
2829
|
+
requestMethod,
|
|
2830
|
+
threadId: parsed.threadId,
|
|
2831
|
+
turnId: parsed.turnId ?? null,
|
|
2832
|
+
serverName: parsed.serverName ?? null,
|
|
2833
|
+
mode: parsed.mode ?? null,
|
|
2834
|
+
message: parsed.message ?? null,
|
|
2835
|
+
requestedSchema: parsed.requestedSchema ?? null,
|
|
2836
|
+
url: parsed.url ?? null,
|
|
2837
|
+
elicitationId: parsed.elicitationId ?? null,
|
|
2838
|
+
_meta: parsed._meta ?? null,
|
|
2431
2839
|
},
|
|
2432
2840
|
};
|
|
2433
2841
|
this.pendingPermissions.set(requestId, request);
|
|
@@ -2435,8 +2843,7 @@ class CodexAppServerAgentSession {
|
|
|
2435
2843
|
return new Promise((resolve) => {
|
|
2436
2844
|
this.pendingPermissionHandlers.set(requestId, {
|
|
2437
2845
|
resolve,
|
|
2438
|
-
kind: "
|
|
2439
|
-
questions: Array.isArray(parsed.questions) ? parsed.questions : [],
|
|
2846
|
+
kind: "elicitation",
|
|
2440
2847
|
});
|
|
2441
2848
|
});
|
|
2442
2849
|
}
|
|
@@ -2616,7 +3023,7 @@ export class CodexAppServerAgentClient {
|
|
|
2616
3023
|
}
|
|
2617
3024
|
}
|
|
2618
3025
|
async isAvailable() {
|
|
2619
|
-
return isProviderCommandAvailable(this.runtimeSettings?.command, resolveCodexBinary);
|
|
3026
|
+
return isProviderCommandAvailable(this.runtimeSettings?.command, resolveCodexBinary, applyProviderEnv(process.env, this.runtimeSettings));
|
|
2620
3027
|
}
|
|
2621
3028
|
}
|
|
2622
3029
|
//# sourceMappingURL=codex-app-server-agent.js.map
|