@ccpocket/bridge 1.24.0 → 1.26.0
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/codex-process.d.ts +29 -0
- package/dist/codex-process.js +560 -44
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +3 -0
- package/dist/parser.js.map +1 -1
- package/dist/push-i18n.d.ts +1 -1
- package/dist/push-i18n.js +18 -1
- package/dist/push-i18n.js.map +1 -1
- package/dist/sdk-process.js +11 -38
- package/dist/sdk-process.js.map +1 -1
- package/dist/session.d.ts +2 -0
- package/dist/session.js +56 -50
- package/dist/session.js.map +1 -1
- package/dist/sessions-index.d.ts +2 -0
- package/dist/sessions-index.js +10 -0
- package/dist/sessions-index.js.map +1 -1
- package/dist/websocket.d.ts +4 -0
- package/dist/websocket.js +104 -10
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/codex-process.js
CHANGED
|
@@ -8,6 +8,8 @@ export class CodexProcess extends EventEmitter {
|
|
|
8
8
|
child = null;
|
|
9
9
|
_status = "starting";
|
|
10
10
|
_threadId = null;
|
|
11
|
+
_agentNickname = null;
|
|
12
|
+
_agentRole = null;
|
|
11
13
|
stopped = false;
|
|
12
14
|
startModel;
|
|
13
15
|
inputResolve = null;
|
|
@@ -43,6 +45,12 @@ export class CodexProcess extends EventEmitter {
|
|
|
43
45
|
get sessionId() {
|
|
44
46
|
return this._threadId;
|
|
45
47
|
}
|
|
48
|
+
get agentNickname() {
|
|
49
|
+
return this._agentNickname;
|
|
50
|
+
}
|
|
51
|
+
get agentRole() {
|
|
52
|
+
return this._agentRole;
|
|
53
|
+
}
|
|
46
54
|
get isRunning() {
|
|
47
55
|
return this.child !== null;
|
|
48
56
|
}
|
|
@@ -89,12 +97,61 @@ export class CodexProcess extends EventEmitter {
|
|
|
89
97
|
async archiveThread(threadId) {
|
|
90
98
|
await this.request("thread/archive", { threadId });
|
|
91
99
|
}
|
|
100
|
+
async listThreads(params = {}) {
|
|
101
|
+
const result = await this.request("thread/list", {
|
|
102
|
+
sortKey: "updated_at",
|
|
103
|
+
archived: false,
|
|
104
|
+
...(params.limit != null ? { limit: params.limit } : {}),
|
|
105
|
+
...(params.cursor !== undefined ? { cursor: params.cursor } : {}),
|
|
106
|
+
...(params.cwd ? { cwd: params.cwd } : {}),
|
|
107
|
+
...(params.searchTerm ? { searchTerm: params.searchTerm } : {}),
|
|
108
|
+
});
|
|
109
|
+
const data = Array.isArray(result.data)
|
|
110
|
+
? result.data.map((entry) => toCodexThreadSummary(entry))
|
|
111
|
+
: [];
|
|
112
|
+
return {
|
|
113
|
+
data,
|
|
114
|
+
nextCursor: typeof result.nextCursor === "string" ? result.nextCursor : null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
92
117
|
start(projectPath, options) {
|
|
93
118
|
if (this.child) {
|
|
94
119
|
this.stop();
|
|
95
120
|
}
|
|
121
|
+
this.prepareLaunch(projectPath, options);
|
|
122
|
+
this.launchAppServer(projectPath, options);
|
|
123
|
+
void this.bootstrap(projectPath, options);
|
|
124
|
+
}
|
|
125
|
+
async initializeOnly(projectPath) {
|
|
126
|
+
if (this.child) {
|
|
127
|
+
this.stop();
|
|
128
|
+
}
|
|
129
|
+
this.prepareLaunch(projectPath);
|
|
130
|
+
this.launchAppServer(projectPath);
|
|
131
|
+
await this.initializeRpcConnection();
|
|
132
|
+
this.setStatus("idle");
|
|
133
|
+
}
|
|
134
|
+
stop() {
|
|
135
|
+
this.stopped = true;
|
|
136
|
+
if (this.inputResolve) {
|
|
137
|
+
this.inputResolve({ text: "" });
|
|
138
|
+
this.inputResolve = null;
|
|
139
|
+
}
|
|
140
|
+
this.pendingApprovals.clear();
|
|
141
|
+
this.pendingUserInputs.clear();
|
|
142
|
+
this.rejectAllPending(new Error("stopped"));
|
|
143
|
+
if (this.child) {
|
|
144
|
+
this.child.kill("SIGTERM");
|
|
145
|
+
this.child = null;
|
|
146
|
+
}
|
|
147
|
+
this.setStatus("idle");
|
|
148
|
+
console.log("[codex-process] Stopped");
|
|
149
|
+
}
|
|
150
|
+
prepareLaunch(projectPath, options) {
|
|
96
151
|
this.stopped = false;
|
|
97
152
|
this._threadId = null;
|
|
153
|
+
this._agentNickname = null;
|
|
154
|
+
this._agentRole = null;
|
|
98
155
|
this.pendingTurnId = null;
|
|
99
156
|
this.pendingTurnCompletion = null;
|
|
100
157
|
this.pendingApprovals.clear();
|
|
@@ -106,6 +163,9 @@ export class CodexProcess extends EventEmitter {
|
|
|
106
163
|
this.lastPlanItemText = null;
|
|
107
164
|
this.pendingPlanCompletion = null;
|
|
108
165
|
this._pendingPlanInput = null;
|
|
166
|
+
this._projectPath = projectPath;
|
|
167
|
+
}
|
|
168
|
+
launchAppServer(projectPath, options) {
|
|
109
169
|
console.log(`[codex-process] Starting app-server (cwd: ${projectPath}, sandbox: ${options?.sandboxMode ?? "workspace-write"}, approval: ${options?.approvalPolicy ?? "never"}, model: ${options?.model ?? "default"}, collaboration: ${this._collaborationMode})`);
|
|
110
170
|
const child = spawn("codex", ["app-server", "--listen", "stdio://"], {
|
|
111
171
|
cwd: projectPath,
|
|
@@ -142,23 +202,6 @@ export class CodexProcess extends EventEmitter {
|
|
|
142
202
|
this.setStatus("idle");
|
|
143
203
|
this.emit("exit", code);
|
|
144
204
|
});
|
|
145
|
-
void this.bootstrap(projectPath, options);
|
|
146
|
-
}
|
|
147
|
-
stop() {
|
|
148
|
-
this.stopped = true;
|
|
149
|
-
if (this.inputResolve) {
|
|
150
|
-
this.inputResolve({ text: "" });
|
|
151
|
-
this.inputResolve = null;
|
|
152
|
-
}
|
|
153
|
-
this.pendingApprovals.clear();
|
|
154
|
-
this.pendingUserInputs.clear();
|
|
155
|
-
this.rejectAllPending(new Error("stopped"));
|
|
156
|
-
if (this.child) {
|
|
157
|
-
this.child.kill("SIGTERM");
|
|
158
|
-
this.child = null;
|
|
159
|
-
}
|
|
160
|
-
this.setStatus("idle");
|
|
161
|
-
console.log("[codex-process] Stopped");
|
|
162
205
|
}
|
|
163
206
|
interrupt() {
|
|
164
207
|
if (!this._threadId || !this.pendingTurnId)
|
|
@@ -211,9 +254,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
211
254
|
return;
|
|
212
255
|
}
|
|
213
256
|
this.pendingApprovals.delete(pending.toolUseId);
|
|
214
|
-
this.respondToServerRequest(pending.requestId,
|
|
215
|
-
decision: "accept",
|
|
216
|
-
});
|
|
257
|
+
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "accept"));
|
|
217
258
|
this.emitToolResult(pending.toolUseId, "Approved");
|
|
218
259
|
if (this.pendingApprovals.size === 0) {
|
|
219
260
|
this.setStatus("running");
|
|
@@ -226,12 +267,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
226
267
|
return;
|
|
227
268
|
}
|
|
228
269
|
this.pendingApprovals.delete(pending.toolUseId);
|
|
229
|
-
this.respondToServerRequest(pending.requestId,
|
|
230
|
-
decision: "accept",
|
|
231
|
-
acceptSettings: {
|
|
232
|
-
forSession: true,
|
|
233
|
-
},
|
|
234
|
-
});
|
|
270
|
+
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "acceptForSession"));
|
|
235
271
|
this.emitToolResult(pending.toolUseId, "Approved (always)");
|
|
236
272
|
if (this.pendingApprovals.size === 0) {
|
|
237
273
|
this.setStatus("running");
|
|
@@ -249,9 +285,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
249
285
|
return;
|
|
250
286
|
}
|
|
251
287
|
this.pendingApprovals.delete(pending.toolUseId);
|
|
252
|
-
this.respondToServerRequest(pending.requestId,
|
|
253
|
-
decision: "decline",
|
|
254
|
-
});
|
|
288
|
+
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "decline"));
|
|
255
289
|
this.emitToolResult(pending.toolUseId, "Rejected");
|
|
256
290
|
if (this.pendingApprovals.size === 0) {
|
|
257
291
|
this.setStatus("running");
|
|
@@ -264,9 +298,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
264
298
|
return;
|
|
265
299
|
}
|
|
266
300
|
this.pendingUserInputs.delete(pending.toolUseId);
|
|
267
|
-
this.respondToServerRequest(pending.requestId,
|
|
268
|
-
answers: buildUserInputAnswers(pending.questions, result),
|
|
269
|
-
});
|
|
301
|
+
this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, result));
|
|
270
302
|
this.emitToolResult(pending.toolUseId, "Answered");
|
|
271
303
|
if (this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
|
|
272
304
|
this.setStatus("running");
|
|
@@ -378,21 +410,13 @@ export class CodexProcess extends EventEmitter {
|
|
|
378
410
|
}
|
|
379
411
|
async bootstrap(projectPath, options) {
|
|
380
412
|
try {
|
|
381
|
-
await this.
|
|
382
|
-
clientInfo: {
|
|
383
|
-
name: "ccpocket_bridge",
|
|
384
|
-
version: "1.0.0",
|
|
385
|
-
title: "ccpocket bridge",
|
|
386
|
-
},
|
|
387
|
-
capabilities: {
|
|
388
|
-
experimentalApi: true,
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
this.notify("initialized", {});
|
|
413
|
+
await this.initializeRpcConnection();
|
|
392
414
|
const threadParams = {
|
|
393
415
|
cwd: projectPath,
|
|
394
416
|
approvalPolicy: normalizeApprovalPolicy(options?.approvalPolicy ?? "never"),
|
|
395
417
|
sandbox: normalizeSandboxMode(options?.sandboxMode ?? "workspace-write"),
|
|
418
|
+
experimentalRawEvents: false,
|
|
419
|
+
persistExtendedHistory: true,
|
|
396
420
|
};
|
|
397
421
|
if (options?.model)
|
|
398
422
|
threadParams.model = options.model;
|
|
@@ -412,6 +436,12 @@ export class CodexProcess extends EventEmitter {
|
|
|
412
436
|
if (options?.threadId) {
|
|
413
437
|
threadParams.threadId = options.threadId;
|
|
414
438
|
}
|
|
439
|
+
else {
|
|
440
|
+
threadParams.experimentalRawEvents = false;
|
|
441
|
+
}
|
|
442
|
+
if (options?.threadId) {
|
|
443
|
+
threadParams.persistExtendedHistory = true;
|
|
444
|
+
}
|
|
415
445
|
const response = await this.request(method, threadParams);
|
|
416
446
|
const thread = response.thread;
|
|
417
447
|
const threadId = typeof thread?.id === "string"
|
|
@@ -448,6 +478,19 @@ export class CodexProcess extends EventEmitter {
|
|
|
448
478
|
this.emit("exit", 1);
|
|
449
479
|
}
|
|
450
480
|
}
|
|
481
|
+
async initializeRpcConnection() {
|
|
482
|
+
await this.request("initialize", {
|
|
483
|
+
clientInfo: {
|
|
484
|
+
name: "ccpocket_bridge",
|
|
485
|
+
version: "1.0.0",
|
|
486
|
+
title: "ccpocket bridge",
|
|
487
|
+
},
|
|
488
|
+
capabilities: {
|
|
489
|
+
experimentalApi: true,
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
this.notify("initialized", {});
|
|
493
|
+
}
|
|
451
494
|
/**
|
|
452
495
|
* Fetch skills from Codex app-server via `skills/list` RPC and emit them
|
|
453
496
|
* as a `supported_commands` system message so the Flutter client can display
|
|
@@ -639,6 +682,11 @@ export class CodexProcess extends EventEmitter {
|
|
|
639
682
|
...(typeof params.cwd === "string" ? { cwd: params.cwd } : {}),
|
|
640
683
|
...(params.commandActions ? { commandActions: params.commandActions } : {}),
|
|
641
684
|
...(params.networkApprovalContext ? { networkApprovalContext: params.networkApprovalContext } : {}),
|
|
685
|
+
...(params.additionalPermissions ? { additionalPermissions: params.additionalPermissions } : {}),
|
|
686
|
+
...(params.skillMetadata ? { skillMetadata: params.skillMetadata } : {}),
|
|
687
|
+
...(params.proposedExecpolicyAmendment ? { proposedExecpolicyAmendment: params.proposedExecpolicyAmendment } : {}),
|
|
688
|
+
...(params.proposedNetworkPolicyAmendments ? { proposedNetworkPolicyAmendments: params.proposedNetworkPolicyAmendments } : {}),
|
|
689
|
+
...(params.availableDecisions ? { availableDecisions: params.availableDecisions } : {}),
|
|
642
690
|
...(typeof params.reason === "string" ? { reason: params.reason } : {}),
|
|
643
691
|
};
|
|
644
692
|
this.pendingApprovals.set(toolUseId, {
|
|
@@ -646,6 +694,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
646
694
|
toolUseId,
|
|
647
695
|
toolName: "Bash",
|
|
648
696
|
input,
|
|
697
|
+
kind: "command",
|
|
649
698
|
});
|
|
650
699
|
this.emitMessage({
|
|
651
700
|
type: "permission_request",
|
|
@@ -660,6 +709,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
660
709
|
const toolUseId = this.extractToolUseId(params, id);
|
|
661
710
|
const input = {
|
|
662
711
|
...(Array.isArray(params.changes) ? { changes: params.changes } : {}),
|
|
712
|
+
...(typeof params.grantRoot === "string" ? { grantRoot: params.grantRoot } : {}),
|
|
663
713
|
...(typeof params.reason === "string" ? { reason: params.reason } : {}),
|
|
664
714
|
};
|
|
665
715
|
this.pendingApprovals.set(toolUseId, {
|
|
@@ -667,6 +717,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
667
717
|
toolUseId,
|
|
668
718
|
toolName: "FileChange",
|
|
669
719
|
input,
|
|
720
|
+
kind: "file",
|
|
670
721
|
});
|
|
671
722
|
this.emitMessage({
|
|
672
723
|
type: "permission_request",
|
|
@@ -694,11 +745,13 @@ export class CodexProcess extends EventEmitter {
|
|
|
694
745
|
this.pendingUserInputs.set(toolUseId, {
|
|
695
746
|
requestId: id,
|
|
696
747
|
toolUseId,
|
|
748
|
+
toolName: "AskUserQuestion",
|
|
697
749
|
questions: questions.map((q) => ({
|
|
698
750
|
id: q.id,
|
|
699
751
|
question: q.question,
|
|
700
752
|
})),
|
|
701
753
|
input,
|
|
754
|
+
kind: "questions",
|
|
702
755
|
});
|
|
703
756
|
this.emitMessage({
|
|
704
757
|
type: "permission_request",
|
|
@@ -709,6 +762,50 @@ export class CodexProcess extends EventEmitter {
|
|
|
709
762
|
this.setStatus("waiting_approval");
|
|
710
763
|
break;
|
|
711
764
|
}
|
|
765
|
+
case "item/permissions/requestApproval": {
|
|
766
|
+
const toolUseId = this.extractToolUseId(params, id);
|
|
767
|
+
const requestedPermissions = asRecord(params.permissions) ?? {};
|
|
768
|
+
const input = {
|
|
769
|
+
permissions: requestedPermissions,
|
|
770
|
+
...(typeof params.reason === "string" ? { reason: params.reason } : {}),
|
|
771
|
+
};
|
|
772
|
+
this.pendingApprovals.set(toolUseId, {
|
|
773
|
+
requestId: id,
|
|
774
|
+
toolUseId,
|
|
775
|
+
toolName: "Permissions",
|
|
776
|
+
input,
|
|
777
|
+
kind: "permissions",
|
|
778
|
+
requestedPermissions,
|
|
779
|
+
});
|
|
780
|
+
this.emitMessage({
|
|
781
|
+
type: "permission_request",
|
|
782
|
+
toolUseId,
|
|
783
|
+
toolName: "Permissions",
|
|
784
|
+
input,
|
|
785
|
+
});
|
|
786
|
+
this.setStatus("waiting_approval");
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
case "mcpServer/elicitation/request": {
|
|
790
|
+
const toolUseId = this.extractToolUseId(params, id);
|
|
791
|
+
const elicitation = createElicitationInput(params);
|
|
792
|
+
this.pendingUserInputs.set(toolUseId, {
|
|
793
|
+
requestId: id,
|
|
794
|
+
toolUseId,
|
|
795
|
+
toolName: "McpElicitation",
|
|
796
|
+
questions: elicitation.questions,
|
|
797
|
+
input: elicitation.input,
|
|
798
|
+
kind: elicitation.kind,
|
|
799
|
+
});
|
|
800
|
+
this.emitMessage({
|
|
801
|
+
type: "permission_request",
|
|
802
|
+
toolUseId,
|
|
803
|
+
toolName: "McpElicitation",
|
|
804
|
+
input: elicitation.input,
|
|
805
|
+
});
|
|
806
|
+
this.setStatus("waiting_approval");
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
712
809
|
default:
|
|
713
810
|
this.respondToServerRequest(id, {});
|
|
714
811
|
break;
|
|
@@ -721,6 +818,8 @@ export class CodexProcess extends EventEmitter {
|
|
|
721
818
|
if (typeof thread?.id === "string") {
|
|
722
819
|
this._threadId = thread.id;
|
|
723
820
|
}
|
|
821
|
+
this._agentNickname = stringOrNull(thread?.agentNickname);
|
|
822
|
+
this._agentRole = stringOrNull(thread?.agentRole);
|
|
724
823
|
break;
|
|
725
824
|
}
|
|
726
825
|
case "turn/started": {
|
|
@@ -811,6 +910,10 @@ export class CodexProcess extends EventEmitter {
|
|
|
811
910
|
});
|
|
812
911
|
break;
|
|
813
912
|
}
|
|
913
|
+
case "serverRequest/resolved": {
|
|
914
|
+
this.handleServerRequestResolved(params);
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
814
917
|
default:
|
|
815
918
|
break;
|
|
816
919
|
}
|
|
@@ -928,6 +1031,56 @@ export class CodexProcess extends EventEmitter {
|
|
|
928
1031
|
});
|
|
929
1032
|
break;
|
|
930
1033
|
}
|
|
1034
|
+
case "dynamictoolcall": {
|
|
1035
|
+
const tool = typeof item.tool === "string" ? item.tool : "DynamicTool";
|
|
1036
|
+
this.emitMessage({
|
|
1037
|
+
type: "assistant",
|
|
1038
|
+
message: {
|
|
1039
|
+
id: itemId,
|
|
1040
|
+
role: "assistant",
|
|
1041
|
+
content: [
|
|
1042
|
+
{
|
|
1043
|
+
type: "tool_use",
|
|
1044
|
+
id: itemId,
|
|
1045
|
+
name: tool,
|
|
1046
|
+
input: toToolUseInput(item.arguments),
|
|
1047
|
+
},
|
|
1048
|
+
],
|
|
1049
|
+
model: "codex",
|
|
1050
|
+
},
|
|
1051
|
+
});
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
case "collabagenttoolcall": {
|
|
1055
|
+
const tool = typeof item.tool === "string" ? item.tool : "subagent";
|
|
1056
|
+
const toolName = "SubAgent";
|
|
1057
|
+
const input = {
|
|
1058
|
+
tool,
|
|
1059
|
+
...(typeof item.prompt === "string" ? { prompt: item.prompt } : {}),
|
|
1060
|
+
...(typeof item.senderThreadId === "string" ? { senderThreadId: item.senderThreadId } : {}),
|
|
1061
|
+
...(Array.isArray(item.receiverThreadIds) ? { receiverThreadIds: item.receiverThreadIds } : {}),
|
|
1062
|
+
...(typeof item.model === "string" ? { model: item.model } : {}),
|
|
1063
|
+
...(typeof item.reasoningEffort === "string" ? { reasoningEffort: item.reasoningEffort } : {}),
|
|
1064
|
+
...(item.agentsStates ? { agentsStates: item.agentsStates } : {}),
|
|
1065
|
+
};
|
|
1066
|
+
this.emitMessage({
|
|
1067
|
+
type: "assistant",
|
|
1068
|
+
message: {
|
|
1069
|
+
id: itemId,
|
|
1070
|
+
role: "assistant",
|
|
1071
|
+
content: [
|
|
1072
|
+
{
|
|
1073
|
+
type: "tool_use",
|
|
1074
|
+
id: itemId,
|
|
1075
|
+
name: toolName,
|
|
1076
|
+
input,
|
|
1077
|
+
},
|
|
1078
|
+
],
|
|
1079
|
+
model: "codex",
|
|
1080
|
+
},
|
|
1081
|
+
});
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
931
1084
|
default:
|
|
932
1085
|
break;
|
|
933
1086
|
}
|
|
@@ -990,6 +1143,7 @@ export class CodexProcess extends EventEmitter {
|
|
|
990
1143
|
const tool = typeof item.tool === "string" ? item.tool : "unknown";
|
|
991
1144
|
const toolName = `mcp:${server}/${tool}`;
|
|
992
1145
|
const result = item.result ?? item.error ?? "MCP call completed";
|
|
1146
|
+
const normalized = normalizeMcpToolResult(result);
|
|
993
1147
|
this.emitMessage({
|
|
994
1148
|
type: "assistant",
|
|
995
1149
|
message: {
|
|
@@ -1009,8 +1163,22 @@ export class CodexProcess extends EventEmitter {
|
|
|
1009
1163
|
this.emitMessage({
|
|
1010
1164
|
type: "tool_result",
|
|
1011
1165
|
toolUseId: itemId,
|
|
1012
|
-
content:
|
|
1166
|
+
content: normalized.content,
|
|
1013
1167
|
toolName,
|
|
1168
|
+
...(normalized.rawContentBlocks.length > 0
|
|
1169
|
+
? { rawContentBlocks: normalized.rawContentBlocks }
|
|
1170
|
+
: {}),
|
|
1171
|
+
});
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
case "dynamictoolcall": {
|
|
1175
|
+
const tool = typeof item.tool === "string" ? item.tool : "DynamicTool";
|
|
1176
|
+
const content = formatDynamicToolResult(item);
|
|
1177
|
+
this.emitMessage({
|
|
1178
|
+
type: "tool_result",
|
|
1179
|
+
toolUseId: itemId,
|
|
1180
|
+
content,
|
|
1181
|
+
toolName: tool,
|
|
1014
1182
|
});
|
|
1015
1183
|
break;
|
|
1016
1184
|
}
|
|
@@ -1040,6 +1208,27 @@ export class CodexProcess extends EventEmitter {
|
|
|
1040
1208
|
});
|
|
1041
1209
|
break;
|
|
1042
1210
|
}
|
|
1211
|
+
case "collabagenttoolcall": {
|
|
1212
|
+
const tool = typeof item.tool === "string" ? item.tool : "subagent";
|
|
1213
|
+
const status = typeof item.status === "string" ? item.status : "completed";
|
|
1214
|
+
const receiverThreadIds = Array.isArray(item.receiverThreadIds)
|
|
1215
|
+
? item.receiverThreadIds.map((entry) => String(entry))
|
|
1216
|
+
: [];
|
|
1217
|
+
const content = [
|
|
1218
|
+
`tool: ${tool}`,
|
|
1219
|
+
`status: ${status}`,
|
|
1220
|
+
...(receiverThreadIds.length > 0
|
|
1221
|
+
? [`agents: ${receiverThreadIds.join(", ")}`]
|
|
1222
|
+
: []),
|
|
1223
|
+
].join("\n");
|
|
1224
|
+
this.emitMessage({
|
|
1225
|
+
type: "tool_result",
|
|
1226
|
+
toolUseId: itemId,
|
|
1227
|
+
content,
|
|
1228
|
+
toolName: "SubAgent",
|
|
1229
|
+
});
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1043
1232
|
case "plan": {
|
|
1044
1233
|
// Plan item completed — save text for plan approval emission in handleTurnCompleted()
|
|
1045
1234
|
const planText = typeof item.text === "string" ? item.text : "";
|
|
@@ -1150,12 +1339,51 @@ export class CodexProcess extends EventEmitter {
|
|
|
1150
1339
|
extractToolUseId(params, requestId) {
|
|
1151
1340
|
if (typeof params.approvalId === "string")
|
|
1152
1341
|
return params.approvalId;
|
|
1342
|
+
if (typeof params.elicitationId === "string")
|
|
1343
|
+
return params.elicitationId;
|
|
1153
1344
|
if (typeof params.itemId === "string")
|
|
1154
1345
|
return params.itemId;
|
|
1155
1346
|
if (typeof requestId === "string")
|
|
1156
1347
|
return requestId;
|
|
1157
1348
|
return `approval-${requestId}`;
|
|
1158
1349
|
}
|
|
1350
|
+
handleServerRequestResolved(params) {
|
|
1351
|
+
const requestId = params.requestId;
|
|
1352
|
+
if (requestId === undefined || requestId === null)
|
|
1353
|
+
return;
|
|
1354
|
+
const approval = [...this.pendingApprovals.values()].find((entry) => entry.requestId === requestId);
|
|
1355
|
+
if (approval) {
|
|
1356
|
+
this.pendingApprovals.delete(approval.toolUseId);
|
|
1357
|
+
this.emitMessage({ type: "permission_resolved", toolUseId: approval.toolUseId });
|
|
1358
|
+
}
|
|
1359
|
+
const inputRequest = [...this.pendingUserInputs.values()].find((entry) => entry.requestId === requestId);
|
|
1360
|
+
if (inputRequest) {
|
|
1361
|
+
this.pendingUserInputs.delete(inputRequest.toolUseId);
|
|
1362
|
+
this.emitMessage({ type: "permission_resolved", toolUseId: inputRequest.toolUseId });
|
|
1363
|
+
}
|
|
1364
|
+
if (!this.pendingPlanCompletion && this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
|
|
1365
|
+
this.setStatus(this.pendingTurnId ? "running" : "idle");
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
function buildApprovalResponse(pending, decision) {
|
|
1370
|
+
if (pending.kind === "permissions") {
|
|
1371
|
+
return {
|
|
1372
|
+
scope: decision === "acceptForSession" ? "session" : "turn",
|
|
1373
|
+
permissions: decision === "decline" ? {} : (pending.requestedPermissions ?? {}),
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
decision,
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
function buildUserInputResponse(pending, rawResult) {
|
|
1381
|
+
if (pending.kind === "questions") {
|
|
1382
|
+
return {
|
|
1383
|
+
answers: buildUserInputAnswers(pending.questions, rawResult),
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
return buildElicitationResponse(pending, rawResult);
|
|
1159
1387
|
}
|
|
1160
1388
|
function normalizeApprovalPolicy(value) {
|
|
1161
1389
|
switch (value) {
|
|
@@ -1197,6 +1425,9 @@ function normalizeItemType(raw) {
|
|
|
1197
1425
|
function numberOrUndefined(value) {
|
|
1198
1426
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1199
1427
|
}
|
|
1428
|
+
function stringOrNull(value) {
|
|
1429
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
1430
|
+
}
|
|
1200
1431
|
function summarizeFileChanges(changes) {
|
|
1201
1432
|
if (!Array.isArray(changes) || changes.length === 0) {
|
|
1202
1433
|
return "No file changes";
|
|
@@ -1212,6 +1443,127 @@ function summarizeFileChanges(changes) {
|
|
|
1212
1443
|
})
|
|
1213
1444
|
.join("\n");
|
|
1214
1445
|
}
|
|
1446
|
+
function toToolUseInput(value) {
|
|
1447
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1448
|
+
return value;
|
|
1449
|
+
}
|
|
1450
|
+
if (Array.isArray(value)) {
|
|
1451
|
+
return { items: value };
|
|
1452
|
+
}
|
|
1453
|
+
if (value === undefined || value === null) {
|
|
1454
|
+
return {};
|
|
1455
|
+
}
|
|
1456
|
+
return { value };
|
|
1457
|
+
}
|
|
1458
|
+
function formatDynamicToolResult(item) {
|
|
1459
|
+
const status = typeof item.status === "string" ? item.status : "completed";
|
|
1460
|
+
const success = typeof item.success === "boolean" ? item.success : null;
|
|
1461
|
+
const contentItems = Array.isArray(item.contentItems) ? item.contentItems : null;
|
|
1462
|
+
const parts = [
|
|
1463
|
+
`status: ${status}`,
|
|
1464
|
+
...(success != null ? [`success: ${success}`] : []),
|
|
1465
|
+
];
|
|
1466
|
+
if (contentItems && contentItems.length > 0) {
|
|
1467
|
+
for (const entry of contentItems) {
|
|
1468
|
+
if (!entry || typeof entry !== "object")
|
|
1469
|
+
continue;
|
|
1470
|
+
const record = entry;
|
|
1471
|
+
const type = typeof record.type === "string" ? record.type : "item";
|
|
1472
|
+
if (type === "inputText" && typeof record.text === "string") {
|
|
1473
|
+
parts.push(record.text);
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
if (type === "inputImage" && typeof record.imageUrl === "string") {
|
|
1477
|
+
parts.push(`image: ${record.imageUrl}`);
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
parts.push(JSON.stringify(record));
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
return parts.join("\n");
|
|
1484
|
+
}
|
|
1485
|
+
function normalizeMcpToolResult(result) {
|
|
1486
|
+
if (typeof result === "string") {
|
|
1487
|
+
return { content: result, rawContentBlocks: [] };
|
|
1488
|
+
}
|
|
1489
|
+
const record = result && typeof result === "object" && !Array.isArray(result)
|
|
1490
|
+
? result
|
|
1491
|
+
: null;
|
|
1492
|
+
const contentItems = Array.isArray(record?.content) ? record.content : null;
|
|
1493
|
+
if (!contentItems) {
|
|
1494
|
+
return {
|
|
1495
|
+
content: result == null ? "MCP call completed" : JSON.stringify(result),
|
|
1496
|
+
rawContentBlocks: [],
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
const textParts = [];
|
|
1500
|
+
const rawContentBlocks = [];
|
|
1501
|
+
for (const entry of contentItems) {
|
|
1502
|
+
if (!entry || typeof entry !== "object")
|
|
1503
|
+
continue;
|
|
1504
|
+
const item = entry;
|
|
1505
|
+
const type = typeof item.type === "string" ? item.type : "";
|
|
1506
|
+
if (type === "text" && typeof item.text === "string") {
|
|
1507
|
+
textParts.push(item.text);
|
|
1508
|
+
rawContentBlocks.push({ type: "text", text: item.text });
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
if (type === "image" && typeof item.data === "string") {
|
|
1512
|
+
const mimeType = typeof item.mimeType === "string"
|
|
1513
|
+
? item.mimeType
|
|
1514
|
+
: typeof item.mediaType === "string"
|
|
1515
|
+
? item.mediaType
|
|
1516
|
+
: typeof item.media_type === "string"
|
|
1517
|
+
? item.media_type
|
|
1518
|
+
: "image/png";
|
|
1519
|
+
rawContentBlocks.push({
|
|
1520
|
+
type: "image",
|
|
1521
|
+
source: {
|
|
1522
|
+
type: "base64",
|
|
1523
|
+
data: item.data,
|
|
1524
|
+
media_type: mimeType,
|
|
1525
|
+
},
|
|
1526
|
+
});
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
rawContentBlocks.push(item);
|
|
1530
|
+
textParts.push(JSON.stringify(item));
|
|
1531
|
+
}
|
|
1532
|
+
const content = textParts.join("\n").trim();
|
|
1533
|
+
if (content.length > 0) {
|
|
1534
|
+
return { content, rawContentBlocks };
|
|
1535
|
+
}
|
|
1536
|
+
const imageCount = rawContentBlocks.filter((entry) => entry.type === "image").length;
|
|
1537
|
+
if (imageCount > 0) {
|
|
1538
|
+
return {
|
|
1539
|
+
content: imageCount === 1 ? "Generated 1 image" : `Generated ${imageCount} images`,
|
|
1540
|
+
rawContentBlocks,
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
return {
|
|
1544
|
+
content: result == null ? "MCP call completed" : JSON.stringify(result),
|
|
1545
|
+
rawContentBlocks,
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
function toCodexThreadSummary(entry) {
|
|
1549
|
+
const record = entry && typeof entry === "object"
|
|
1550
|
+
? entry
|
|
1551
|
+
: {};
|
|
1552
|
+
const gitInfo = record.gitInfo && typeof record.gitInfo === "object"
|
|
1553
|
+
? record.gitInfo
|
|
1554
|
+
: {};
|
|
1555
|
+
return {
|
|
1556
|
+
id: typeof record.id === "string" ? record.id : "",
|
|
1557
|
+
preview: typeof record.preview === "string" ? record.preview : "",
|
|
1558
|
+
createdAt: numberOrUndefined(record.createdAt) ?? 0,
|
|
1559
|
+
updatedAt: numberOrUndefined(record.updatedAt) ?? 0,
|
|
1560
|
+
cwd: typeof record.cwd === "string" ? record.cwd : "",
|
|
1561
|
+
agentNickname: stringOrNull(record.agentNickname),
|
|
1562
|
+
agentRole: stringOrNull(record.agentRole),
|
|
1563
|
+
gitBranch: stringOrNull(gitInfo.branch),
|
|
1564
|
+
name: stringOrNull(record.name),
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1215
1567
|
/**
|
|
1216
1568
|
* Format file changes including unified diff content for display in chat.
|
|
1217
1569
|
* Falls back to `kind: path` summary when no diff is available.
|
|
@@ -1367,6 +1719,170 @@ function normalizeAnswerValues(value) {
|
|
|
1367
1719
|
const normalized = String(value).trim();
|
|
1368
1720
|
return normalized ? [normalized] : [];
|
|
1369
1721
|
}
|
|
1722
|
+
function buildElicitationResponse(pending, rawResult) {
|
|
1723
|
+
if (pending.kind === "elicitation_url") {
|
|
1724
|
+
const action = parseElicitationAction(rawResult);
|
|
1725
|
+
return {
|
|
1726
|
+
action,
|
|
1727
|
+
content: null,
|
|
1728
|
+
_meta: null,
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
const parsed = parseResultObject(rawResult);
|
|
1732
|
+
const content = {};
|
|
1733
|
+
for (const question of pending.questions) {
|
|
1734
|
+
const candidate = parsed.byId[question.id] ?? parsed.byQuestion[question.question];
|
|
1735
|
+
const answers = normalizeAnswerValues(candidate);
|
|
1736
|
+
if (answers.length === 1) {
|
|
1737
|
+
content[question.id] = answers[0];
|
|
1738
|
+
}
|
|
1739
|
+
else if (answers.length > 1) {
|
|
1740
|
+
content[question.id] = answers;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (Object.keys(content).length === 0 && pending.questions.length === 1) {
|
|
1744
|
+
const answers = normalizeAnswerValues(rawResult);
|
|
1745
|
+
if (answers.length === 1) {
|
|
1746
|
+
content[pending.questions[0].id] = answers[0];
|
|
1747
|
+
}
|
|
1748
|
+
else if (answers.length > 1) {
|
|
1749
|
+
content[pending.questions[0].id] = answers;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
return {
|
|
1753
|
+
action: "accept",
|
|
1754
|
+
content: Object.keys(content).length > 0 ? content : null,
|
|
1755
|
+
_meta: null,
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
function parseElicitationAction(rawResult) {
|
|
1759
|
+
const normalized = rawResult.trim().toLowerCase();
|
|
1760
|
+
if (normalized.includes("cancel"))
|
|
1761
|
+
return "cancel";
|
|
1762
|
+
if (normalized.includes("decline") || normalized.includes("deny"))
|
|
1763
|
+
return "decline";
|
|
1764
|
+
try {
|
|
1765
|
+
const parsed = JSON.parse(rawResult);
|
|
1766
|
+
const answers = parsed.answers;
|
|
1767
|
+
if (answers && typeof answers === "object" && !Array.isArray(answers)) {
|
|
1768
|
+
const first = Object.values(answers)[0];
|
|
1769
|
+
const answer = normalizeAnswerValues(first).join(" ").toLowerCase();
|
|
1770
|
+
if (answer.includes("cancel"))
|
|
1771
|
+
return "cancel";
|
|
1772
|
+
if (answer.includes("decline") || answer.includes("deny"))
|
|
1773
|
+
return "decline";
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
catch {
|
|
1777
|
+
// Fall through to accept.
|
|
1778
|
+
}
|
|
1779
|
+
return "accept";
|
|
1780
|
+
}
|
|
1781
|
+
function createElicitationInput(params) {
|
|
1782
|
+
const serverName = typeof params.serverName === "string" ? params.serverName : "MCP";
|
|
1783
|
+
const message = typeof params.message === "string" ? params.message : "Provide input";
|
|
1784
|
+
if (params.mode === "url") {
|
|
1785
|
+
const url = typeof params.url === "string" ? params.url : "";
|
|
1786
|
+
const question = url ? `${message}\n${url}` : message;
|
|
1787
|
+
return {
|
|
1788
|
+
kind: "elicitation_url",
|
|
1789
|
+
questions: [{ id: "elicitation_action", question }],
|
|
1790
|
+
input: {
|
|
1791
|
+
mode: "url",
|
|
1792
|
+
serverName,
|
|
1793
|
+
url,
|
|
1794
|
+
message,
|
|
1795
|
+
questions: [
|
|
1796
|
+
{
|
|
1797
|
+
id: "elicitation_action",
|
|
1798
|
+
header: serverName,
|
|
1799
|
+
question,
|
|
1800
|
+
options: [
|
|
1801
|
+
{ label: "Accept", description: "Continue with this request" },
|
|
1802
|
+
{ label: "Decline", description: "Reject this request" },
|
|
1803
|
+
{ label: "Cancel", description: "Cancel without accepting" },
|
|
1804
|
+
],
|
|
1805
|
+
multiSelect: false,
|
|
1806
|
+
isOther: false,
|
|
1807
|
+
isSecret: false,
|
|
1808
|
+
},
|
|
1809
|
+
],
|
|
1810
|
+
},
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
const schema = asRecord(params.requestedSchema);
|
|
1814
|
+
const properties = asRecord(schema?.properties) ?? {};
|
|
1815
|
+
const requiredFields = new Set(Array.isArray(schema?.required)
|
|
1816
|
+
? schema.required.map((entry) => String(entry))
|
|
1817
|
+
: []);
|
|
1818
|
+
const questions = Object.entries(properties)
|
|
1819
|
+
.filter(([, value]) => value && typeof value === "object")
|
|
1820
|
+
.map(([key, value]) => {
|
|
1821
|
+
const field = value;
|
|
1822
|
+
const title = typeof field.title === "string" ? field.title : key;
|
|
1823
|
+
const description = typeof field.description === "string" ? field.description : message;
|
|
1824
|
+
const enumValues = Array.isArray(field.enum) ? field.enum.map((entry) => String(entry)) : [];
|
|
1825
|
+
const type = typeof field.type === "string" ? field.type : "";
|
|
1826
|
+
const options = enumValues.length > 0
|
|
1827
|
+
? enumValues.map((entry, index) => ({
|
|
1828
|
+
label: entry,
|
|
1829
|
+
description: index === 0 ? description : "",
|
|
1830
|
+
}))
|
|
1831
|
+
: type === "boolean"
|
|
1832
|
+
? [
|
|
1833
|
+
{ label: "true", description: description },
|
|
1834
|
+
{ label: "false", description: "" },
|
|
1835
|
+
]
|
|
1836
|
+
: [];
|
|
1837
|
+
return {
|
|
1838
|
+
id: key,
|
|
1839
|
+
question: requiredFields.has(key) ? `${title} (required)` : title,
|
|
1840
|
+
header: serverName,
|
|
1841
|
+
options,
|
|
1842
|
+
isOther: options.length === 0,
|
|
1843
|
+
isSecret: false,
|
|
1844
|
+
};
|
|
1845
|
+
});
|
|
1846
|
+
const normalizedQuestions = questions.length > 0
|
|
1847
|
+
? questions
|
|
1848
|
+
: [
|
|
1849
|
+
{
|
|
1850
|
+
id: "value",
|
|
1851
|
+
question: message,
|
|
1852
|
+
header: serverName,
|
|
1853
|
+
options: [],
|
|
1854
|
+
isOther: true,
|
|
1855
|
+
isSecret: false,
|
|
1856
|
+
},
|
|
1857
|
+
];
|
|
1858
|
+
return {
|
|
1859
|
+
kind: "elicitation_form",
|
|
1860
|
+
questions: normalizedQuestions.map((question) => ({
|
|
1861
|
+
id: question.id,
|
|
1862
|
+
question: question.question,
|
|
1863
|
+
})),
|
|
1864
|
+
input: {
|
|
1865
|
+
mode: "form",
|
|
1866
|
+
serverName,
|
|
1867
|
+
message,
|
|
1868
|
+
requestedSchema: schema,
|
|
1869
|
+
questions: normalizedQuestions.map((question) => ({
|
|
1870
|
+
id: question.id,
|
|
1871
|
+
header: question.header,
|
|
1872
|
+
question: question.question,
|
|
1873
|
+
options: question.options,
|
|
1874
|
+
multiSelect: false,
|
|
1875
|
+
isOther: question.isOther,
|
|
1876
|
+
isSecret: question.isSecret,
|
|
1877
|
+
})),
|
|
1878
|
+
},
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
function asRecord(value) {
|
|
1882
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
1883
|
+
? value
|
|
1884
|
+
: undefined;
|
|
1885
|
+
}
|
|
1370
1886
|
function formatPlanUpdateText(params) {
|
|
1371
1887
|
const stepsRaw = params.plan;
|
|
1372
1888
|
if (!Array.isArray(stepsRaw) || stepsRaw.length === 0)
|