@ccpocket-base-auth/bridge 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/README.md +67 -0
- package/dist/archive-store.d.ts +28 -0
- package/dist/archive-store.js +68 -0
- package/dist/archive-store.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-process.d.ts +171 -0
- package/dist/codex-process.js +1928 -0
- package/dist/codex-process.js.map +1 -0
- package/dist/debug-trace-store.d.ts +15 -0
- package/dist/debug-trace-store.js +78 -0
- package/dist/debug-trace-store.js.map +1 -0
- package/dist/doctor.d.ts +58 -0
- package/dist/doctor.js +663 -0
- package/dist/doctor.js.map +1 -0
- package/dist/firebase-auth.d.ts +35 -0
- package/dist/firebase-auth.js +132 -0
- package/dist/firebase-auth.js.map +1 -0
- package/dist/gallery-store.d.ts +67 -0
- package/dist/gallery-store.js +333 -0
- package/dist/gallery-store.js.map +1 -0
- package/dist/image-store.d.ts +23 -0
- package/dist/image-store.js +142 -0
- package/dist/image-store.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/mdns.d.ts +7 -0
- package/dist/mdns.js +49 -0
- package/dist/mdns.js.map +1 -0
- package/dist/parser.d.ts +465 -0
- package/dist/parser.js +251 -0
- package/dist/parser.js.map +1 -0
- package/dist/project-history.d.ts +10 -0
- package/dist/project-history.js +73 -0
- package/dist/project-history.js.map +1 -0
- package/dist/prompt-history-backup.d.ts +15 -0
- package/dist/prompt-history-backup.js +46 -0
- package/dist/prompt-history-backup.js.map +1 -0
- package/dist/proxy.d.ts +15 -0
- package/dist/proxy.js +95 -0
- package/dist/proxy.js.map +1 -0
- package/dist/push-i18n.d.ts +7 -0
- package/dist/push-i18n.js +75 -0
- package/dist/push-i18n.js.map +1 -0
- package/dist/push-relay.d.ts +29 -0
- package/dist/push-relay.js +70 -0
- package/dist/push-relay.js.map +1 -0
- package/dist/recording-store.d.ts +51 -0
- package/dist/recording-store.js +158 -0
- package/dist/recording-store.js.map +1 -0
- package/dist/screenshot.d.ts +28 -0
- package/dist/screenshot.js +98 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/sdk-process.d.ts +180 -0
- package/dist/sdk-process.js +937 -0
- package/dist/sdk-process.js.map +1 -0
- package/dist/session.d.ts +142 -0
- package/dist/session.js +615 -0
- package/dist/session.js.map +1 -0
- package/dist/sessions-index.d.ts +128 -0
- package/dist/sessions-index.js +1767 -0
- package/dist/sessions-index.js.map +1 -0
- package/dist/setup-launchd.d.ts +8 -0
- package/dist/setup-launchd.js +109 -0
- package/dist/setup-launchd.js.map +1 -0
- package/dist/setup-systemd.d.ts +8 -0
- package/dist/setup-systemd.js +118 -0
- package/dist/setup-systemd.js.map +1 -0
- package/dist/startup-info.d.ts +8 -0
- package/dist/startup-info.js +92 -0
- package/dist/startup-info.js.map +1 -0
- package/dist/usage.d.ts +69 -0
- package/dist/usage.js +545 -0
- package/dist/usage.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +43 -0
- package/dist/version.js.map +1 -0
- package/dist/websocket.d.ts +127 -0
- package/dist/websocket.js +2482 -0
- package/dist/websocket.js.map +1 -0
- package/dist/worktree-store.d.ts +25 -0
- package/dist/worktree-store.js +59 -0
- package/dist/worktree-store.js.map +1 -0
- package/dist/worktree.d.ts +47 -0
- package/dist/worktree.js +313 -0
- package/dist/worktree.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,1928 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
export class CodexProcess extends EventEmitter {
|
|
8
|
+
child = null;
|
|
9
|
+
_status = "starting";
|
|
10
|
+
_threadId = null;
|
|
11
|
+
_agentNickname = null;
|
|
12
|
+
_agentRole = null;
|
|
13
|
+
stopped = false;
|
|
14
|
+
startModel;
|
|
15
|
+
inputResolve = null;
|
|
16
|
+
pendingTurnId = null;
|
|
17
|
+
pendingTurnCompletion = null;
|
|
18
|
+
pendingApprovals = new Map();
|
|
19
|
+
pendingUserInputs = new Map();
|
|
20
|
+
lastTokenUsage = null;
|
|
21
|
+
/** Full skill metadata from the last `skills/list` response. */
|
|
22
|
+
_skills = [];
|
|
23
|
+
/** Project path stored for re-fetching skills on `skills/changed`. */
|
|
24
|
+
_projectPath = null;
|
|
25
|
+
/** Expose skill metadata so session/websocket can access it. */
|
|
26
|
+
get skills() {
|
|
27
|
+
return this._skills;
|
|
28
|
+
}
|
|
29
|
+
rpcSeq = 1;
|
|
30
|
+
pendingRpc = new Map();
|
|
31
|
+
stdoutBuffer = "";
|
|
32
|
+
// Collaboration mode & plan completion state
|
|
33
|
+
_approvalPolicy = "never";
|
|
34
|
+
_collaborationMode = "default";
|
|
35
|
+
lastPlanItemText = null;
|
|
36
|
+
pendingPlanCompletion = null;
|
|
37
|
+
/** Queued plan execution text when inputResolve wasn't ready at approval time. */
|
|
38
|
+
_pendingPlanInput = null;
|
|
39
|
+
get status() {
|
|
40
|
+
return this._status;
|
|
41
|
+
}
|
|
42
|
+
get isWaitingForInput() {
|
|
43
|
+
return this.inputResolve !== null;
|
|
44
|
+
}
|
|
45
|
+
get sessionId() {
|
|
46
|
+
return this._threadId;
|
|
47
|
+
}
|
|
48
|
+
get agentNickname() {
|
|
49
|
+
return this._agentNickname;
|
|
50
|
+
}
|
|
51
|
+
get agentRole() {
|
|
52
|
+
return this._agentRole;
|
|
53
|
+
}
|
|
54
|
+
get isRunning() {
|
|
55
|
+
return this.child !== null;
|
|
56
|
+
}
|
|
57
|
+
get approvalPolicy() {
|
|
58
|
+
return this._approvalPolicy;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Update approval policy at runtime.
|
|
62
|
+
* Takes effect on the next `turn/start` RPC call.
|
|
63
|
+
*/
|
|
64
|
+
setApprovalPolicy(policy) {
|
|
65
|
+
this._approvalPolicy = policy;
|
|
66
|
+
console.log(`[codex-process] Approval policy changed to: ${policy}`);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Set collaboration mode ("plan" or "default").
|
|
70
|
+
* Takes effect on the next `turn/start` RPC call.
|
|
71
|
+
*/
|
|
72
|
+
setCollaborationMode(mode) {
|
|
73
|
+
this._collaborationMode = mode;
|
|
74
|
+
console.log(`[codex-process] Collaboration mode changed to: ${mode}`);
|
|
75
|
+
}
|
|
76
|
+
get collaborationMode() {
|
|
77
|
+
return this._collaborationMode;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Rename a thread via the app-server RPC.
|
|
81
|
+
* Sends thread/name/set which persists to ~/.codex/session_index.jsonl.
|
|
82
|
+
*/
|
|
83
|
+
async renameThread(name) {
|
|
84
|
+
if (!this._threadId) {
|
|
85
|
+
throw new Error("No thread ID available for rename");
|
|
86
|
+
}
|
|
87
|
+
await this.request("thread/name/set", {
|
|
88
|
+
threadId: this._threadId,
|
|
89
|
+
name,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Archive a Codex thread via the app-server `thread/archive` RPC.
|
|
94
|
+
* Accepts an explicit threadId so that historical (non-active) sessions
|
|
95
|
+
* can be archived without requiring a running process.
|
|
96
|
+
*/
|
|
97
|
+
async archiveThread(threadId) {
|
|
98
|
+
await this.request("thread/archive", { threadId });
|
|
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
|
+
}
|
|
117
|
+
start(projectPath, options) {
|
|
118
|
+
if (this.child) {
|
|
119
|
+
this.stop();
|
|
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) {
|
|
151
|
+
this.stopped = false;
|
|
152
|
+
this._threadId = null;
|
|
153
|
+
this._agentNickname = null;
|
|
154
|
+
this._agentRole = null;
|
|
155
|
+
this.pendingTurnId = null;
|
|
156
|
+
this.pendingTurnCompletion = null;
|
|
157
|
+
this.pendingApprovals.clear();
|
|
158
|
+
this.pendingUserInputs.clear();
|
|
159
|
+
this.lastTokenUsage = null;
|
|
160
|
+
this.startModel = options?.model;
|
|
161
|
+
this._approvalPolicy = options?.approvalPolicy ?? "never";
|
|
162
|
+
this._collaborationMode = options?.collaborationMode ?? "default";
|
|
163
|
+
this.lastPlanItemText = null;
|
|
164
|
+
this.pendingPlanCompletion = null;
|
|
165
|
+
this._pendingPlanInput = null;
|
|
166
|
+
this._projectPath = projectPath;
|
|
167
|
+
}
|
|
168
|
+
launchAppServer(projectPath, options) {
|
|
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})`);
|
|
170
|
+
const child = spawn("codex", ["app-server", "--listen", "stdio://"], {
|
|
171
|
+
cwd: projectPath,
|
|
172
|
+
stdio: "pipe",
|
|
173
|
+
env: process.env,
|
|
174
|
+
});
|
|
175
|
+
this.child = child;
|
|
176
|
+
child.stdout.setEncoding("utf8");
|
|
177
|
+
child.stdout.on("data", (chunk) => {
|
|
178
|
+
this.handleStdoutChunk(chunk);
|
|
179
|
+
});
|
|
180
|
+
child.stderr.setEncoding("utf8");
|
|
181
|
+
child.stderr.on("data", (chunk) => {
|
|
182
|
+
const line = chunk.trim();
|
|
183
|
+
if (line) {
|
|
184
|
+
console.log(`[codex-process] stderr: ${line}`);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
child.on("error", (err) => {
|
|
188
|
+
if (this.stopped)
|
|
189
|
+
return;
|
|
190
|
+
console.error("[codex-process] app-server process error:", err);
|
|
191
|
+
this.emitMessage({ type: "error", message: `Failed to start codex app-server: ${err.message}` });
|
|
192
|
+
this.setStatus("idle");
|
|
193
|
+
this.emit("exit", 1);
|
|
194
|
+
});
|
|
195
|
+
child.on("exit", (code) => {
|
|
196
|
+
const exitCode = code ?? 0;
|
|
197
|
+
this.child = null;
|
|
198
|
+
this.rejectAllPending(new Error("codex app-server exited"));
|
|
199
|
+
if (!this.stopped && exitCode !== 0) {
|
|
200
|
+
this.emitMessage({ type: "error", message: `codex app-server exited with code ${exitCode}` });
|
|
201
|
+
}
|
|
202
|
+
this.setStatus("idle");
|
|
203
|
+
this.emit("exit", code);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
interrupt() {
|
|
207
|
+
if (!this._threadId || !this.pendingTurnId)
|
|
208
|
+
return;
|
|
209
|
+
void this.request("turn/interrupt", {
|
|
210
|
+
threadId: this._threadId,
|
|
211
|
+
turnId: this.pendingTurnId,
|
|
212
|
+
}).catch((err) => {
|
|
213
|
+
if (!this.stopped) {
|
|
214
|
+
console.warn(`[codex-process] turn/interrupt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
sendInput(text) {
|
|
219
|
+
if (!this.inputResolve) {
|
|
220
|
+
console.error("[codex-process] No pending input resolver for sendInput");
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const resolve = this.inputResolve;
|
|
224
|
+
this.inputResolve = null;
|
|
225
|
+
resolve({ text });
|
|
226
|
+
}
|
|
227
|
+
sendInputWithImages(text, images) {
|
|
228
|
+
if (!this.inputResolve) {
|
|
229
|
+
console.error("[codex-process] No pending input resolver for sendInputWithImages");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const resolve = this.inputResolve;
|
|
233
|
+
this.inputResolve = null;
|
|
234
|
+
resolve({ text, images });
|
|
235
|
+
}
|
|
236
|
+
sendInputWithSkill(text, skill) {
|
|
237
|
+
if (!this.inputResolve) {
|
|
238
|
+
console.error("[codex-process] No pending input resolver for sendInputWithSkill");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const resolve = this.inputResolve;
|
|
242
|
+
this.inputResolve = null;
|
|
243
|
+
resolve({ text, skill });
|
|
244
|
+
}
|
|
245
|
+
approve(toolUseId, _updatedInput) {
|
|
246
|
+
// Check if this is a plan completion approval
|
|
247
|
+
if (this.pendingPlanCompletion && toolUseId === this.pendingPlanCompletion.toolUseId) {
|
|
248
|
+
this.handlePlanApproved(_updatedInput);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const pending = this.resolvePendingApproval(toolUseId);
|
|
252
|
+
if (!pending) {
|
|
253
|
+
console.log("[codex-process] approve() called but no pending permission requests");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this.pendingApprovals.delete(pending.toolUseId);
|
|
257
|
+
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "accept"));
|
|
258
|
+
this.emitToolResult(pending.toolUseId, "Approved");
|
|
259
|
+
if (this.pendingApprovals.size === 0) {
|
|
260
|
+
this.setStatus("running");
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
approveAlways(toolUseId) {
|
|
264
|
+
const pending = this.resolvePendingApproval(toolUseId);
|
|
265
|
+
if (!pending) {
|
|
266
|
+
console.log("[codex-process] approveAlways() called but no pending permission requests");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
this.pendingApprovals.delete(pending.toolUseId);
|
|
270
|
+
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "acceptForSession"));
|
|
271
|
+
this.emitToolResult(pending.toolUseId, "Approved (always)");
|
|
272
|
+
if (this.pendingApprovals.size === 0) {
|
|
273
|
+
this.setStatus("running");
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
reject(toolUseId, _message) {
|
|
277
|
+
// Check if this is a plan completion rejection
|
|
278
|
+
if (this.pendingPlanCompletion && toolUseId === this.pendingPlanCompletion.toolUseId) {
|
|
279
|
+
this.handlePlanRejected(_message);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const pending = this.resolvePendingApproval(toolUseId);
|
|
283
|
+
if (!pending) {
|
|
284
|
+
console.log("[codex-process] reject() called but no pending permission requests");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
this.pendingApprovals.delete(pending.toolUseId);
|
|
288
|
+
this.respondToServerRequest(pending.requestId, buildApprovalResponse(pending, "decline"));
|
|
289
|
+
this.emitToolResult(pending.toolUseId, "Rejected");
|
|
290
|
+
if (this.pendingApprovals.size === 0) {
|
|
291
|
+
this.setStatus("running");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
answer(toolUseId, result) {
|
|
295
|
+
const pending = this.resolvePendingUserInput(toolUseId);
|
|
296
|
+
if (!pending) {
|
|
297
|
+
console.log("[codex-process] answer() called but no pending AskUserQuestion");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
this.pendingUserInputs.delete(pending.toolUseId);
|
|
301
|
+
this.respondToServerRequest(pending.requestId, buildUserInputResponse(pending, result));
|
|
302
|
+
this.emitToolResult(pending.toolUseId, "Answered");
|
|
303
|
+
if (this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
|
|
304
|
+
this.setStatus("running");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
getPendingPermission(toolUseId) {
|
|
308
|
+
// Check plan completion first
|
|
309
|
+
if (this.pendingPlanCompletion) {
|
|
310
|
+
if (!toolUseId || toolUseId === this.pendingPlanCompletion.toolUseId) {
|
|
311
|
+
return {
|
|
312
|
+
toolUseId: this.pendingPlanCompletion.toolUseId,
|
|
313
|
+
toolName: "ExitPlanMode",
|
|
314
|
+
input: { plan: this.pendingPlanCompletion.planText },
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const pending = this.resolvePendingApproval(toolUseId);
|
|
319
|
+
if (pending) {
|
|
320
|
+
return {
|
|
321
|
+
toolUseId: pending.toolUseId,
|
|
322
|
+
toolName: pending.toolName,
|
|
323
|
+
input: { ...pending.input },
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const pendingAsk = this.resolvePendingUserInput(toolUseId);
|
|
327
|
+
if (!pendingAsk)
|
|
328
|
+
return undefined;
|
|
329
|
+
return {
|
|
330
|
+
toolUseId: pendingAsk.toolUseId,
|
|
331
|
+
toolName: "AskUserQuestion",
|
|
332
|
+
input: { ...pendingAsk.input },
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/** Emit a synthetic tool_result so history replay can match it to a permission_request. */
|
|
336
|
+
emitToolResult(toolUseId, content) {
|
|
337
|
+
this.emitMessage({
|
|
338
|
+
type: "tool_result",
|
|
339
|
+
toolUseId,
|
|
340
|
+
content,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
resolvePendingApproval(toolUseId) {
|
|
344
|
+
if (toolUseId)
|
|
345
|
+
return this.pendingApprovals.get(toolUseId);
|
|
346
|
+
const first = this.pendingApprovals.values().next();
|
|
347
|
+
return first.done ? undefined : first.value;
|
|
348
|
+
}
|
|
349
|
+
resolvePendingUserInput(toolUseId) {
|
|
350
|
+
if (toolUseId)
|
|
351
|
+
return this.pendingUserInputs.get(toolUseId);
|
|
352
|
+
const first = this.pendingUserInputs.values().next();
|
|
353
|
+
return first.done ? undefined : first.value;
|
|
354
|
+
}
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// Plan completion handlers (native collaboration_mode)
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
/**
|
|
359
|
+
* Plan approved → switch to Default mode and auto-start execution.
|
|
360
|
+
*/
|
|
361
|
+
handlePlanApproved(updatedInput) {
|
|
362
|
+
const planText = updatedInput?.plan ?? this.pendingPlanCompletion?.planText ?? "";
|
|
363
|
+
const resolvedToolUseId = this.pendingPlanCompletion?.toolUseId;
|
|
364
|
+
this.pendingPlanCompletion = null;
|
|
365
|
+
this._collaborationMode = "default";
|
|
366
|
+
console.log("[codex-process] Plan approved, switching to Default mode");
|
|
367
|
+
// Emit synthetic tool_result so history replay knows this approval is resolved
|
|
368
|
+
if (resolvedToolUseId) {
|
|
369
|
+
this.emitToolResult(resolvedToolUseId, "Plan approved");
|
|
370
|
+
}
|
|
371
|
+
// Resolve inputResolve to start the next turn (Default mode) automatically
|
|
372
|
+
if (this.inputResolve) {
|
|
373
|
+
const resolve = this.inputResolve;
|
|
374
|
+
this.inputResolve = null;
|
|
375
|
+
resolve({ text: `Execute the following plan:\n\n${planText}` });
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// inputResolve may not be ready yet if approval comes before the next
|
|
379
|
+
// input loop iteration. Queue the text so sendInput() can pick it up.
|
|
380
|
+
console.warn("[codex-process] Plan approved but inputResolve not ready, queuing as pending input");
|
|
381
|
+
this._pendingPlanInput = `Execute the following plan:\n\n${planText}`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Plan rejected → stay in Plan mode and re-plan with feedback.
|
|
386
|
+
*/
|
|
387
|
+
handlePlanRejected(feedback) {
|
|
388
|
+
const resolvedToolUseId = this.pendingPlanCompletion?.toolUseId;
|
|
389
|
+
this.pendingPlanCompletion = null;
|
|
390
|
+
console.log("[codex-process] Plan rejected, continuing in Plan mode");
|
|
391
|
+
// Stay in Plan mode
|
|
392
|
+
// Emit synthetic tool_result so history replay knows this approval is resolved
|
|
393
|
+
if (resolvedToolUseId) {
|
|
394
|
+
this.emitToolResult(resolvedToolUseId, "Plan rejected");
|
|
395
|
+
}
|
|
396
|
+
if (feedback) {
|
|
397
|
+
if (this.inputResolve) {
|
|
398
|
+
const resolve = this.inputResolve;
|
|
399
|
+
this.inputResolve = null;
|
|
400
|
+
resolve({ text: feedback });
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
console.warn("[codex-process] Plan rejected but inputResolve not ready, queuing feedback");
|
|
404
|
+
this._pendingPlanInput = feedback;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
this.setStatus("idle");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async bootstrap(projectPath, options) {
|
|
412
|
+
try {
|
|
413
|
+
await this.initializeRpcConnection();
|
|
414
|
+
const threadParams = {
|
|
415
|
+
cwd: projectPath,
|
|
416
|
+
approvalPolicy: normalizeApprovalPolicy(options?.approvalPolicy ?? "never"),
|
|
417
|
+
sandbox: normalizeSandboxMode(options?.sandboxMode ?? "workspace-write"),
|
|
418
|
+
experimentalRawEvents: false,
|
|
419
|
+
persistExtendedHistory: true,
|
|
420
|
+
};
|
|
421
|
+
if (options?.model)
|
|
422
|
+
threadParams.model = options.model;
|
|
423
|
+
if (options?.modelReasoningEffort) {
|
|
424
|
+
threadParams.effort = normalizeReasoningEffort(options.modelReasoningEffort);
|
|
425
|
+
}
|
|
426
|
+
if (options?.networkAccessEnabled !== undefined) {
|
|
427
|
+
threadParams.sandboxPolicy = {
|
|
428
|
+
type: normalizeSandboxMode(options?.sandboxMode ?? "workspace-write"),
|
|
429
|
+
networkAccess: options.networkAccessEnabled,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (options?.webSearchMode) {
|
|
433
|
+
threadParams.webSearchMode = options.webSearchMode;
|
|
434
|
+
}
|
|
435
|
+
const method = options?.threadId ? "thread/resume" : "thread/start";
|
|
436
|
+
if (options?.threadId) {
|
|
437
|
+
threadParams.threadId = options.threadId;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
threadParams.experimentalRawEvents = false;
|
|
441
|
+
}
|
|
442
|
+
if (options?.threadId) {
|
|
443
|
+
threadParams.persistExtendedHistory = true;
|
|
444
|
+
}
|
|
445
|
+
const response = await this.request(method, threadParams);
|
|
446
|
+
const thread = response.thread;
|
|
447
|
+
const threadId = typeof thread?.id === "string"
|
|
448
|
+
? thread.id
|
|
449
|
+
: options?.threadId;
|
|
450
|
+
if (!threadId) {
|
|
451
|
+
throw new Error(`${method} returned no thread id`);
|
|
452
|
+
}
|
|
453
|
+
// Capture the resolved model name from thread response
|
|
454
|
+
if (typeof thread?.model === "string" && thread.model) {
|
|
455
|
+
this.startModel = thread.model;
|
|
456
|
+
}
|
|
457
|
+
this._threadId = threadId;
|
|
458
|
+
this.emitMessage({
|
|
459
|
+
type: "system",
|
|
460
|
+
subtype: "init",
|
|
461
|
+
sessionId: threadId,
|
|
462
|
+
model: this.startModel ?? "codex",
|
|
463
|
+
});
|
|
464
|
+
this.setStatus("idle");
|
|
465
|
+
// Fetch skills in background (non-blocking)
|
|
466
|
+
this._projectPath = projectPath;
|
|
467
|
+
void this.fetchSkills(projectPath);
|
|
468
|
+
await this.runInputLoop(options);
|
|
469
|
+
}
|
|
470
|
+
catch (err) {
|
|
471
|
+
if (!this.stopped) {
|
|
472
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
473
|
+
console.error("[codex-process] bootstrap error:", err);
|
|
474
|
+
this.emitMessage({ type: "error", message: `Codex error: ${message}` });
|
|
475
|
+
this.emitMessage({ type: "result", subtype: "error", error: message, sessionId: this._threadId ?? undefined });
|
|
476
|
+
}
|
|
477
|
+
this.setStatus("idle");
|
|
478
|
+
this.emit("exit", 1);
|
|
479
|
+
}
|
|
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
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Fetch skills from Codex app-server via `skills/list` RPC and emit them
|
|
496
|
+
* as a `supported_commands` system message so the Flutter client can display
|
|
497
|
+
* skill entries alongside built-in slash commands.
|
|
498
|
+
*/
|
|
499
|
+
async fetchSkills(projectPath) {
|
|
500
|
+
const TIMEOUT_MS = 10_000;
|
|
501
|
+
try {
|
|
502
|
+
const result = await Promise.race([
|
|
503
|
+
this.request("skills/list", { cwds: [projectPath] }),
|
|
504
|
+
new Promise((resolve) => setTimeout(() => resolve(null), TIMEOUT_MS)),
|
|
505
|
+
]);
|
|
506
|
+
if (this.stopped || !result?.data)
|
|
507
|
+
return;
|
|
508
|
+
const skills = [];
|
|
509
|
+
const slashCommands = [];
|
|
510
|
+
const skillMetadata = [];
|
|
511
|
+
for (const entry of result.data) {
|
|
512
|
+
for (const skill of entry.skills) {
|
|
513
|
+
if (skill.enabled) {
|
|
514
|
+
skills.push(skill.name);
|
|
515
|
+
slashCommands.push(skill.name);
|
|
516
|
+
skillMetadata.push({
|
|
517
|
+
name: skill.name,
|
|
518
|
+
path: skill.path,
|
|
519
|
+
description: skill.description,
|
|
520
|
+
shortDescription: skill.shortDescription ?? skill.interface?.shortDescription ?? undefined,
|
|
521
|
+
enabled: skill.enabled,
|
|
522
|
+
scope: skill.scope,
|
|
523
|
+
displayName: skill.interface?.displayName ?? undefined,
|
|
524
|
+
defaultPrompt: skill.interface?.defaultPrompt ?? undefined,
|
|
525
|
+
brandColor: skill.interface?.brandColor ?? undefined,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
this._skills = skillMetadata;
|
|
531
|
+
if (slashCommands.length > 0) {
|
|
532
|
+
console.log(`[codex-process] skills/list returned ${slashCommands.length} skills`);
|
|
533
|
+
this.emitMessage({
|
|
534
|
+
type: "system",
|
|
535
|
+
subtype: "supported_commands",
|
|
536
|
+
slashCommands,
|
|
537
|
+
skills,
|
|
538
|
+
skillMetadata,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
console.log(`[codex-process] skills/list failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async runInputLoop(options) {
|
|
547
|
+
while (!this.stopped) {
|
|
548
|
+
const pendingInput = await new Promise((resolve) => {
|
|
549
|
+
this.inputResolve = resolve;
|
|
550
|
+
// If plan approval arrived before inputResolve was ready, drain it now.
|
|
551
|
+
if (this._pendingPlanInput) {
|
|
552
|
+
const text = this._pendingPlanInput;
|
|
553
|
+
this._pendingPlanInput = null;
|
|
554
|
+
this.inputResolve = null;
|
|
555
|
+
resolve({ text });
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
if (this.stopped || !pendingInput.text)
|
|
559
|
+
break;
|
|
560
|
+
if (!this._threadId) {
|
|
561
|
+
this.emitMessage({ type: "error", message: "Codex thread is not initialized" });
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const { input, tempPaths } = await this.toRpcInput(pendingInput);
|
|
565
|
+
if (!input) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
this.setStatus("running");
|
|
569
|
+
this.lastTokenUsage = null;
|
|
570
|
+
const completion = await new Promise((resolve, reject) => {
|
|
571
|
+
this.pendingTurnCompletion = { resolve, reject };
|
|
572
|
+
const params = {
|
|
573
|
+
threadId: this._threadId,
|
|
574
|
+
input,
|
|
575
|
+
approvalPolicy: normalizeApprovalPolicy(this._approvalPolicy),
|
|
576
|
+
};
|
|
577
|
+
if (options?.model)
|
|
578
|
+
params.model = options.model;
|
|
579
|
+
if (options?.modelReasoningEffort) {
|
|
580
|
+
params.effort = normalizeReasoningEffort(options.modelReasoningEffort);
|
|
581
|
+
}
|
|
582
|
+
// Always send collaborationMode so the server switches modes correctly.
|
|
583
|
+
// Omitting it causes the server to persist the previous turn's mode.
|
|
584
|
+
const modeSettings = {
|
|
585
|
+
model: options?.model || this.startModel || "gpt-5.4",
|
|
586
|
+
};
|
|
587
|
+
if (this._collaborationMode === "plan") {
|
|
588
|
+
modeSettings.reasoning_effort = "medium";
|
|
589
|
+
}
|
|
590
|
+
params.collaborationMode = {
|
|
591
|
+
mode: this._collaborationMode,
|
|
592
|
+
settings: modeSettings,
|
|
593
|
+
};
|
|
594
|
+
console.log(`[codex-process] turn/start: approval=${params.approvalPolicy}, collaboration=${this._collaborationMode}`);
|
|
595
|
+
void this.request("turn/start", params)
|
|
596
|
+
.then((result) => {
|
|
597
|
+
const turn = result.turn;
|
|
598
|
+
if (typeof turn?.id === "string") {
|
|
599
|
+
this.pendingTurnId = turn.id;
|
|
600
|
+
}
|
|
601
|
+
})
|
|
602
|
+
.catch((err) => {
|
|
603
|
+
this.pendingTurnCompletion = null;
|
|
604
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
605
|
+
});
|
|
606
|
+
}).catch((err) => {
|
|
607
|
+
if (!this.stopped) {
|
|
608
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
609
|
+
this.emitMessage({ type: "error", message });
|
|
610
|
+
this.emitMessage({
|
|
611
|
+
type: "result",
|
|
612
|
+
subtype: "error",
|
|
613
|
+
error: message,
|
|
614
|
+
sessionId: this._threadId ?? undefined,
|
|
615
|
+
});
|
|
616
|
+
this.setStatus("idle");
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
await Promise.all(tempPaths.map((path) => rm(path, { force: true }).catch(() => { })));
|
|
620
|
+
void completion;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
handleStdoutChunk(chunk) {
|
|
624
|
+
this.stdoutBuffer += chunk;
|
|
625
|
+
while (true) {
|
|
626
|
+
const newlineIndex = this.stdoutBuffer.indexOf("\n");
|
|
627
|
+
if (newlineIndex < 0)
|
|
628
|
+
break;
|
|
629
|
+
const line = this.stdoutBuffer.slice(0, newlineIndex).trim();
|
|
630
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(newlineIndex + 1);
|
|
631
|
+
if (!line)
|
|
632
|
+
continue;
|
|
633
|
+
try {
|
|
634
|
+
const envelope = JSON.parse(line);
|
|
635
|
+
this.handleRpcEnvelope(envelope);
|
|
636
|
+
}
|
|
637
|
+
catch (err) {
|
|
638
|
+
console.warn(`[codex-process] failed to parse app-server JSON line: ${line.slice(0, 200)}`);
|
|
639
|
+
if (!this.stopped) {
|
|
640
|
+
this.emitMessage({
|
|
641
|
+
type: "error",
|
|
642
|
+
message: `Failed to parse codex app-server output: ${err instanceof Error ? err.message : String(err)}`,
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
handleRpcEnvelope(envelope) {
|
|
649
|
+
if (envelope.id != null && envelope.method && envelope.result === undefined && envelope.error === undefined) {
|
|
650
|
+
this.handleServerRequest(envelope.id, envelope.method, envelope.params ?? {});
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (envelope.id != null && (envelope.result !== undefined || envelope.error)) {
|
|
654
|
+
this.handleRpcResponse(envelope);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
if (envelope.method) {
|
|
658
|
+
this.handleNotification(envelope.method, envelope.params ?? {});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
handleRpcResponse(envelope) {
|
|
662
|
+
if (typeof envelope.id !== "number") {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const pending = this.pendingRpc.get(envelope.id);
|
|
666
|
+
if (!pending)
|
|
667
|
+
return;
|
|
668
|
+
this.pendingRpc.delete(envelope.id);
|
|
669
|
+
if ("error" in envelope && envelope.error) {
|
|
670
|
+
const message = envelope.error.message ?? `RPC error ${envelope.error.code ?? ""}`;
|
|
671
|
+
pending.reject(new Error(message));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
pending.resolve(envelope.result);
|
|
675
|
+
}
|
|
676
|
+
handleServerRequest(id, method, params) {
|
|
677
|
+
switch (method) {
|
|
678
|
+
case "item/commandExecution/requestApproval": {
|
|
679
|
+
const toolUseId = this.extractToolUseId(params, id);
|
|
680
|
+
const input = {
|
|
681
|
+
...(typeof params.command === "string" ? { command: params.command } : {}),
|
|
682
|
+
...(typeof params.cwd === "string" ? { cwd: params.cwd } : {}),
|
|
683
|
+
...(params.commandActions ? { commandActions: params.commandActions } : {}),
|
|
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 } : {}),
|
|
690
|
+
...(typeof params.reason === "string" ? { reason: params.reason } : {}),
|
|
691
|
+
};
|
|
692
|
+
this.pendingApprovals.set(toolUseId, {
|
|
693
|
+
requestId: id,
|
|
694
|
+
toolUseId,
|
|
695
|
+
toolName: "Bash",
|
|
696
|
+
input,
|
|
697
|
+
kind: "command",
|
|
698
|
+
});
|
|
699
|
+
this.emitMessage({
|
|
700
|
+
type: "permission_request",
|
|
701
|
+
toolUseId,
|
|
702
|
+
toolName: "Bash",
|
|
703
|
+
input,
|
|
704
|
+
});
|
|
705
|
+
this.setStatus("waiting_approval");
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
case "item/fileChange/requestApproval": {
|
|
709
|
+
const toolUseId = this.extractToolUseId(params, id);
|
|
710
|
+
const input = {
|
|
711
|
+
...(Array.isArray(params.changes) ? { changes: params.changes } : {}),
|
|
712
|
+
...(typeof params.grantRoot === "string" ? { grantRoot: params.grantRoot } : {}),
|
|
713
|
+
...(typeof params.reason === "string" ? { reason: params.reason } : {}),
|
|
714
|
+
};
|
|
715
|
+
this.pendingApprovals.set(toolUseId, {
|
|
716
|
+
requestId: id,
|
|
717
|
+
toolUseId,
|
|
718
|
+
toolName: "FileChange",
|
|
719
|
+
input,
|
|
720
|
+
kind: "file",
|
|
721
|
+
});
|
|
722
|
+
this.emitMessage({
|
|
723
|
+
type: "permission_request",
|
|
724
|
+
toolUseId,
|
|
725
|
+
toolName: "FileChange",
|
|
726
|
+
input,
|
|
727
|
+
});
|
|
728
|
+
this.setStatus("waiting_approval");
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
case "item/tool/requestUserInput": {
|
|
732
|
+
const toolUseId = this.extractToolUseId(params, id);
|
|
733
|
+
const questions = normalizeUserInputQuestions(params.questions);
|
|
734
|
+
const input = {
|
|
735
|
+
questions: questions.map((q) => ({
|
|
736
|
+
id: q.id,
|
|
737
|
+
question: q.question,
|
|
738
|
+
header: q.header,
|
|
739
|
+
options: q.options,
|
|
740
|
+
multiSelect: false,
|
|
741
|
+
isOther: q.isOther,
|
|
742
|
+
isSecret: q.isSecret,
|
|
743
|
+
})),
|
|
744
|
+
};
|
|
745
|
+
this.pendingUserInputs.set(toolUseId, {
|
|
746
|
+
requestId: id,
|
|
747
|
+
toolUseId,
|
|
748
|
+
toolName: "AskUserQuestion",
|
|
749
|
+
questions: questions.map((q) => ({
|
|
750
|
+
id: q.id,
|
|
751
|
+
question: q.question,
|
|
752
|
+
})),
|
|
753
|
+
input,
|
|
754
|
+
kind: "questions",
|
|
755
|
+
});
|
|
756
|
+
this.emitMessage({
|
|
757
|
+
type: "permission_request",
|
|
758
|
+
toolUseId,
|
|
759
|
+
toolName: "AskUserQuestion",
|
|
760
|
+
input,
|
|
761
|
+
});
|
|
762
|
+
this.setStatus("waiting_approval");
|
|
763
|
+
break;
|
|
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
|
+
}
|
|
809
|
+
default:
|
|
810
|
+
this.respondToServerRequest(id, {});
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
handleNotification(method, params) {
|
|
815
|
+
switch (method) {
|
|
816
|
+
case "thread/started": {
|
|
817
|
+
const thread = params.thread;
|
|
818
|
+
if (typeof thread?.id === "string") {
|
|
819
|
+
this._threadId = thread.id;
|
|
820
|
+
}
|
|
821
|
+
this._agentNickname = stringOrNull(thread?.agentNickname);
|
|
822
|
+
this._agentRole = stringOrNull(thread?.agentRole);
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
case "turn/started": {
|
|
826
|
+
const turn = params.turn;
|
|
827
|
+
if (typeof turn?.id === "string") {
|
|
828
|
+
this.pendingTurnId = turn.id;
|
|
829
|
+
}
|
|
830
|
+
this.setStatus("running");
|
|
831
|
+
break;
|
|
832
|
+
}
|
|
833
|
+
case "turn/completed": {
|
|
834
|
+
this.handleTurnCompleted(params.turn);
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
case "thread/name/updated": {
|
|
838
|
+
// Name change notification — handled by session manager
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
case "thread/tokenUsage/updated": {
|
|
842
|
+
const usage = params.usage;
|
|
843
|
+
if (usage) {
|
|
844
|
+
this.lastTokenUsage = {
|
|
845
|
+
input: numberOrUndefined(usage.inputTokens ?? usage.input_tokens),
|
|
846
|
+
cachedInput: numberOrUndefined(usage.cachedInputTokens ?? usage.cached_input_tokens),
|
|
847
|
+
output: numberOrUndefined(usage.outputTokens ?? usage.output_tokens),
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
case "item/started": {
|
|
853
|
+
this.processItemStarted(params.item);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
case "item/completed": {
|
|
857
|
+
this.processItemCompleted(params.item);
|
|
858
|
+
break;
|
|
859
|
+
}
|
|
860
|
+
case "item/agentMessage/delta": {
|
|
861
|
+
const delta = typeof params.delta === "string"
|
|
862
|
+
? params.delta
|
|
863
|
+
: typeof params.textDelta === "string"
|
|
864
|
+
? params.textDelta
|
|
865
|
+
: "";
|
|
866
|
+
if (delta) {
|
|
867
|
+
this.emitMessage({ type: "stream_delta", text: delta });
|
|
868
|
+
}
|
|
869
|
+
break;
|
|
870
|
+
}
|
|
871
|
+
case "item/reasoning/summaryTextDelta":
|
|
872
|
+
case "item/reasoning/textDelta": {
|
|
873
|
+
const delta = typeof params.delta === "string"
|
|
874
|
+
? params.delta
|
|
875
|
+
: typeof params.textDelta === "string"
|
|
876
|
+
? params.textDelta
|
|
877
|
+
: "";
|
|
878
|
+
if (delta) {
|
|
879
|
+
this.emitMessage({ type: "thinking_delta", text: delta });
|
|
880
|
+
}
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
case "item/plan/delta": {
|
|
884
|
+
const delta = typeof params.delta === "string" ? params.delta : "";
|
|
885
|
+
if (delta) {
|
|
886
|
+
this.emitMessage({ type: "thinking_delta", text: delta });
|
|
887
|
+
}
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
case "skills/changed": {
|
|
891
|
+
// Re-fetch skills when Codex notifies us of changes
|
|
892
|
+
if (this._projectPath) {
|
|
893
|
+
void this.fetchSkills(this._projectPath);
|
|
894
|
+
}
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case "turn/plan/updated": {
|
|
898
|
+
// Default mode's update_plan tool output — always show as informational text
|
|
899
|
+
const text = formatPlanUpdateText(params);
|
|
900
|
+
if (!text)
|
|
901
|
+
break;
|
|
902
|
+
this.emitMessage({
|
|
903
|
+
type: "assistant",
|
|
904
|
+
message: {
|
|
905
|
+
id: randomUUID(),
|
|
906
|
+
role: "assistant",
|
|
907
|
+
content: [{ type: "text", text }],
|
|
908
|
+
model: "codex",
|
|
909
|
+
},
|
|
910
|
+
});
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
case "serverRequest/resolved": {
|
|
914
|
+
this.handleServerRequestResolved(params);
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
default:
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
handleTurnCompleted(turn) {
|
|
922
|
+
const status = String(turn?.status ?? "completed");
|
|
923
|
+
const usage = this.lastTokenUsage;
|
|
924
|
+
this.lastTokenUsage = null;
|
|
925
|
+
if (status === "failed") {
|
|
926
|
+
const errorObj = turn?.error;
|
|
927
|
+
const message = typeof errorObj?.message === "string"
|
|
928
|
+
? errorObj.message
|
|
929
|
+
: "Turn failed";
|
|
930
|
+
this.emitMessage({
|
|
931
|
+
type: "result",
|
|
932
|
+
subtype: "error",
|
|
933
|
+
error: message,
|
|
934
|
+
sessionId: this._threadId ?? undefined,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
else if (status === "interrupted") {
|
|
938
|
+
this.emitMessage({
|
|
939
|
+
type: "result",
|
|
940
|
+
subtype: "interrupted",
|
|
941
|
+
sessionId: this._threadId ?? undefined,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
this.emitMessage({
|
|
946
|
+
type: "result",
|
|
947
|
+
subtype: "success",
|
|
948
|
+
sessionId: this._threadId ?? undefined,
|
|
949
|
+
...(usage?.input != null ? { inputTokens: usage.input } : {}),
|
|
950
|
+
...(usage?.cachedInput != null ? { cachedInputTokens: usage.cachedInput } : {}),
|
|
951
|
+
...(usage?.output != null ? { outputTokens: usage.output } : {}),
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
this.pendingTurnId = null;
|
|
955
|
+
// Plan mode: emit synthetic plan approval and wait for user decision
|
|
956
|
+
if (this._collaborationMode === "plan" && this.lastPlanItemText) {
|
|
957
|
+
const toolUseId = `plan_${randomUUID()}`;
|
|
958
|
+
this.pendingPlanCompletion = {
|
|
959
|
+
toolUseId,
|
|
960
|
+
planText: this.lastPlanItemText,
|
|
961
|
+
};
|
|
962
|
+
this.lastPlanItemText = null;
|
|
963
|
+
this.emitMessage({
|
|
964
|
+
type: "permission_request",
|
|
965
|
+
toolUseId,
|
|
966
|
+
toolName: "ExitPlanMode",
|
|
967
|
+
input: { plan: this.pendingPlanCompletion.planText },
|
|
968
|
+
});
|
|
969
|
+
this.setStatus("waiting_approval");
|
|
970
|
+
// Do NOT set idle — waiting for plan approval
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
this.lastPlanItemText = null;
|
|
974
|
+
if (this.pendingApprovals.size === 0 && this.pendingUserInputs.size === 0) {
|
|
975
|
+
this.setStatus("idle");
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (this.pendingTurnCompletion) {
|
|
979
|
+
this.pendingTurnCompletion.resolve();
|
|
980
|
+
this.pendingTurnCompletion = null;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
processItemStarted(item) {
|
|
984
|
+
if (!item || typeof item !== "object")
|
|
985
|
+
return;
|
|
986
|
+
const itemId = typeof item.id === "string" ? item.id : randomUUID();
|
|
987
|
+
const itemType = normalizeItemType(item.type);
|
|
988
|
+
switch (itemType) {
|
|
989
|
+
case "commandexecution": {
|
|
990
|
+
const commandText = typeof item.command === "string"
|
|
991
|
+
? item.command
|
|
992
|
+
: Array.isArray(item.command)
|
|
993
|
+
? item.command.map((part) => String(part)).join(" ")
|
|
994
|
+
: "";
|
|
995
|
+
this.emitMessage({
|
|
996
|
+
type: "assistant",
|
|
997
|
+
message: {
|
|
998
|
+
id: itemId,
|
|
999
|
+
role: "assistant",
|
|
1000
|
+
content: [
|
|
1001
|
+
{
|
|
1002
|
+
type: "tool_use",
|
|
1003
|
+
id: itemId,
|
|
1004
|
+
name: "Bash",
|
|
1005
|
+
input: { command: commandText },
|
|
1006
|
+
},
|
|
1007
|
+
],
|
|
1008
|
+
model: "codex",
|
|
1009
|
+
},
|
|
1010
|
+
});
|
|
1011
|
+
break;
|
|
1012
|
+
}
|
|
1013
|
+
case "filechange": {
|
|
1014
|
+
this.emitMessage({
|
|
1015
|
+
type: "assistant",
|
|
1016
|
+
message: {
|
|
1017
|
+
id: itemId,
|
|
1018
|
+
role: "assistant",
|
|
1019
|
+
content: [
|
|
1020
|
+
{
|
|
1021
|
+
type: "tool_use",
|
|
1022
|
+
id: itemId,
|
|
1023
|
+
name: "FileChange",
|
|
1024
|
+
input: {
|
|
1025
|
+
changes: Array.isArray(item.changes) ? item.changes : [],
|
|
1026
|
+
},
|
|
1027
|
+
},
|
|
1028
|
+
],
|
|
1029
|
+
model: "codex",
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
break;
|
|
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
|
+
}
|
|
1084
|
+
default:
|
|
1085
|
+
break;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
processItemCompleted(item) {
|
|
1089
|
+
if (!item || typeof item !== "object")
|
|
1090
|
+
return;
|
|
1091
|
+
const itemId = typeof item.id === "string" ? item.id : randomUUID();
|
|
1092
|
+
const itemType = normalizeItemType(item.type);
|
|
1093
|
+
switch (itemType) {
|
|
1094
|
+
case "agentmessage": {
|
|
1095
|
+
const text = extractAgentText(item);
|
|
1096
|
+
if (!text)
|
|
1097
|
+
return;
|
|
1098
|
+
this.emitMessage({
|
|
1099
|
+
type: "assistant",
|
|
1100
|
+
message: {
|
|
1101
|
+
id: itemId,
|
|
1102
|
+
role: "assistant",
|
|
1103
|
+
content: [{ type: "text", text }],
|
|
1104
|
+
model: "codex",
|
|
1105
|
+
},
|
|
1106
|
+
});
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
case "reasoning": {
|
|
1110
|
+
const text = extractReasoningText(item);
|
|
1111
|
+
if (text) {
|
|
1112
|
+
this.emitMessage({ type: "thinking_delta", text });
|
|
1113
|
+
}
|
|
1114
|
+
break;
|
|
1115
|
+
}
|
|
1116
|
+
case "commandexecution": {
|
|
1117
|
+
const output = typeof item.aggregatedOutput === "string"
|
|
1118
|
+
? item.aggregatedOutput
|
|
1119
|
+
: typeof item.output === "string"
|
|
1120
|
+
? item.output
|
|
1121
|
+
: "";
|
|
1122
|
+
const exitCode = numberOrUndefined(item.exitCode ?? item.exit_code);
|
|
1123
|
+
this.emitMessage({
|
|
1124
|
+
type: "tool_result",
|
|
1125
|
+
toolUseId: itemId,
|
|
1126
|
+
content: output || `exit code: ${exitCode ?? "unknown"}`,
|
|
1127
|
+
toolName: "Bash",
|
|
1128
|
+
});
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
case "filechange": {
|
|
1132
|
+
const content = formatFileChangesWithDiff(item.changes);
|
|
1133
|
+
this.emitMessage({
|
|
1134
|
+
type: "tool_result",
|
|
1135
|
+
toolUseId: itemId,
|
|
1136
|
+
content,
|
|
1137
|
+
toolName: "FileChange",
|
|
1138
|
+
});
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
case "mcptoolcall": {
|
|
1142
|
+
const server = typeof item.server === "string" ? item.server : "mcp";
|
|
1143
|
+
const tool = typeof item.tool === "string" ? item.tool : "unknown";
|
|
1144
|
+
const toolName = `mcp:${server}/${tool}`;
|
|
1145
|
+
const result = item.result ?? item.error ?? "MCP call completed";
|
|
1146
|
+
const normalized = normalizeMcpToolResult(result);
|
|
1147
|
+
this.emitMessage({
|
|
1148
|
+
type: "assistant",
|
|
1149
|
+
message: {
|
|
1150
|
+
id: itemId,
|
|
1151
|
+
role: "assistant",
|
|
1152
|
+
content: [
|
|
1153
|
+
{
|
|
1154
|
+
type: "tool_use",
|
|
1155
|
+
id: itemId,
|
|
1156
|
+
name: toolName,
|
|
1157
|
+
input: item.arguments ?? {},
|
|
1158
|
+
},
|
|
1159
|
+
],
|
|
1160
|
+
model: "codex",
|
|
1161
|
+
},
|
|
1162
|
+
});
|
|
1163
|
+
this.emitMessage({
|
|
1164
|
+
type: "tool_result",
|
|
1165
|
+
toolUseId: itemId,
|
|
1166
|
+
content: normalized.content,
|
|
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,
|
|
1182
|
+
});
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
case "websearch": {
|
|
1186
|
+
const query = typeof item.query === "string" ? item.query : "";
|
|
1187
|
+
this.emitMessage({
|
|
1188
|
+
type: "assistant",
|
|
1189
|
+
message: {
|
|
1190
|
+
id: itemId,
|
|
1191
|
+
role: "assistant",
|
|
1192
|
+
content: [
|
|
1193
|
+
{
|
|
1194
|
+
type: "tool_use",
|
|
1195
|
+
id: itemId,
|
|
1196
|
+
name: "WebSearch",
|
|
1197
|
+
input: { query },
|
|
1198
|
+
},
|
|
1199
|
+
],
|
|
1200
|
+
model: "codex",
|
|
1201
|
+
},
|
|
1202
|
+
});
|
|
1203
|
+
this.emitMessage({
|
|
1204
|
+
type: "tool_result",
|
|
1205
|
+
toolUseId: itemId,
|
|
1206
|
+
content: query ? `Web search: ${query}` : "Web search completed",
|
|
1207
|
+
toolName: "WebSearch",
|
|
1208
|
+
});
|
|
1209
|
+
break;
|
|
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
|
+
}
|
|
1232
|
+
case "plan": {
|
|
1233
|
+
// Plan item completed — save text for plan approval emission in handleTurnCompleted()
|
|
1234
|
+
const planText = typeof item.text === "string" ? item.text : "";
|
|
1235
|
+
this.lastPlanItemText = planText;
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
case "error": {
|
|
1239
|
+
const message = typeof item.message === "string" ? item.message : "Codex item error";
|
|
1240
|
+
this.emitMessage({ type: "error", message });
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
default:
|
|
1244
|
+
break;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
async toRpcInput(pendingInput) {
|
|
1248
|
+
const input = [];
|
|
1249
|
+
const tempPaths = [];
|
|
1250
|
+
// Prepend SkillUserInput if a skill reference is attached
|
|
1251
|
+
if (pendingInput.skill) {
|
|
1252
|
+
input.push({ type: "skill", name: pendingInput.skill.name, path: pendingInput.skill.path });
|
|
1253
|
+
}
|
|
1254
|
+
input.push({ type: "text", text: pendingInput.text });
|
|
1255
|
+
if (!pendingInput.images || pendingInput.images.length === 0) {
|
|
1256
|
+
return { input, tempPaths };
|
|
1257
|
+
}
|
|
1258
|
+
for (const image of pendingInput.images) {
|
|
1259
|
+
const ext = extensionFromMime(image.mimeType);
|
|
1260
|
+
if (!ext) {
|
|
1261
|
+
this.emitMessage({
|
|
1262
|
+
type: "error",
|
|
1263
|
+
message: `Unsupported image mime type for Codex: ${image.mimeType}`,
|
|
1264
|
+
});
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
let buffer;
|
|
1268
|
+
try {
|
|
1269
|
+
buffer = Buffer.from(image.base64, "base64");
|
|
1270
|
+
}
|
|
1271
|
+
catch {
|
|
1272
|
+
this.emitMessage({
|
|
1273
|
+
type: "error",
|
|
1274
|
+
message: "Invalid base64 image data for Codex input",
|
|
1275
|
+
});
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
const tempPath = join(tmpdir(), `ccpocket-codex-image-${randomUUID()}.${ext}`);
|
|
1279
|
+
await writeFile(tempPath, buffer);
|
|
1280
|
+
tempPaths.push(tempPath);
|
|
1281
|
+
input.push({ type: "localImage", path: tempPath });
|
|
1282
|
+
}
|
|
1283
|
+
return { input, tempPaths };
|
|
1284
|
+
}
|
|
1285
|
+
request(method, params) {
|
|
1286
|
+
const id = this.rpcSeq++;
|
|
1287
|
+
const envelope = { id, method, params };
|
|
1288
|
+
return new Promise((resolve, reject) => {
|
|
1289
|
+
this.pendingRpc.set(id, { resolve, reject, method });
|
|
1290
|
+
try {
|
|
1291
|
+
this.writeEnvelope(envelope);
|
|
1292
|
+
}
|
|
1293
|
+
catch (err) {
|
|
1294
|
+
this.pendingRpc.delete(id);
|
|
1295
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
1296
|
+
}
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
notify(method, params) {
|
|
1300
|
+
this.writeEnvelope({ method, params });
|
|
1301
|
+
}
|
|
1302
|
+
respondToServerRequest(id, result) {
|
|
1303
|
+
try {
|
|
1304
|
+
this.writeEnvelope({ id, result });
|
|
1305
|
+
}
|
|
1306
|
+
catch (err) {
|
|
1307
|
+
if (!this.stopped) {
|
|
1308
|
+
console.warn(`[codex-process] failed to respond to server request: ${err instanceof Error ? err.message : String(err)}`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
writeEnvelope(envelope) {
|
|
1313
|
+
if (!this.child || this.child.killed) {
|
|
1314
|
+
throw new Error("codex app-server is not running");
|
|
1315
|
+
}
|
|
1316
|
+
const line = `${JSON.stringify(envelope)}\n`;
|
|
1317
|
+
this.child.stdin.write(line);
|
|
1318
|
+
}
|
|
1319
|
+
rejectAllPending(error) {
|
|
1320
|
+
for (const pending of this.pendingRpc.values()) {
|
|
1321
|
+
pending.reject(error);
|
|
1322
|
+
}
|
|
1323
|
+
this.pendingRpc.clear();
|
|
1324
|
+
if (this.pendingTurnCompletion) {
|
|
1325
|
+
this.pendingTurnCompletion.reject(error);
|
|
1326
|
+
this.pendingTurnCompletion = null;
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
setStatus(status) {
|
|
1330
|
+
if (this._status !== status) {
|
|
1331
|
+
this._status = status;
|
|
1332
|
+
this.emit("status", status);
|
|
1333
|
+
this.emitMessage({ type: "status", status });
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
emitMessage(msg) {
|
|
1337
|
+
this.emit("message", msg);
|
|
1338
|
+
}
|
|
1339
|
+
extractToolUseId(params, requestId) {
|
|
1340
|
+
if (typeof params.approvalId === "string")
|
|
1341
|
+
return params.approvalId;
|
|
1342
|
+
if (typeof params.elicitationId === "string")
|
|
1343
|
+
return params.elicitationId;
|
|
1344
|
+
if (typeof params.itemId === "string")
|
|
1345
|
+
return params.itemId;
|
|
1346
|
+
if (typeof requestId === "string")
|
|
1347
|
+
return requestId;
|
|
1348
|
+
return `approval-${requestId}`;
|
|
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);
|
|
1387
|
+
}
|
|
1388
|
+
function normalizeApprovalPolicy(value) {
|
|
1389
|
+
switch (value) {
|
|
1390
|
+
case "on-request":
|
|
1391
|
+
return "on-request";
|
|
1392
|
+
case "on-failure":
|
|
1393
|
+
return "on-failure";
|
|
1394
|
+
case "untrusted":
|
|
1395
|
+
return "untrusted";
|
|
1396
|
+
case "never":
|
|
1397
|
+
default:
|
|
1398
|
+
return "never";
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
function normalizeSandboxMode(value) {
|
|
1402
|
+
switch (value) {
|
|
1403
|
+
case "read-only":
|
|
1404
|
+
return "read-only";
|
|
1405
|
+
case "danger-full-access":
|
|
1406
|
+
return "danger-full-access";
|
|
1407
|
+
case "workspace-write":
|
|
1408
|
+
default:
|
|
1409
|
+
return "workspace-write";
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function normalizeReasoningEffort(value) {
|
|
1413
|
+
switch (value) {
|
|
1414
|
+
case "xhigh":
|
|
1415
|
+
return "high";
|
|
1416
|
+
default:
|
|
1417
|
+
return value;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
function normalizeItemType(raw) {
|
|
1421
|
+
if (typeof raw !== "string")
|
|
1422
|
+
return "";
|
|
1423
|
+
return raw.replace(/[_\s-]/g, "").toLowerCase();
|
|
1424
|
+
}
|
|
1425
|
+
function numberOrUndefined(value) {
|
|
1426
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1427
|
+
}
|
|
1428
|
+
function stringOrNull(value) {
|
|
1429
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
1430
|
+
}
|
|
1431
|
+
function summarizeFileChanges(changes) {
|
|
1432
|
+
if (!Array.isArray(changes) || changes.length === 0) {
|
|
1433
|
+
return "No file changes";
|
|
1434
|
+
}
|
|
1435
|
+
return changes
|
|
1436
|
+
.map((entry) => {
|
|
1437
|
+
if (!entry || typeof entry !== "object")
|
|
1438
|
+
return "changed";
|
|
1439
|
+
const record = entry;
|
|
1440
|
+
const kind = typeof record.kind === "string" ? record.kind : "changed";
|
|
1441
|
+
const path = typeof record.path === "string" ? record.path : "(unknown)";
|
|
1442
|
+
return `${kind}: ${path}`;
|
|
1443
|
+
})
|
|
1444
|
+
.join("\n");
|
|
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
|
+
}
|
|
1567
|
+
/**
|
|
1568
|
+
* Format file changes including unified diff content for display in chat.
|
|
1569
|
+
* Falls back to `kind: path` summary when no diff is available.
|
|
1570
|
+
*/
|
|
1571
|
+
function formatFileChangesWithDiff(changes) {
|
|
1572
|
+
if (!Array.isArray(changes) || changes.length === 0) {
|
|
1573
|
+
return "No file changes";
|
|
1574
|
+
}
|
|
1575
|
+
return changes
|
|
1576
|
+
.map((entry) => {
|
|
1577
|
+
if (!entry || typeof entry !== "object")
|
|
1578
|
+
return "changed";
|
|
1579
|
+
const record = entry;
|
|
1580
|
+
const kind = typeof record.kind === "string" ? record.kind : "changed";
|
|
1581
|
+
const path = typeof record.path === "string" ? record.path : "(unknown)";
|
|
1582
|
+
const diff = typeof record.diff === "string" ? record.diff.trim() : "";
|
|
1583
|
+
if (diff) {
|
|
1584
|
+
// If diff already has unified headers, use as-is; otherwise add them
|
|
1585
|
+
if (diff.startsWith("---") || diff.startsWith("@@")) {
|
|
1586
|
+
return `--- a/${path}\n+++ b/${path}\n${diff}`;
|
|
1587
|
+
}
|
|
1588
|
+
return diff;
|
|
1589
|
+
}
|
|
1590
|
+
return `${kind}: ${path}`;
|
|
1591
|
+
})
|
|
1592
|
+
.join("\n\n");
|
|
1593
|
+
}
|
|
1594
|
+
function extractAgentText(item) {
|
|
1595
|
+
if (typeof item.text === "string")
|
|
1596
|
+
return item.text;
|
|
1597
|
+
const parts = item.content;
|
|
1598
|
+
if (Array.isArray(parts)) {
|
|
1599
|
+
const text = parts
|
|
1600
|
+
.filter((part) => part && typeof part === "object")
|
|
1601
|
+
.map((part) => {
|
|
1602
|
+
const record = part;
|
|
1603
|
+
if (record.type === "text" && typeof record.text === "string") {
|
|
1604
|
+
return record.text;
|
|
1605
|
+
}
|
|
1606
|
+
return "";
|
|
1607
|
+
})
|
|
1608
|
+
.filter((part) => part.length > 0)
|
|
1609
|
+
.join("\n");
|
|
1610
|
+
if (text)
|
|
1611
|
+
return text;
|
|
1612
|
+
}
|
|
1613
|
+
return "";
|
|
1614
|
+
}
|
|
1615
|
+
function extractReasoningText(item) {
|
|
1616
|
+
if (typeof item.text === "string")
|
|
1617
|
+
return item.text;
|
|
1618
|
+
const summary = item.summary;
|
|
1619
|
+
if (Array.isArray(summary)) {
|
|
1620
|
+
const text = summary
|
|
1621
|
+
.map((entry) => {
|
|
1622
|
+
if (!entry || typeof entry !== "object")
|
|
1623
|
+
return "";
|
|
1624
|
+
const record = entry;
|
|
1625
|
+
return typeof record.text === "string" ? record.text : "";
|
|
1626
|
+
})
|
|
1627
|
+
.filter((part) => part.length > 0)
|
|
1628
|
+
.join("\n");
|
|
1629
|
+
if (text)
|
|
1630
|
+
return text;
|
|
1631
|
+
}
|
|
1632
|
+
return "";
|
|
1633
|
+
}
|
|
1634
|
+
function normalizeUserInputQuestions(raw) {
|
|
1635
|
+
if (!Array.isArray(raw))
|
|
1636
|
+
return [];
|
|
1637
|
+
return raw
|
|
1638
|
+
.filter((entry) => !!entry && typeof entry === "object")
|
|
1639
|
+
.map((entry, index) => {
|
|
1640
|
+
const id = typeof entry.id === "string" ? entry.id : `question_${index + 1}`;
|
|
1641
|
+
const question = typeof entry.question === "string" ? entry.question : "";
|
|
1642
|
+
const header = typeof entry.header === "string" ? entry.header : `Question ${index + 1}`;
|
|
1643
|
+
const optionsRaw = Array.isArray(entry.options) ? entry.options : [];
|
|
1644
|
+
const options = optionsRaw
|
|
1645
|
+
.filter((option) => !!option && typeof option === "object")
|
|
1646
|
+
.map((option) => ({
|
|
1647
|
+
label: typeof option.label === "string" ? option.label : "",
|
|
1648
|
+
description: typeof option.description === "string" ? option.description : "",
|
|
1649
|
+
}))
|
|
1650
|
+
.filter((option) => option.label.length > 0);
|
|
1651
|
+
return {
|
|
1652
|
+
id,
|
|
1653
|
+
question,
|
|
1654
|
+
header,
|
|
1655
|
+
options,
|
|
1656
|
+
isOther: Boolean(entry.isOther),
|
|
1657
|
+
isSecret: Boolean(entry.isSecret),
|
|
1658
|
+
};
|
|
1659
|
+
})
|
|
1660
|
+
.filter((question) => question.question.length > 0);
|
|
1661
|
+
}
|
|
1662
|
+
function buildUserInputAnswers(questions, rawResult) {
|
|
1663
|
+
const parsed = parseResultObject(rawResult);
|
|
1664
|
+
const answerMap = {};
|
|
1665
|
+
for (const question of questions) {
|
|
1666
|
+
const candidate = parsed.byId[question.id] ?? parsed.byQuestion[question.question];
|
|
1667
|
+
const answers = normalizeAnswerValues(candidate);
|
|
1668
|
+
if (answers.length > 0) {
|
|
1669
|
+
answerMap[question.id] = { answers };
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
if (Object.keys(answerMap).length === 0 && questions.length > 0) {
|
|
1673
|
+
answerMap[questions[0].id] = { answers: normalizeAnswerValues(rawResult) };
|
|
1674
|
+
}
|
|
1675
|
+
return answerMap;
|
|
1676
|
+
}
|
|
1677
|
+
function parseResultObject(rawResult) {
|
|
1678
|
+
try {
|
|
1679
|
+
const parsed = JSON.parse(rawResult);
|
|
1680
|
+
const byId = {};
|
|
1681
|
+
const byQuestion = {};
|
|
1682
|
+
if (parsed && typeof parsed === "object") {
|
|
1683
|
+
const answers = parsed.answers;
|
|
1684
|
+
if (answers && typeof answers === "object" && !Array.isArray(answers)) {
|
|
1685
|
+
for (const [key, value] of Object.entries(answers)) {
|
|
1686
|
+
byId[key] = value;
|
|
1687
|
+
byQuestion[key] = value;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return { byId, byQuestion };
|
|
1692
|
+
}
|
|
1693
|
+
catch {
|
|
1694
|
+
return { byId: {}, byQuestion: {} };
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
function normalizeAnswerValues(value) {
|
|
1698
|
+
if (typeof value === "string") {
|
|
1699
|
+
return value
|
|
1700
|
+
.split(",")
|
|
1701
|
+
.map((part) => part.trim())
|
|
1702
|
+
.filter((part) => part.length > 0);
|
|
1703
|
+
}
|
|
1704
|
+
if (Array.isArray(value)) {
|
|
1705
|
+
return value
|
|
1706
|
+
.map((entry) => String(entry).trim())
|
|
1707
|
+
.filter((entry) => entry.length > 0);
|
|
1708
|
+
}
|
|
1709
|
+
if (value && typeof value === "object") {
|
|
1710
|
+
const record = value;
|
|
1711
|
+
if (Array.isArray(record.answers)) {
|
|
1712
|
+
return record.answers
|
|
1713
|
+
.map((entry) => String(entry).trim())
|
|
1714
|
+
.filter((entry) => entry.length > 0);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
if (value == null)
|
|
1718
|
+
return [];
|
|
1719
|
+
const normalized = String(value).trim();
|
|
1720
|
+
return normalized ? [normalized] : [];
|
|
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
|
+
}
|
|
1886
|
+
function formatPlanUpdateText(params) {
|
|
1887
|
+
const stepsRaw = params.plan;
|
|
1888
|
+
if (!Array.isArray(stepsRaw) || stepsRaw.length === 0)
|
|
1889
|
+
return "";
|
|
1890
|
+
const explanation = typeof params.explanation === "string" ? params.explanation.trim() : "";
|
|
1891
|
+
const lines = stepsRaw
|
|
1892
|
+
.filter((entry) => !!entry && typeof entry === "object")
|
|
1893
|
+
.map((entry, index) => {
|
|
1894
|
+
const step = typeof entry.step === "string" ? entry.step : `Step ${index + 1}`;
|
|
1895
|
+
const status = normalizePlanStatus(entry.status);
|
|
1896
|
+
return `${index + 1}. [${status}] ${step}`;
|
|
1897
|
+
});
|
|
1898
|
+
if (lines.length === 0)
|
|
1899
|
+
return "";
|
|
1900
|
+
const header = explanation ? `Plan update: ${explanation}` : "Plan update:";
|
|
1901
|
+
return `${header}\n${lines.join("\n")}`;
|
|
1902
|
+
}
|
|
1903
|
+
function normalizePlanStatus(raw) {
|
|
1904
|
+
switch (raw) {
|
|
1905
|
+
case "inProgress":
|
|
1906
|
+
return "in progress";
|
|
1907
|
+
case "completed":
|
|
1908
|
+
return "completed";
|
|
1909
|
+
case "pending":
|
|
1910
|
+
default:
|
|
1911
|
+
return "pending";
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function extensionFromMime(mimeType) {
|
|
1915
|
+
switch (mimeType) {
|
|
1916
|
+
case "image/png":
|
|
1917
|
+
return "png";
|
|
1918
|
+
case "image/jpeg":
|
|
1919
|
+
return "jpg";
|
|
1920
|
+
case "image/webp":
|
|
1921
|
+
return "webp";
|
|
1922
|
+
case "image/gif":
|
|
1923
|
+
return "gif";
|
|
1924
|
+
default:
|
|
1925
|
+
return null;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
//# sourceMappingURL=codex-process.js.map
|