@btatum5/codex-bridge 0.1.0 → 1.3.2
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 +472 -30
- package/bin/codex-bridge.js +136 -100
- package/bin/phodex.js +8 -0
- package/bin/remodex.js +8 -0
- package/package.json +29 -15
- package/src/bridge.js +620 -0
- package/src/codex-desktop-refresher.js +776 -0
- package/src/codex-transport.js +238 -0
- package/src/daemon-state.js +170 -0
- package/src/desktop-handler.js +407 -0
- package/src/git-handler.js +1267 -0
- package/src/index.js +35 -0
- package/src/macos-launch-agent.js +457 -0
- package/src/notifications-handler.js +95 -0
- package/src/push-notification-completion-dedupe.js +147 -0
- package/src/push-notification-service-client.js +151 -0
- package/src/push-notification-tracker.js +688 -0
- package/src/qr.js +19 -0
- package/src/rollout-live-mirror.js +730 -0
- package/src/rollout-watch.js +853 -0
- package/src/scripts/codex-handoff.applescript +100 -0
- package/src/scripts/codex-refresh.applescript +51 -0
- package/src/secure-device-state.js +430 -0
- package/src/secure-transport.js +738 -0
- package/src/session-state.js +62 -0
- package/src/thread-context-handler.js +80 -0
- package/src/workspace-handler.js +464 -0
- package/server.mjs +0 -290
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
// FILE: rollout-live-mirror.js
|
|
2
|
+
// Purpose: Mirrors desktop-origin rollout activity back into live bridge notifications for iPhone catch-up.
|
|
3
|
+
// Layer: CLI helper
|
|
4
|
+
// Exports: createRolloutLiveMirrorController
|
|
5
|
+
// Depends on: fs, ./rollout-watch
|
|
6
|
+
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const {
|
|
9
|
+
findRecentRolloutFileForContextRead,
|
|
10
|
+
resolveSessionsRoot,
|
|
11
|
+
} = require("./rollout-watch");
|
|
12
|
+
|
|
13
|
+
const DEFAULT_POLL_INTERVAL_MS = 700;
|
|
14
|
+
const DEFAULT_LOOKUP_TIMEOUT_MS = 5_000;
|
|
15
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
|
|
16
|
+
const DESKTOP_RESUME_METHODS = new Set(["thread/read", "thread/resume"]);
|
|
17
|
+
|
|
18
|
+
// Observes desktop-authored rollout files and replays the currently active run as
|
|
19
|
+
// bridge notifications so the phone can render live thinking/tool activity.
|
|
20
|
+
function createRolloutLiveMirrorController({
|
|
21
|
+
sendApplicationResponse,
|
|
22
|
+
logPrefix = "[codex-bridge]",
|
|
23
|
+
fsModule = fs,
|
|
24
|
+
now = () => Date.now(),
|
|
25
|
+
setIntervalFn = setInterval,
|
|
26
|
+
clearIntervalFn = clearInterval,
|
|
27
|
+
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
28
|
+
lookupTimeoutMs = DEFAULT_LOOKUP_TIMEOUT_MS,
|
|
29
|
+
idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,
|
|
30
|
+
} = {}) {
|
|
31
|
+
const mirrorsByThreadId = new Map();
|
|
32
|
+
|
|
33
|
+
function observeInbound(rawMessage) {
|
|
34
|
+
const request = safeParseJSON(rawMessage);
|
|
35
|
+
const method = readString(request?.method);
|
|
36
|
+
if (!DESKTOP_RESUME_METHODS.has(method)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const threadId = readThreadId(request?.params);
|
|
41
|
+
if (!threadId) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const existingMirror = mirrorsByThreadId.get(threadId);
|
|
46
|
+
if (existingMirror) {
|
|
47
|
+
existingMirror.bump();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let mirror;
|
|
52
|
+
mirror = createThreadRolloutLiveMirror({
|
|
53
|
+
threadId,
|
|
54
|
+
sendApplicationResponse,
|
|
55
|
+
logPrefix,
|
|
56
|
+
fsModule,
|
|
57
|
+
now,
|
|
58
|
+
setIntervalFn,
|
|
59
|
+
clearIntervalFn,
|
|
60
|
+
pollIntervalMs,
|
|
61
|
+
lookupTimeoutMs,
|
|
62
|
+
idleTimeoutMs,
|
|
63
|
+
onStop() {
|
|
64
|
+
if (mirrorsByThreadId.get(threadId) === mirror) {
|
|
65
|
+
mirrorsByThreadId.delete(threadId);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
mirrorsByThreadId.set(threadId, mirror);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function stopAll() {
|
|
73
|
+
for (const mirror of mirrorsByThreadId.values()) {
|
|
74
|
+
mirror.stop();
|
|
75
|
+
}
|
|
76
|
+
mirrorsByThreadId.clear();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
observeInbound,
|
|
81
|
+
stopAll,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Tails one thread rollout and emits synthetic app-server-like notifications for
|
|
86
|
+
// the currently active desktop-origin run only.
|
|
87
|
+
function createThreadRolloutLiveMirror({
|
|
88
|
+
threadId,
|
|
89
|
+
sendApplicationResponse,
|
|
90
|
+
logPrefix,
|
|
91
|
+
fsModule,
|
|
92
|
+
now,
|
|
93
|
+
setIntervalFn,
|
|
94
|
+
clearIntervalFn,
|
|
95
|
+
pollIntervalMs,
|
|
96
|
+
lookupTimeoutMs,
|
|
97
|
+
idleTimeoutMs,
|
|
98
|
+
onStop = () => {},
|
|
99
|
+
}) {
|
|
100
|
+
const startedAt = now();
|
|
101
|
+
const state = createMirrorState(threadId);
|
|
102
|
+
|
|
103
|
+
let isStopped = false;
|
|
104
|
+
let rolloutPath = null;
|
|
105
|
+
let lastSize = 0;
|
|
106
|
+
let partialLine = "";
|
|
107
|
+
let lastActivityAt = startedAt;
|
|
108
|
+
let didBootstrap = false;
|
|
109
|
+
|
|
110
|
+
const intervalId = setIntervalFn(tick, pollIntervalMs);
|
|
111
|
+
tick();
|
|
112
|
+
|
|
113
|
+
function tick() {
|
|
114
|
+
if (isStopped) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const currentTime = now();
|
|
120
|
+
|
|
121
|
+
if (!rolloutPath) {
|
|
122
|
+
if (currentTime - startedAt >= lookupTimeoutMs) {
|
|
123
|
+
stop();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
rolloutPath = findRecentRolloutFileForContextRead(resolveSessionsRoot(), {
|
|
128
|
+
threadId,
|
|
129
|
+
fsModule,
|
|
130
|
+
});
|
|
131
|
+
if (!rolloutPath) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const fileSize = readFileSize(rolloutPath, fsModule);
|
|
137
|
+
if (!didBootstrap) {
|
|
138
|
+
didBootstrap = true;
|
|
139
|
+
bootstrapFromExistingRollout({
|
|
140
|
+
rolloutPath,
|
|
141
|
+
fileSize,
|
|
142
|
+
state,
|
|
143
|
+
fsModule,
|
|
144
|
+
sendApplicationResponse,
|
|
145
|
+
});
|
|
146
|
+
lastSize = fileSize;
|
|
147
|
+
lastActivityAt = currentTime;
|
|
148
|
+
if (state.isDesktopOrigin === false) {
|
|
149
|
+
stop();
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (fileSize > lastSize) {
|
|
155
|
+
const chunk = readFileSlice(rolloutPath, lastSize, fileSize, fsModule);
|
|
156
|
+
lastSize = fileSize;
|
|
157
|
+
lastActivityAt = currentTime;
|
|
158
|
+
if (!chunk) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const combined = `${partialLine}${chunk}`;
|
|
163
|
+
const lines = combined.split("\n");
|
|
164
|
+
partialLine = lines.pop() || "";
|
|
165
|
+
processRolloutLines(lines, state, sendApplicationResponse);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (currentTime - lastActivityAt >= idleTimeoutMs) {
|
|
170
|
+
stop();
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.warn(`${logPrefix} rollout live mirror stopped for ${threadId}: ${error.message}`);
|
|
174
|
+
stop();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function bump() {
|
|
179
|
+
lastActivityAt = now();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function stop() {
|
|
183
|
+
if (isStopped) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
isStopped = true;
|
|
188
|
+
clearIntervalFn(intervalId);
|
|
189
|
+
onStop();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
bump,
|
|
194
|
+
stop,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function bootstrapFromExistingRollout({
|
|
199
|
+
rolloutPath,
|
|
200
|
+
fileSize,
|
|
201
|
+
state,
|
|
202
|
+
fsModule,
|
|
203
|
+
sendApplicationResponse,
|
|
204
|
+
}) {
|
|
205
|
+
const initialContents = readFileSlice(rolloutPath, 0, fileSize, fsModule);
|
|
206
|
+
if (!initialContents) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const lines = initialContents.split("\n");
|
|
211
|
+
const activeRunLines = [];
|
|
212
|
+
let insideActiveRun = false;
|
|
213
|
+
let activeTurnId = null;
|
|
214
|
+
let pendingUserPreludeLine = null;
|
|
215
|
+
|
|
216
|
+
for (const rawLine of lines) {
|
|
217
|
+
const line = rawLine.trim();
|
|
218
|
+
if (!line) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const parsed = safeParseJSON(line);
|
|
223
|
+
if (!parsed) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (parsed.type === "session_meta") {
|
|
228
|
+
populateSessionMetaState(state, parsed.payload);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const taskEventType = parsed?.type === "event_msg"
|
|
232
|
+
? readString(parsed?.payload?.type)
|
|
233
|
+
: "";
|
|
234
|
+
if (taskEventType === "user_message") {
|
|
235
|
+
pendingUserPreludeLine = line;
|
|
236
|
+
}
|
|
237
|
+
if (taskEventType === "task_started") {
|
|
238
|
+
insideActiveRun = true;
|
|
239
|
+
activeTurnId = readString(parsed?.payload?.turn_id)
|
|
240
|
+
|| readString(parsed?.payload?.turnId)
|
|
241
|
+
|| "";
|
|
242
|
+
activeRunLines.length = 0;
|
|
243
|
+
if (pendingUserPreludeLine) {
|
|
244
|
+
activeRunLines.push(pendingUserPreludeLine);
|
|
245
|
+
}
|
|
246
|
+
activeRunLines.push(line);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!insideActiveRun) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
activeRunLines.push(line);
|
|
255
|
+
if (taskEventType === "task_complete") {
|
|
256
|
+
insideActiveRun = false;
|
|
257
|
+
activeTurnId = "";
|
|
258
|
+
activeRunLines.length = 0;
|
|
259
|
+
pendingUserPreludeLine = null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!isDesktopRolloutOrigin(state.sessionMeta)) {
|
|
264
|
+
state.isDesktopOrigin = false;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
state.isDesktopOrigin = true;
|
|
269
|
+
if (activeTurnId) {
|
|
270
|
+
state.activeTurnId = activeTurnId;
|
|
271
|
+
}
|
|
272
|
+
processRolloutLines(activeRunLines, state, sendApplicationResponse);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function processRolloutLines(lines, state, sendApplicationResponse) {
|
|
276
|
+
if (!Array.isArray(lines) || lines.length === 0) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
for (const rawLine of lines) {
|
|
281
|
+
const line = rawLine.trim();
|
|
282
|
+
if (!line) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const parsed = safeParseJSON(line);
|
|
287
|
+
if (!parsed) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const notifications = synthesizeNotificationsFromRolloutEntry(parsed, state);
|
|
292
|
+
for (const notification of notifications) {
|
|
293
|
+
sendApplicationResponse(JSON.stringify(notification));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function synthesizeNotificationsFromRolloutEntry(entry, state) {
|
|
299
|
+
if (entry?.type === "session_meta") {
|
|
300
|
+
populateSessionMetaState(state, entry.payload);
|
|
301
|
+
if (!isDesktopRolloutOrigin(state.sessionMeta)) {
|
|
302
|
+
state.isDesktopOrigin = false;
|
|
303
|
+
} else if (state.isDesktopOrigin == null) {
|
|
304
|
+
state.isDesktopOrigin = true;
|
|
305
|
+
}
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (state.isDesktopOrigin === false) {
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const notifications = [];
|
|
314
|
+
|
|
315
|
+
if (entry?.type === "event_msg") {
|
|
316
|
+
const payload = entry.payload || {};
|
|
317
|
+
const eventType = readString(payload.type);
|
|
318
|
+
|
|
319
|
+
if (eventType === "task_started") {
|
|
320
|
+
const turnId = readString(payload.turn_id) || readString(payload.turnId);
|
|
321
|
+
if (!turnId) {
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
state.activeTurnId = turnId;
|
|
326
|
+
state.reasoningItemId = buildSyntheticItemId("thinking", state.threadId, turnId);
|
|
327
|
+
state.hasThinking = false;
|
|
328
|
+
state.commandCalls.clear();
|
|
329
|
+
|
|
330
|
+
notifications.push(createNotification("turn/started", {
|
|
331
|
+
threadId: state.threadId,
|
|
332
|
+
turnId,
|
|
333
|
+
id: turnId,
|
|
334
|
+
}));
|
|
335
|
+
notifications.push(...ensureThinkingNotifications(state));
|
|
336
|
+
return notifications;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (eventType === "user_message") {
|
|
340
|
+
const message = readString(payload.message) || readString(payload.text);
|
|
341
|
+
if (!message) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
notifications.push(createNotification("codex/event/user_message", {
|
|
346
|
+
threadId: state.threadId,
|
|
347
|
+
turnId: readString(payload.turn_id) || readString(payload.turnId) || state.activeTurnId || "",
|
|
348
|
+
message,
|
|
349
|
+
}));
|
|
350
|
+
return notifications;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (eventType === "task_complete") {
|
|
354
|
+
const turnId = readString(payload.turn_id) || readString(payload.turnId) || state.activeTurnId;
|
|
355
|
+
if (!turnId) {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
notifications.push(createNotification("turn/completed", {
|
|
360
|
+
threadId: state.threadId,
|
|
361
|
+
turnId,
|
|
362
|
+
id: turnId,
|
|
363
|
+
}));
|
|
364
|
+
resetRunState(state);
|
|
365
|
+
return notifications;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (eventType === "agent_reasoning") {
|
|
369
|
+
notifications.push(...reasoningNotifications(state, firstNonEmptyString([
|
|
370
|
+
readString(payload.message),
|
|
371
|
+
readString(payload.text),
|
|
372
|
+
readString(payload.summary),
|
|
373
|
+
])));
|
|
374
|
+
return notifications;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (eventType === "agent_message") {
|
|
378
|
+
const message = readString(payload.message) || readString(payload.text);
|
|
379
|
+
if (!message || !shouldMirrorAgentMessage(payload)) {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
notifications.push(createNotification("codex/event/agent_message", {
|
|
384
|
+
threadId: state.threadId,
|
|
385
|
+
turnId: readString(payload.turn_id) || readString(payload.turnId) || state.activeTurnId || "",
|
|
386
|
+
message,
|
|
387
|
+
}));
|
|
388
|
+
return notifications;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (entry?.type !== "response_item") {
|
|
395
|
+
return [];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const payload = entry.payload || {};
|
|
399
|
+
const itemType = readString(payload.type);
|
|
400
|
+
|
|
401
|
+
if (itemType === "reasoning") {
|
|
402
|
+
notifications.push(...reasoningNotifications(state, extractReasoningText(payload)));
|
|
403
|
+
return notifications;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (itemType === "function_call") {
|
|
407
|
+
notifications.push(...toolStartNotifications(state, payload));
|
|
408
|
+
return notifications;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (itemType === "function_call_output") {
|
|
412
|
+
notifications.push(...toolOutputNotifications(state, payload));
|
|
413
|
+
return notifications;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return notifications;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function reasoningNotifications(state, text) {
|
|
420
|
+
if (!state.activeTurnId) {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const delta = readString(text);
|
|
425
|
+
if (!delta) {
|
|
426
|
+
return ensureThinkingNotifications(state);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
state.hasThinking = true;
|
|
430
|
+
return [
|
|
431
|
+
createNotification("item/reasoning/textDelta", {
|
|
432
|
+
threadId: state.threadId,
|
|
433
|
+
turnId: state.activeTurnId,
|
|
434
|
+
itemId: state.reasoningItemId || buildSyntheticItemId("thinking", state.threadId, state.activeTurnId),
|
|
435
|
+
delta,
|
|
436
|
+
}),
|
|
437
|
+
];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function toolStartNotifications(state, payload) {
|
|
441
|
+
if (!state.activeTurnId) {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const callId = readString(payload.call_id) || readString(payload.callId);
|
|
446
|
+
const toolName = readString(payload.name);
|
|
447
|
+
if (!callId || !toolName) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const argumentsObject = parseToolArguments(payload.arguments);
|
|
452
|
+
state.commandCalls.set(callId, {
|
|
453
|
+
toolName,
|
|
454
|
+
command: resolveToolCommand(toolName, argumentsObject),
|
|
455
|
+
cwd: resolveToolWorkingDirectory(argumentsObject, state),
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if (isCommandToolName(toolName)) {
|
|
459
|
+
const command = state.commandCalls.get(callId)?.command || toolName;
|
|
460
|
+
return [
|
|
461
|
+
...ensureThinkingNotifications(state),
|
|
462
|
+
createNotification("codex/event/exec_command_begin", {
|
|
463
|
+
threadId: state.threadId,
|
|
464
|
+
turnId: state.activeTurnId,
|
|
465
|
+
call_id: callId,
|
|
466
|
+
command,
|
|
467
|
+
cwd: state.commandCalls.get(callId)?.cwd || state.sessionMeta?.cwd || "",
|
|
468
|
+
status: "running",
|
|
469
|
+
}),
|
|
470
|
+
];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const activityMessage = genericToolActivityMessage(toolName);
|
|
474
|
+
if (!activityMessage) {
|
|
475
|
+
return ensureThinkingNotifications(state);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return [
|
|
479
|
+
...ensureThinkingNotifications(state),
|
|
480
|
+
createNotification("codex/event/background_event", {
|
|
481
|
+
threadId: state.threadId,
|
|
482
|
+
turnId: state.activeTurnId,
|
|
483
|
+
call_id: callId,
|
|
484
|
+
message: activityMessage,
|
|
485
|
+
}),
|
|
486
|
+
];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function toolOutputNotifications(state, payload) {
|
|
490
|
+
if (!state.activeTurnId) {
|
|
491
|
+
return [];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const callId = readString(payload.call_id) || readString(payload.callId);
|
|
495
|
+
if (!callId) {
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const toolCall = state.commandCalls.get(callId);
|
|
500
|
+
if (!toolCall) {
|
|
501
|
+
return [];
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!isCommandToolName(toolCall.toolName)) {
|
|
505
|
+
state.commandCalls.delete(callId);
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const output = readString(payload.output);
|
|
510
|
+
const notifications = [...ensureThinkingNotifications(state)];
|
|
511
|
+
if (output) {
|
|
512
|
+
notifications.push(createNotification("codex/event/exec_command_output_delta", {
|
|
513
|
+
threadId: state.threadId,
|
|
514
|
+
turnId: state.activeTurnId,
|
|
515
|
+
call_id: callId,
|
|
516
|
+
command: toolCall.command,
|
|
517
|
+
cwd: toolCall.cwd || "",
|
|
518
|
+
chunk: output,
|
|
519
|
+
}));
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
notifications.push(createNotification("codex/event/exec_command_end", {
|
|
523
|
+
threadId: state.threadId,
|
|
524
|
+
turnId: state.activeTurnId,
|
|
525
|
+
call_id: callId,
|
|
526
|
+
command: toolCall.command,
|
|
527
|
+
cwd: toolCall.cwd || "",
|
|
528
|
+
status: "completed",
|
|
529
|
+
output: output || "",
|
|
530
|
+
}));
|
|
531
|
+
state.commandCalls.delete(callId);
|
|
532
|
+
return notifications;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function ensureThinkingNotifications(state) {
|
|
536
|
+
if (!state.activeTurnId || state.hasThinking) {
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
state.hasThinking = true;
|
|
541
|
+
if (!state.reasoningItemId) {
|
|
542
|
+
state.reasoningItemId = buildSyntheticItemId("thinking", state.threadId, state.activeTurnId);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return [
|
|
546
|
+
createNotification("item/reasoning/textDelta", {
|
|
547
|
+
threadId: state.threadId,
|
|
548
|
+
turnId: state.activeTurnId,
|
|
549
|
+
itemId: state.reasoningItemId,
|
|
550
|
+
delta: "Thinking...",
|
|
551
|
+
}),
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function createMirrorState(threadId) {
|
|
556
|
+
return {
|
|
557
|
+
threadId,
|
|
558
|
+
sessionMeta: null,
|
|
559
|
+
isDesktopOrigin: null,
|
|
560
|
+
activeTurnId: null,
|
|
561
|
+
reasoningItemId: null,
|
|
562
|
+
hasThinking: false,
|
|
563
|
+
commandCalls: new Map(),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function populateSessionMetaState(state, payload) {
|
|
568
|
+
if (!payload || typeof payload !== "object") {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
state.sessionMeta = {
|
|
573
|
+
originator: readString(payload.originator),
|
|
574
|
+
source: readString(payload.source),
|
|
575
|
+
cwd: readString(payload.cwd),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function isDesktopRolloutOrigin(sessionMeta) {
|
|
580
|
+
const originator = readString(sessionMeta?.originator).toLowerCase();
|
|
581
|
+
const source = readString(sessionMeta?.source).toLowerCase();
|
|
582
|
+
if (!originator && !source) {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (originator.includes("mobile") || originator.includes("ios")) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return originator.includes("desktop")
|
|
591
|
+
|| originator.includes("vscode")
|
|
592
|
+
|| source.includes("vscode")
|
|
593
|
+
|| source.includes("desktop");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function extractReasoningText(payload) {
|
|
597
|
+
const summary = Array.isArray(payload?.summary)
|
|
598
|
+
? payload.summary
|
|
599
|
+
.map((part) => readString(part?.text) || readString(part?.summary))
|
|
600
|
+
.filter(Boolean)
|
|
601
|
+
.join("\n")
|
|
602
|
+
: "";
|
|
603
|
+
return firstNonEmptyString([
|
|
604
|
+
summary,
|
|
605
|
+
readString(payload?.text),
|
|
606
|
+
readString(payload?.content),
|
|
607
|
+
]);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function parseToolArguments(rawArguments) {
|
|
611
|
+
const parsed = safeParseJSON(rawArguments);
|
|
612
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function resolveToolCommand(toolName, argumentsObject) {
|
|
616
|
+
if (isCommandToolName(toolName)) {
|
|
617
|
+
return firstNonEmptyString([
|
|
618
|
+
readString(argumentsObject.cmd),
|
|
619
|
+
readString(argumentsObject.command),
|
|
620
|
+
readString(argumentsObject.raw_command),
|
|
621
|
+
readString(argumentsObject.rawCommand),
|
|
622
|
+
]) || toolName;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
return toolName;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function resolveToolWorkingDirectory(argumentsObject, state) {
|
|
629
|
+
return firstNonEmptyString([
|
|
630
|
+
readString(argumentsObject.workdir),
|
|
631
|
+
readString(argumentsObject.cwd),
|
|
632
|
+
readString(argumentsObject.working_directory),
|
|
633
|
+
readString(state.sessionMeta?.cwd),
|
|
634
|
+
]) || "";
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function isCommandToolName(toolName) {
|
|
638
|
+
const normalized = readString(toolName).toLowerCase();
|
|
639
|
+
return normalized === "exec_command" || normalized === "shell_command";
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function genericToolActivityMessage(toolName) {
|
|
643
|
+
switch (readString(toolName).toLowerCase()) {
|
|
644
|
+
case "apply_patch":
|
|
645
|
+
return "Applying patch";
|
|
646
|
+
case "write_stdin":
|
|
647
|
+
return "Writing to terminal";
|
|
648
|
+
case "read_thread_terminal":
|
|
649
|
+
return "Reading terminal output";
|
|
650
|
+
default:
|
|
651
|
+
return `Running ${toolName}`;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function shouldMirrorAgentMessage(payload) {
|
|
656
|
+
const phase = readString(payload?.phase).toLowerCase();
|
|
657
|
+
return phase !== "commentary";
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function createNotification(method, params) {
|
|
661
|
+
return { method, params };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function buildSyntheticItemId(kind, threadId, turnId) {
|
|
665
|
+
return `rollout-${kind}:${threadId}:${turnId}`;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function resetRunState(state) {
|
|
669
|
+
state.activeTurnId = null;
|
|
670
|
+
state.reasoningItemId = null;
|
|
671
|
+
state.hasThinking = false;
|
|
672
|
+
state.commandCalls.clear();
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function readThreadId(params) {
|
|
676
|
+
return firstNonEmptyString([
|
|
677
|
+
readString(params?.threadId),
|
|
678
|
+
readString(params?.thread_id),
|
|
679
|
+
]) || "";
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function readFileSize(filePath, fsModule) {
|
|
683
|
+
return fsModule.statSync(filePath).size;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function readFileSlice(filePath, start, endExclusive, fsModule) {
|
|
687
|
+
const length = Math.max(0, endExclusive - start);
|
|
688
|
+
if (length === 0) {
|
|
689
|
+
return "";
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const fileHandle = fsModule.openSync(filePath, "r");
|
|
693
|
+
try {
|
|
694
|
+
const buffer = Buffer.alloc(length);
|
|
695
|
+
const bytesRead = fsModule.readSync(fileHandle, buffer, 0, length, start);
|
|
696
|
+
return buffer.toString("utf8", 0, bytesRead);
|
|
697
|
+
} finally {
|
|
698
|
+
fsModule.closeSync(fileHandle);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function safeParseJSON(rawValue) {
|
|
703
|
+
if (typeof rawValue !== "string" || !rawValue.trim()) {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
try {
|
|
708
|
+
return JSON.parse(rawValue);
|
|
709
|
+
} catch {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function readString(value) {
|
|
715
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function firstNonEmptyString(values) {
|
|
719
|
+
for (const value of values) {
|
|
720
|
+
if (typeof value === "string" && value.trim()) {
|
|
721
|
+
return value.trim();
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return "";
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
module.exports = {
|
|
728
|
+
createRolloutLiveMirrorController,
|
|
729
|
+
isDesktopRolloutOrigin,
|
|
730
|
+
};
|