@junctionpanel/server 0.1.28 → 0.1.31
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 +42 -5
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +85 -3
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +7 -0
- 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/providers/codex-app-server-agent.d.ts +8 -0
- 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 +244 -135
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts +4 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.js +36 -8
- package/dist/server/server/agent/providers/gemini-agent.js.map +1 -1
- package/dist/server/server/agent/providers/image-attachments.d.ts +8 -0
- package/dist/server/server/agent/providers/image-attachments.d.ts.map +1 -0
- package/dist/server/server/agent/providers/image-attachments.js +47 -0
- package/dist/server/server/agent/providers/image-attachments.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +3 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/daemon-doctor.d.ts +39 -0
- package/dist/server/server/daemon-doctor.d.ts.map +1 -0
- package/dist/server/server/daemon-doctor.js +260 -0
- package/dist/server/server/daemon-doctor.js.map +1 -0
- package/dist/server/server/daemon-provider-settings.d.ts +42 -0
- package/dist/server/server/daemon-provider-settings.d.ts.map +1 -0
- package/dist/server/server/daemon-provider-settings.js +207 -0
- package/dist/server/server/daemon-provider-settings.js.map +1 -0
- package/dist/server/server/file-explorer/service.d.ts +4 -2
- package/dist/server/server/file-explorer/service.d.ts.map +1 -1
- package/dist/server/server/file-explorer/service.js +104 -2
- package/dist/server/server/file-explorer/service.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +24 -24
- package/dist/server/server/session.d.ts +10 -1
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +439 -62
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/worktree-bootstrap.d.ts +1 -0
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
- package/dist/server/server/worktree-bootstrap.js +4 -0
- package/dist/server/server/worktree-bootstrap.js.map +1 -1
- package/dist/server/shared/messages.d.ts +4245 -34
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +167 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +23 -4
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +298 -79
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/directory-suggestions.d.ts +4 -0
- package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
- package/dist/server/utils/directory-suggestions.js +83 -5
- package/dist/server/utils/directory-suggestions.js.map +1 -1
- package/dist/server/utils/workspace-ref-files.d.ts +31 -0
- package/dist/server/utils/workspace-ref-files.d.ts.map +1 -0
- package/dist/server/utils/workspace-ref-files.js +207 -0
- package/dist/server/utils/workspace-ref-files.js.map +1 -0
- package/dist/server/utils/worktree.d.ts +6 -3
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +46 -45
- package/dist/server/utils/worktree.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { execSync, spawn } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
@@ -8,10 +7,10 @@ import { z } from "zod";
|
|
|
8
7
|
import { loadCodexPersistedTimeline } from "./codex-rollout-timeline.js";
|
|
9
8
|
import { mapCodexRolloutToolCall, mapCodexToolCallFromThreadItem, } from "./codex/tool-call-mapper.js";
|
|
10
9
|
import { applyProviderEnv, isProviderCommandAvailable, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
|
|
10
|
+
import { writeImageAttachment } from "./image-attachments.js";
|
|
11
11
|
const DEFAULT_TIMEOUT_MS = 14 * 24 * 60 * 60 * 1000;
|
|
12
12
|
const TURN_START_TIMEOUT_MS = 90 * 1000;
|
|
13
13
|
const CODEX_PROVIDER = "codex";
|
|
14
|
-
const CODEX_IMAGE_ATTACHMENT_DIR = "junction-attachments";
|
|
15
14
|
const CODEX_APP_SERVER_CAPABILITIES = {
|
|
16
15
|
supportsStreaming: true,
|
|
17
16
|
supportsSessionPersistence: true,
|
|
@@ -219,6 +218,9 @@ function parseUpdatedQuestionAnswers(updatedInput) {
|
|
|
219
218
|
}
|
|
220
219
|
return parsed;
|
|
221
220
|
}
|
|
221
|
+
function toPendingPermissionId(requestId) {
|
|
222
|
+
return `permission-${String(requestId)}`;
|
|
223
|
+
}
|
|
222
224
|
async function listCodexCustomPrompts() {
|
|
223
225
|
const codexHome = resolveCodexHomeDir();
|
|
224
226
|
const promptsDir = path.join(codexHome, "prompts");
|
|
@@ -525,7 +527,7 @@ class CodexAppServerClient {
|
|
|
525
527
|
const request = msg;
|
|
526
528
|
const handler = this.requestHandlers.get(request.method);
|
|
527
529
|
try {
|
|
528
|
-
const result = handler ? await handler(request.params) : {};
|
|
530
|
+
const result = handler ? await handler(request.params, request.id) : {};
|
|
529
531
|
const response = { id: request.id, result };
|
|
530
532
|
this.child.stdin.write(`${JSON.stringify(response)}\n`);
|
|
531
533
|
}
|
|
@@ -582,12 +584,73 @@ function parsePlanTextToTodoItems(text) {
|
|
|
582
584
|
completed: false,
|
|
583
585
|
}));
|
|
584
586
|
}
|
|
587
|
+
function formatProposedPlanBlock(text) {
|
|
588
|
+
return `<proposed_plan>\n${text}\n</proposed_plan>`;
|
|
589
|
+
}
|
|
590
|
+
function formatProposedPlanChunk(text, options) {
|
|
591
|
+
const parts = [];
|
|
592
|
+
if (options?.open) {
|
|
593
|
+
parts.push("<proposed_plan>\n");
|
|
594
|
+
}
|
|
595
|
+
parts.push(text);
|
|
596
|
+
if (options?.close) {
|
|
597
|
+
parts.push("\n</proposed_plan>");
|
|
598
|
+
}
|
|
599
|
+
return parts.join("");
|
|
600
|
+
}
|
|
585
601
|
function planStepsToTodoItems(steps) {
|
|
586
602
|
return steps.map((entry) => ({
|
|
587
603
|
text: entry.step,
|
|
588
604
|
completed: entry.status === "completed",
|
|
589
605
|
}));
|
|
590
606
|
}
|
|
607
|
+
function supportsPlanCollaborationMode(response) {
|
|
608
|
+
const candidateArrays = [
|
|
609
|
+
Array.isArray(response) ? response : null,
|
|
610
|
+
Array.isArray(response?.data)
|
|
611
|
+
? (response.data)
|
|
612
|
+
: null,
|
|
613
|
+
Array.isArray(response?.modes)
|
|
614
|
+
? (response.modes)
|
|
615
|
+
: null,
|
|
616
|
+
Array.isArray(response?.collaborationModes)
|
|
617
|
+
? (response.collaborationModes)
|
|
618
|
+
: null,
|
|
619
|
+
Array.isArray(response?.items)
|
|
620
|
+
? (response.items)
|
|
621
|
+
: null,
|
|
622
|
+
];
|
|
623
|
+
for (const entries of candidateArrays) {
|
|
624
|
+
if (!entries)
|
|
625
|
+
continue;
|
|
626
|
+
for (const entry of entries) {
|
|
627
|
+
const record = toRecord(entry);
|
|
628
|
+
const modeName = (typeof record?.mode === "string" ? record.mode : null) ??
|
|
629
|
+
(typeof record?.name === "string" ? record.name : null) ??
|
|
630
|
+
(typeof record?.id === "string" ? record.id : null) ??
|
|
631
|
+
(typeof entry === "string" ? entry : null);
|
|
632
|
+
if (modeName?.trim().toLowerCase() === "plan") {
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
function shouldRetryInitializeWithoutExperimentalApi(error) {
|
|
640
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
641
|
+
return (message.includes("experimentalapi") ||
|
|
642
|
+
message.includes("experimental api") ||
|
|
643
|
+
message.includes("capabilities") ||
|
|
644
|
+
message.includes("unknown field") ||
|
|
645
|
+
message.includes("invalid params"));
|
|
646
|
+
}
|
|
647
|
+
function shouldRetryTurnStartWithoutCollaborationMode(error) {
|
|
648
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
649
|
+
return (message.includes("collaborationmode") ||
|
|
650
|
+
message.includes("collaboration mode") ||
|
|
651
|
+
message.includes("experimentalapi") ||
|
|
652
|
+
message.includes("experimental api"));
|
|
653
|
+
}
|
|
591
654
|
function extractPatchLikeText(value) {
|
|
592
655
|
if (!value || typeof value !== "object") {
|
|
593
656
|
return undefined;
|
|
@@ -897,8 +960,11 @@ function threadItemToTimeline(item, options) {
|
|
|
897
960
|
}
|
|
898
961
|
case "plan": {
|
|
899
962
|
const text = normalizedItem.text ?? "";
|
|
963
|
+
if (typeof text === "string" && text.trim().length > 0) {
|
|
964
|
+
return { type: "assistant_message", text: formatProposedPlanBlock(text) };
|
|
965
|
+
}
|
|
900
966
|
const items = parsePlanTextToTodoItems(text);
|
|
901
|
-
return { type: "todo", items };
|
|
967
|
+
return items.length > 0 ? { type: "todo", items } : null;
|
|
902
968
|
}
|
|
903
969
|
case "reasoning": {
|
|
904
970
|
const summary = Array.isArray(normalizedItem.summary)
|
|
@@ -931,33 +997,6 @@ function toSandboxPolicy(type, networkAccess) {
|
|
|
931
997
|
return { type: "workspaceWrite", networkAccess: networkAccess ?? false };
|
|
932
998
|
}
|
|
933
999
|
}
|
|
934
|
-
function getImageExtension(mimeType) {
|
|
935
|
-
switch (mimeType) {
|
|
936
|
-
case "image/jpeg":
|
|
937
|
-
return "jpg";
|
|
938
|
-
case "image/png":
|
|
939
|
-
return "png";
|
|
940
|
-
case "image/webp":
|
|
941
|
-
return "webp";
|
|
942
|
-
case "image/gif":
|
|
943
|
-
return "gif";
|
|
944
|
-
case "image/bmp":
|
|
945
|
-
return "bmp";
|
|
946
|
-
case "image/tiff":
|
|
947
|
-
return "tiff";
|
|
948
|
-
default:
|
|
949
|
-
return "bin";
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
function normalizeImageData(mimeType, data) {
|
|
953
|
-
if (data.startsWith("data:")) {
|
|
954
|
-
const match = data.match(/^data:([^;]+);base64,(.*)$/);
|
|
955
|
-
if (match) {
|
|
956
|
-
return { mimeType: match[1], data: match[2] };
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
return { mimeType, data };
|
|
960
|
-
}
|
|
961
1000
|
const ThreadStartedNotificationSchema = z.object({
|
|
962
1001
|
thread: z.object({ id: z.string() }).passthrough(),
|
|
963
1002
|
}).passthrough();
|
|
@@ -986,6 +1025,13 @@ const TurnPlanUpdatedNotificationSchema = z.object({
|
|
|
986
1025
|
})
|
|
987
1026
|
.passthrough()),
|
|
988
1027
|
}).passthrough();
|
|
1028
|
+
const ItemPlanDeltaNotificationSchema = z.object({
|
|
1029
|
+
itemId: z.string(),
|
|
1030
|
+
delta: z.string(),
|
|
1031
|
+
}).passthrough();
|
|
1032
|
+
const ServerRequestResolvedNotificationSchema = z.object({
|
|
1033
|
+
requestId: z.union([z.string(), z.number()]),
|
|
1034
|
+
}).passthrough();
|
|
989
1035
|
const TurnDiffUpdatedNotificationSchema = z.object({
|
|
990
1036
|
diff: z.string(),
|
|
991
1037
|
}).passthrough();
|
|
@@ -1125,6 +1171,12 @@ const CodexNotificationSchema = z.union([
|
|
|
1125
1171
|
})),
|
|
1126
1172
|
})),
|
|
1127
1173
|
z.object({ method: z.literal("turn/plan/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1174
|
+
z.object({ method: z.literal("item/plan/delta"), params: ItemPlanDeltaNotificationSchema }).transform(({ params }) => ({
|
|
1175
|
+
kind: "plan_delta",
|
|
1176
|
+
itemId: params.itemId,
|
|
1177
|
+
delta: params.delta,
|
|
1178
|
+
})),
|
|
1179
|
+
z.object({ method: z.literal("item/plan/delta"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1128
1180
|
z.object({ method: z.literal("turn/diff/updated"), params: TurnDiffUpdatedNotificationSchema }).transform(({ params }) => ({ kind: "diff_updated", diff: params.diff })),
|
|
1129
1181
|
z.object({ method: z.literal("turn/diff/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1130
1182
|
z.object({
|
|
@@ -1132,6 +1184,14 @@ const CodexNotificationSchema = z.union([
|
|
|
1132
1184
|
params: ThreadTokenUsageUpdatedNotificationSchema,
|
|
1133
1185
|
}).transform(({ params }) => ({ kind: "token_usage_updated", tokenUsage: params.tokenUsage })),
|
|
1134
1186
|
z.object({ method: z.literal("thread/tokenUsage/updated"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1187
|
+
z.object({
|
|
1188
|
+
method: z.literal("serverRequest/resolved"),
|
|
1189
|
+
params: ServerRequestResolvedNotificationSchema,
|
|
1190
|
+
}).transform(({ params }) => ({
|
|
1191
|
+
kind: "server_request_resolved",
|
|
1192
|
+
requestId: String(params.requestId),
|
|
1193
|
+
})),
|
|
1194
|
+
z.object({ method: z.literal("serverRequest/resolved"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1135
1195
|
z.object({ method: z.literal("item/agentMessage/delta"), params: ItemTextDeltaNotificationSchema }).transform(({ params }) => ({
|
|
1136
1196
|
kind: "agent_message_delta",
|
|
1137
1197
|
itemId: params.itemId,
|
|
@@ -1268,16 +1328,6 @@ const CodexNotificationSchema = z.union([
|
|
|
1268
1328
|
z.object({ method: z.literal("codex/event/task_complete"), params: z.unknown() }).transform(({ method, params }) => ({ kind: "invalid_payload", method, params })),
|
|
1269
1329
|
z.object({ method: z.string(), params: z.unknown() }).transform(({ method, params }) => ({ kind: "unknown_method", method, params })),
|
|
1270
1330
|
]);
|
|
1271
|
-
async function writeImageAttachment(mimeType, data) {
|
|
1272
|
-
const attachmentsDir = path.join(os.tmpdir(), CODEX_IMAGE_ATTACHMENT_DIR);
|
|
1273
|
-
await fs.mkdir(attachmentsDir, { recursive: true });
|
|
1274
|
-
const normalized = normalizeImageData(mimeType, data);
|
|
1275
|
-
const extension = getImageExtension(normalized.mimeType);
|
|
1276
|
-
const filename = `${randomUUID()}.${extension}`;
|
|
1277
|
-
const filePath = path.join(attachmentsDir, filename);
|
|
1278
|
-
await fs.writeFile(filePath, Buffer.from(normalized.data, "base64"));
|
|
1279
|
-
return filePath;
|
|
1280
|
-
}
|
|
1281
1331
|
async function readCodexConfiguredDefaults(client, logger) {
|
|
1282
1332
|
let savedConfigDefaults = {};
|
|
1283
1333
|
try {
|
|
@@ -1341,6 +1391,10 @@ export async function codexAppServerTurnInputFromPrompt(prompt, logger) {
|
|
|
1341
1391
|
}
|
|
1342
1392
|
export const __codexAppServerInternals = {
|
|
1343
1393
|
mapCodexPatchNotificationToToolCall,
|
|
1394
|
+
supportsPlanCollaborationMode,
|
|
1395
|
+
shouldRetryInitializeWithoutExperimentalApi,
|
|
1396
|
+
shouldRetryTurnStartWithoutCollaborationMode,
|
|
1397
|
+
formatProposedPlanBlock,
|
|
1344
1398
|
};
|
|
1345
1399
|
class CodexAppServerAgentSession {
|
|
1346
1400
|
constructor(config, resumeHandle, logger, spawnAppServer) {
|
|
@@ -1370,8 +1424,8 @@ class CodexAppServerAgentSession {
|
|
|
1370
1424
|
this.warnedInvalidNotificationPayloads = new Set();
|
|
1371
1425
|
this.warnedIncompleteEditToolCallIds = new Set();
|
|
1372
1426
|
this.connected = false;
|
|
1373
|
-
this.
|
|
1374
|
-
this.
|
|
1427
|
+
this.nativePlanModeSupported = null;
|
|
1428
|
+
this.pendingPlanTexts = new Map();
|
|
1375
1429
|
this.cachedSkills = [];
|
|
1376
1430
|
this.logger = logger.child({ module: "agent", provider: CODEX_PROVIDER });
|
|
1377
1431
|
if (config.modeId === undefined) {
|
|
@@ -1396,13 +1450,26 @@ class CodexAppServerAgentSession {
|
|
|
1396
1450
|
this.client = new CodexAppServerClient(child, this.logger);
|
|
1397
1451
|
this.client.setNotificationHandler((method, params) => this.handleNotification(method, params));
|
|
1398
1452
|
this.registerRequestHandlers();
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1453
|
+
const clientInfo = {
|
|
1454
|
+
name: "junction",
|
|
1455
|
+
title: "Junction",
|
|
1456
|
+
version: "0.0.0",
|
|
1457
|
+
};
|
|
1458
|
+
try {
|
|
1459
|
+
await this.client.request("initialize", {
|
|
1460
|
+
clientInfo,
|
|
1461
|
+
capabilities: {
|
|
1462
|
+
experimentalApi: true,
|
|
1463
|
+
},
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
catch (error) {
|
|
1467
|
+
if (!shouldRetryInitializeWithoutExperimentalApi(error)) {
|
|
1468
|
+
throw error;
|
|
1469
|
+
}
|
|
1470
|
+
await this.client.request("initialize", { clientInfo });
|
|
1471
|
+
this.nativePlanModeSupported = false;
|
|
1472
|
+
}
|
|
1406
1473
|
this.client.notify("initialized", {});
|
|
1407
1474
|
await this.loadCollaborationModes();
|
|
1408
1475
|
await this.loadSkills();
|
|
@@ -1415,22 +1482,17 @@ class CodexAppServerAgentSession {
|
|
|
1415
1482
|
async loadCollaborationModes() {
|
|
1416
1483
|
if (!this.client)
|
|
1417
1484
|
return;
|
|
1485
|
+
if (this.nativePlanModeSupported === false) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1418
1488
|
try {
|
|
1419
|
-
const response =
|
|
1420
|
-
|
|
1421
|
-
this.collaborationModes = data.map((entry) => ({
|
|
1422
|
-
name: String(entry.name ?? ""),
|
|
1423
|
-
mode: entry.mode ?? null,
|
|
1424
|
-
model: entry.model ?? null,
|
|
1425
|
-
reasoning_effort: entry.reasoning_effort ?? null,
|
|
1426
|
-
developer_instructions: entry.developer_instructions ?? null,
|
|
1427
|
-
}));
|
|
1489
|
+
const response = await this.client.request("collaborationMode/list", {});
|
|
1490
|
+
this.nativePlanModeSupported = supportsPlanCollaborationMode(response);
|
|
1428
1491
|
}
|
|
1429
1492
|
catch (error) {
|
|
1430
1493
|
this.logger.trace({ error }, "Failed to load collaboration modes");
|
|
1431
|
-
this.
|
|
1494
|
+
this.nativePlanModeSupported = false;
|
|
1432
1495
|
}
|
|
1433
|
-
this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
|
|
1434
1496
|
}
|
|
1435
1497
|
async loadSkills() {
|
|
1436
1498
|
if (!this.client)
|
|
@@ -1460,59 +1522,12 @@ class CodexAppServerAgentSession {
|
|
|
1460
1522
|
this.cachedSkills = [];
|
|
1461
1523
|
}
|
|
1462
1524
|
}
|
|
1463
|
-
resolveCollaborationMode(modeId) {
|
|
1464
|
-
if (this.collaborationModes.length === 0)
|
|
1465
|
-
return null;
|
|
1466
|
-
const normalized = modeId.toLowerCase();
|
|
1467
|
-
const findByName = (predicate) => this.collaborationModes.find((entry) => predicate(entry.name.toLowerCase()));
|
|
1468
|
-
let match;
|
|
1469
|
-
if (normalized === "read-only") {
|
|
1470
|
-
// Prefer explicit plan collaboration modes over generic read-only modes.
|
|
1471
|
-
match =
|
|
1472
|
-
findByName((name) => name.includes("plan")) ??
|
|
1473
|
-
findByName((name) => name.includes("read"));
|
|
1474
|
-
}
|
|
1475
|
-
else if (normalized === "full-access") {
|
|
1476
|
-
match = findByName((name) => name.includes("full") || name.includes("exec"));
|
|
1477
|
-
}
|
|
1478
|
-
else {
|
|
1479
|
-
match = findByName((name) => name.includes("auto") || name.includes("code"));
|
|
1480
|
-
}
|
|
1481
|
-
if (!match) {
|
|
1482
|
-
match = this.collaborationModes[0] ?? null;
|
|
1483
|
-
}
|
|
1484
|
-
if (!match)
|
|
1485
|
-
return null;
|
|
1486
|
-
const settings = {};
|
|
1487
|
-
if (match.model)
|
|
1488
|
-
settings.model = match.model;
|
|
1489
|
-
if (match.reasoning_effort)
|
|
1490
|
-
settings.reasoning_effort = match.reasoning_effort;
|
|
1491
|
-
const modeSpecificInstruction = normalized === "read-only"
|
|
1492
|
-
? "Plan mode is enabled. Do not execute commands, edit files, or perform write operations. Provide analysis and a step-by-step plan only."
|
|
1493
|
-
: "";
|
|
1494
|
-
const developerInstructions = [
|
|
1495
|
-
modeSpecificInstruction,
|
|
1496
|
-
match.developer_instructions?.trim(),
|
|
1497
|
-
this.config.systemPrompt?.trim(),
|
|
1498
|
-
]
|
|
1499
|
-
.filter((entry) => typeof entry === "string" && entry.length > 0)
|
|
1500
|
-
.join("\n\n");
|
|
1501
|
-
if (developerInstructions)
|
|
1502
|
-
settings.developer_instructions = developerInstructions;
|
|
1503
|
-
if (this.config.model)
|
|
1504
|
-
settings.model = this.config.model;
|
|
1505
|
-
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
|
1506
|
-
if (thinkingOptionId)
|
|
1507
|
-
settings.reasoning_effort = thinkingOptionId;
|
|
1508
|
-
return { mode: match.mode ?? "code", settings, name: match.name };
|
|
1509
|
-
}
|
|
1510
1525
|
registerRequestHandlers() {
|
|
1511
1526
|
if (!this.client)
|
|
1512
1527
|
return;
|
|
1513
|
-
this.client.setRequestHandler("item/commandExecution/requestApproval", (params) => this.handleCommandApprovalRequest(params));
|
|
1514
|
-
this.client.setRequestHandler("item/fileChange/requestApproval", (params) => this.handleFileChangeApprovalRequest(params));
|
|
1515
|
-
this.client.setRequestHandler("tool/requestUserInput", (params) => this.handleToolApprovalRequest(params));
|
|
1528
|
+
this.client.setRequestHandler("item/commandExecution/requestApproval", (params, requestId) => this.handleCommandApprovalRequest(params, requestId));
|
|
1529
|
+
this.client.setRequestHandler("item/fileChange/requestApproval", (params, requestId) => this.handleFileChangeApprovalRequest(params, requestId));
|
|
1530
|
+
this.client.setRequestHandler("tool/requestUserInput", (params, requestId) => this.handleToolApprovalRequest(params, requestId));
|
|
1516
1531
|
}
|
|
1517
1532
|
async loadPersistedHistory() {
|
|
1518
1533
|
if (!this.client || !this.currentThreadId)
|
|
@@ -1710,6 +1725,8 @@ class CodexAppServerAgentSession {
|
|
|
1710
1725
|
const preset = MODE_PRESETS[this.currentMode] ?? MODE_PRESETS[DEFAULT_CODEX_MODE_ID];
|
|
1711
1726
|
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
|
|
1712
1727
|
const sandboxPolicyType = this.config.sandboxMode ?? preset.sandbox;
|
|
1728
|
+
const planModeRequested = options?.extra?.codex?.planMode === true;
|
|
1729
|
+
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
|
1713
1730
|
const params = {
|
|
1714
1731
|
threadId: this.currentThreadId,
|
|
1715
1732
|
input,
|
|
@@ -1721,16 +1738,9 @@ class CodexAppServerAgentSession {
|
|
|
1721
1738
|
if (this.config.model) {
|
|
1722
1739
|
params.model = this.config.model;
|
|
1723
1740
|
}
|
|
1724
|
-
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId);
|
|
1725
1741
|
if (thinkingOptionId) {
|
|
1726
1742
|
params.effort = thinkingOptionId;
|
|
1727
1743
|
}
|
|
1728
|
-
if (this.resolvedCollaborationMode) {
|
|
1729
|
-
params.collaborationMode = {
|
|
1730
|
-
mode: this.resolvedCollaborationMode.mode,
|
|
1731
|
-
settings: this.resolvedCollaborationMode.settings,
|
|
1732
|
-
};
|
|
1733
|
-
}
|
|
1734
1744
|
if (this.config.cwd) {
|
|
1735
1745
|
params.cwd = this.config.cwd;
|
|
1736
1746
|
}
|
|
@@ -1744,7 +1754,38 @@ class CodexAppServerAgentSession {
|
|
|
1744
1754
|
if (codexConfig) {
|
|
1745
1755
|
params.config = codexConfig;
|
|
1746
1756
|
}
|
|
1747
|
-
|
|
1757
|
+
let downgradedFromPlanMode = false;
|
|
1758
|
+
if (this.nativePlanModeSupported !== false) {
|
|
1759
|
+
const collaborationMode = this.buildCollaborationModePayload(planModeRequested ? "plan" : "default");
|
|
1760
|
+
if (collaborationMode) {
|
|
1761
|
+
params.collaborationMode = collaborationMode;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
try {
|
|
1765
|
+
await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
|
|
1766
|
+
}
|
|
1767
|
+
catch (error) {
|
|
1768
|
+
const canRetryWithoutPlanMode = Object.prototype.hasOwnProperty.call(params, "collaborationMode") &&
|
|
1769
|
+
shouldRetryTurnStartWithoutCollaborationMode(error);
|
|
1770
|
+
if (!canRetryWithoutPlanMode) {
|
|
1771
|
+
throw error;
|
|
1772
|
+
}
|
|
1773
|
+
delete params.collaborationMode;
|
|
1774
|
+
this.nativePlanModeSupported = false;
|
|
1775
|
+
this.cachedRuntimeInfo = null;
|
|
1776
|
+
downgradedFromPlanMode = planModeRequested;
|
|
1777
|
+
await this.client.request("turn/start", params, TURN_START_TIMEOUT_MS);
|
|
1778
|
+
}
|
|
1779
|
+
if (downgradedFromPlanMode) {
|
|
1780
|
+
this.emitEvent({
|
|
1781
|
+
type: "timeline",
|
|
1782
|
+
provider: CODEX_PROVIDER,
|
|
1783
|
+
item: {
|
|
1784
|
+
type: "assistant_message",
|
|
1785
|
+
text: "Plan mode is not supported by this Codex runtime. Sent as a normal turn instead.",
|
|
1786
|
+
},
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1748
1789
|
let sawTurnStarted = false;
|
|
1749
1790
|
for await (const event of queue) {
|
|
1750
1791
|
// Drop pre-start timeline noise that can leak from the previous turn.
|
|
@@ -1801,9 +1842,9 @@ class CodexAppServerAgentSession {
|
|
|
1801
1842
|
model: this.config.model ?? null,
|
|
1802
1843
|
thinkingOptionId: normalizeCodexThinkingOptionId(this.config.thinkingOptionId) ?? null,
|
|
1803
1844
|
modeId: this.currentMode ?? null,
|
|
1804
|
-
extra: this.
|
|
1805
|
-
?
|
|
1806
|
-
:
|
|
1845
|
+
extra: this.nativePlanModeSupported === null
|
|
1846
|
+
? undefined
|
|
1847
|
+
: { planModeSupported: this.nativePlanModeSupported },
|
|
1807
1848
|
};
|
|
1808
1849
|
this.cachedRuntimeInfo = info;
|
|
1809
1850
|
return { ...info };
|
|
@@ -1817,17 +1858,14 @@ class CodexAppServerAgentSession {
|
|
|
1817
1858
|
async setMode(modeId) {
|
|
1818
1859
|
validateCodexMode(modeId);
|
|
1819
1860
|
this.currentMode = modeId;
|
|
1820
|
-
this.resolvedCollaborationMode = this.resolveCollaborationMode(modeId);
|
|
1821
1861
|
this.cachedRuntimeInfo = null;
|
|
1822
1862
|
}
|
|
1823
1863
|
async setModel(modelId) {
|
|
1824
1864
|
this.config.model = modelId ?? undefined;
|
|
1825
|
-
this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
|
|
1826
1865
|
this.cachedRuntimeInfo = null;
|
|
1827
1866
|
}
|
|
1828
1867
|
async setThinkingOption(thinkingOptionId) {
|
|
1829
1868
|
this.config.thinkingOptionId = normalizeCodexThinkingOptionId(thinkingOptionId);
|
|
1830
|
-
this.resolvedCollaborationMode = this.resolveCollaborationMode(this.currentMode);
|
|
1831
1869
|
this.cachedRuntimeInfo = null;
|
|
1832
1870
|
}
|
|
1833
1871
|
getPendingPermissions() {
|
|
@@ -2057,6 +2095,24 @@ class CodexAppServerAgentSession {
|
|
|
2057
2095
|
}
|
|
2058
2096
|
return Object.keys(innerConfig).length > 0 ? innerConfig : null;
|
|
2059
2097
|
}
|
|
2098
|
+
buildCollaborationModePayload(mode) {
|
|
2099
|
+
if (this.nativePlanModeSupported !== true) {
|
|
2100
|
+
return null;
|
|
2101
|
+
}
|
|
2102
|
+
const model = normalizeCodexModelId(this.config.model);
|
|
2103
|
+
if (!model) {
|
|
2104
|
+
return null;
|
|
2105
|
+
}
|
|
2106
|
+
const thinkingOptionId = normalizeCodexThinkingOptionId(this.config.thinkingOptionId) ?? null;
|
|
2107
|
+
return {
|
|
2108
|
+
mode,
|
|
2109
|
+
settings: {
|
|
2110
|
+
model,
|
|
2111
|
+
reasoning_effort: thinkingOptionId,
|
|
2112
|
+
developer_instructions: null,
|
|
2113
|
+
},
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2060
2116
|
async buildUserInput(prompt) {
|
|
2061
2117
|
if (typeof prompt === "string") {
|
|
2062
2118
|
return [{ type: "text", text: prompt }];
|
|
@@ -2111,6 +2167,7 @@ class CodexAppServerAgentSession {
|
|
|
2111
2167
|
this.emittedExecCommandCompletedCallIds.clear();
|
|
2112
2168
|
this.pendingCommandOutputDeltas.clear();
|
|
2113
2169
|
this.pendingFileChangeOutputDeltas.clear();
|
|
2170
|
+
this.pendingPlanTexts.clear();
|
|
2114
2171
|
this.warnedIncompleteEditToolCallIds.clear();
|
|
2115
2172
|
this.eventQueue?.end();
|
|
2116
2173
|
return;
|
|
@@ -2127,6 +2184,22 @@ class CodexAppServerAgentSession {
|
|
|
2127
2184
|
});
|
|
2128
2185
|
return;
|
|
2129
2186
|
}
|
|
2187
|
+
if (parsed.kind === "plan_delta") {
|
|
2188
|
+
const previous = this.pendingPlanTexts.get(parsed.itemId) ?? "";
|
|
2189
|
+
const next = previous + parsed.delta;
|
|
2190
|
+
this.pendingPlanTexts.set(parsed.itemId, next);
|
|
2191
|
+
this.emitEvent({
|
|
2192
|
+
type: "timeline",
|
|
2193
|
+
provider: CODEX_PROVIDER,
|
|
2194
|
+
item: {
|
|
2195
|
+
type: "assistant_message",
|
|
2196
|
+
text: formatProposedPlanChunk(parsed.delta, {
|
|
2197
|
+
open: previous.length === 0,
|
|
2198
|
+
}),
|
|
2199
|
+
},
|
|
2200
|
+
});
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2130
2203
|
if (parsed.kind === "diff_updated") {
|
|
2131
2204
|
// NOTE: Codex app-server emits frequent `turn/diff/updated` notifications
|
|
2132
2205
|
// containing a full accumulated unified diff for the *entire turn*.
|
|
@@ -2134,6 +2207,20 @@ class CodexAppServerAgentSession {
|
|
|
2134
2207
|
// We intentionally do NOT store every diff update in the timeline.
|
|
2135
2208
|
return;
|
|
2136
2209
|
}
|
|
2210
|
+
if (parsed.kind === "server_request_resolved") {
|
|
2211
|
+
const pendingRequestId = toPendingPermissionId(parsed.requestId);
|
|
2212
|
+
if (this.resolvedPermissionRequests.has(pendingRequestId)) {
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
this.pendingPermissions.delete(pendingRequestId);
|
|
2216
|
+
this.emitEvent({
|
|
2217
|
+
type: "permission_resolved",
|
|
2218
|
+
provider: CODEX_PROVIDER,
|
|
2219
|
+
requestId: pendingRequestId,
|
|
2220
|
+
resolution: { behavior: "allow" },
|
|
2221
|
+
});
|
|
2222
|
+
return;
|
|
2223
|
+
}
|
|
2137
2224
|
if (parsed.kind === "token_usage_updated") {
|
|
2138
2225
|
this.latestUsage = toAgentUsage(parsed.tokenUsage);
|
|
2139
2226
|
return;
|
|
@@ -2263,6 +2350,24 @@ class CodexAppServerAgentSession {
|
|
|
2263
2350
|
timelineItem.text = buffered;
|
|
2264
2351
|
}
|
|
2265
2352
|
}
|
|
2353
|
+
if (timelineItem.type === "assistant_message" &&
|
|
2354
|
+
normalizedItemType === "plan" &&
|
|
2355
|
+
itemId) {
|
|
2356
|
+
const bufferedPlanText = this.pendingPlanTexts.get(itemId) ?? "";
|
|
2357
|
+
const finalPlanText = typeof parsed.item.text === "string" ? parsed.item.text : "";
|
|
2358
|
+
if (bufferedPlanText.length > 0) {
|
|
2359
|
+
const trailingText = finalPlanText.startsWith(bufferedPlanText)
|
|
2360
|
+
? finalPlanText.slice(bufferedPlanText.length)
|
|
2361
|
+
: "";
|
|
2362
|
+
timelineItem.text = formatProposedPlanChunk(trailingText, {
|
|
2363
|
+
close: true,
|
|
2364
|
+
});
|
|
2365
|
+
this.pendingPlanTexts.delete(itemId);
|
|
2366
|
+
}
|
|
2367
|
+
else if (finalPlanText.trim().length > 0) {
|
|
2368
|
+
timelineItem.text = formatProposedPlanBlock(finalPlanText);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2266
2371
|
if (timelineItem.type === "reasoning" && itemId) {
|
|
2267
2372
|
const buffered = this.pendingReasoning.get(itemId);
|
|
2268
2373
|
if (buffered && buffered.length > 0) {
|
|
@@ -2278,6 +2383,7 @@ class CodexAppServerAgentSession {
|
|
|
2278
2383
|
this.emittedItemStartedIds.delete(itemId);
|
|
2279
2384
|
this.pendingCommandOutputDeltas.delete(itemId);
|
|
2280
2385
|
this.pendingFileChangeOutputDeltas.delete(itemId);
|
|
2386
|
+
this.pendingPlanTexts.delete(itemId);
|
|
2281
2387
|
}
|
|
2282
2388
|
}
|
|
2283
2389
|
return;
|
|
@@ -2374,7 +2480,7 @@ class CodexAppServerAgentSession {
|
|
|
2374
2480
|
payload,
|
|
2375
2481
|
}, "Codex edit tool call is missing diff/content fields");
|
|
2376
2482
|
}
|
|
2377
|
-
handleCommandApprovalRequest(params) {
|
|
2483
|
+
handleCommandApprovalRequest(params, rawRequestId) {
|
|
2378
2484
|
const parsed = params;
|
|
2379
2485
|
const commandPreview = mapCodexExecNotificationToToolCall({
|
|
2380
2486
|
callId: parsed.itemId,
|
|
@@ -2382,7 +2488,7 @@ class CodexAppServerAgentSession {
|
|
|
2382
2488
|
cwd: parsed.cwd ?? this.config.cwd ?? null,
|
|
2383
2489
|
running: true,
|
|
2384
2490
|
});
|
|
2385
|
-
const requestId =
|
|
2491
|
+
const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
|
|
2386
2492
|
const title = parsed.command ? `Run command: ${parsed.command}` : "Run command";
|
|
2387
2493
|
const request = {
|
|
2388
2494
|
id: requestId,
|
|
@@ -2415,9 +2521,9 @@ class CodexAppServerAgentSession {
|
|
|
2415
2521
|
this.pendingPermissionHandlers.set(requestId, { resolve, kind: "command" });
|
|
2416
2522
|
});
|
|
2417
2523
|
}
|
|
2418
|
-
handleFileChangeApprovalRequest(params) {
|
|
2524
|
+
handleFileChangeApprovalRequest(params, rawRequestId) {
|
|
2419
2525
|
const parsed = params;
|
|
2420
|
-
const requestId =
|
|
2526
|
+
const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
|
|
2421
2527
|
const request = {
|
|
2422
2528
|
id: requestId,
|
|
2423
2529
|
provider: CODEX_PROVIDER,
|
|
@@ -2444,16 +2550,19 @@ class CodexAppServerAgentSession {
|
|
|
2444
2550
|
this.pendingPermissionHandlers.set(requestId, { resolve, kind: "file" });
|
|
2445
2551
|
});
|
|
2446
2552
|
}
|
|
2447
|
-
handleToolApprovalRequest(params) {
|
|
2553
|
+
handleToolApprovalRequest(params, rawRequestId) {
|
|
2448
2554
|
const parsed = params;
|
|
2449
|
-
const requestId =
|
|
2555
|
+
const requestId = toPendingPermissionId(rawRequestId ?? parsed.itemId);
|
|
2450
2556
|
const request = {
|
|
2451
2557
|
id: requestId,
|
|
2452
2558
|
provider: CODEX_PROVIDER,
|
|
2453
|
-
name: "
|
|
2454
|
-
kind: "
|
|
2455
|
-
title: "
|
|
2559
|
+
name: "CodexQuestion",
|
|
2560
|
+
kind: "question",
|
|
2561
|
+
title: "Answer question",
|
|
2456
2562
|
description: undefined,
|
|
2563
|
+
input: {
|
|
2564
|
+
questions: Array.isArray(parsed.questions) ? parsed.questions : [],
|
|
2565
|
+
},
|
|
2457
2566
|
detail: {
|
|
2458
2567
|
type: "unknown",
|
|
2459
2568
|
input: {
|
|
@@ -2473,7 +2582,7 @@ class CodexAppServerAgentSession {
|
|
|
2473
2582
|
return new Promise((resolve) => {
|
|
2474
2583
|
this.pendingPermissionHandlers.set(requestId, {
|
|
2475
2584
|
resolve,
|
|
2476
|
-
kind: "
|
|
2585
|
+
kind: "question",
|
|
2477
2586
|
questions: Array.isArray(parsed.questions) ? parsed.questions : [],
|
|
2478
2587
|
});
|
|
2479
2588
|
});
|