@makerbi/remodex 2.4.0 → 3.1.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/bin/remodex.js +25 -2
- package/package.json +1 -1
- package/src/bridge.js +46 -61
- package/src/codex-desktop-refresher.js +173 -8
- package/src/codex-tool-wrapper.js +523 -0
- package/src/desktop-ipc-action-follower.js +316 -59
- package/src/desktop-ipc-live-owner.js +148 -6
- package/src/desktop-ipc-owner-transport.js +143 -73
- package/src/desktop-ipc-shared.js +125 -8
- package/src/git-handler.js +189 -108
- package/src/index.js +2 -0
- package/src/macos-launch-agent.js +65 -34
- package/src/rollout-live-mirror.js +142 -13
- package/src/rollout-watch.js +176 -54
- package/src/session-jsonl-history.js +72 -32
- package/src/thread-list-provenance.js +105 -0
- package/src/thread-row-enrichment.js +43 -0
- package/src/thread-runtime-settings-store.js +3 -21
- package/src/worktree-origin.js +192 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// FILE: macos-launch-agent.js
|
|
2
|
-
// Purpose: Owns macOS-only launchd install/start/stop/status helpers for the background Remodex bridge.
|
|
2
|
+
// Purpose: Owns macOS-only launchd install/start/stop/uninstall/status helpers for the background Remodex bridge.
|
|
3
3
|
// Layer: CLI helper
|
|
4
|
-
// Exports: start/stop/status helpers plus the launchd service runner used by `remodex up`.
|
|
4
|
+
// Exports: start/stop/uninstall/status helpers plus the launchd service runner used by `remodex up`.
|
|
5
5
|
// Depends on: child_process, fs, os, path, ./bridge, ./daemon-state, ./codex-desktop-refresher, ./qr, ./secure-device-state
|
|
6
6
|
|
|
7
7
|
const { execFileSync } = require("child_process");
|
|
@@ -33,6 +33,23 @@ const SERVICE_LABEL = "com.remodex.bridge";
|
|
|
33
33
|
const DEFAULT_PAIRING_WAIT_TIMEOUT_MS = 10_000;
|
|
34
34
|
const DEFAULT_PAIRING_WAIT_INTERVAL_MS = 200;
|
|
35
35
|
|
|
36
|
+
// If the saved Node binary or CLI entrypoint disappears (npm uninstall, deleted
|
|
37
|
+
// checkout), exit 0 so launchd's KeepAlive.SuccessfulExit=false stops rescheduling
|
|
38
|
+
// the job; `exec` keeps genuine daemon failures non-zero so they still restart.
|
|
39
|
+
const LAUNCH_AGENT_GUARD_SCRIPT = 'if [ ! -x "$1" ] || [ ! -f "$2" ]; then exit 0; fi; exec "$1" "$2" run-service';
|
|
40
|
+
|
|
41
|
+
// Keeps the guard script constant: the installed paths are shell positionals, never interpolated source.
|
|
42
|
+
function buildLaunchAgentProgramArguments({ nodePath, cliPath }) {
|
|
43
|
+
return [
|
|
44
|
+
"/bin/sh",
|
|
45
|
+
"-c",
|
|
46
|
+
LAUNCH_AGENT_GUARD_SCRIPT,
|
|
47
|
+
SERVICE_LABEL,
|
|
48
|
+
nodePath,
|
|
49
|
+
cliPath,
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
// Runs the bridge inside launchd while keeping QR rendering in the foreground CLI command.
|
|
37
54
|
function runMacOSBridgeService({ env = process.env, platform = process.platform } = {}) {
|
|
38
55
|
assertDarwinPlatform(platform);
|
|
@@ -123,17 +140,19 @@ async function startMacOSBridgeService({
|
|
|
123
140
|
};
|
|
124
141
|
}
|
|
125
142
|
|
|
126
|
-
// Restarts the installed LaunchAgent without rewriting relay config,
|
|
143
|
+
// Restarts the installed LaunchAgent without rewriting relay config, regenerating the plist so
|
|
144
|
+
// legacy launch definitions pick up the current Node/CLI paths and launch policy.
|
|
127
145
|
async function restartMacOSBridgeService({
|
|
128
146
|
env = process.env,
|
|
129
147
|
platform = process.platform,
|
|
130
148
|
fsImpl = fs,
|
|
131
149
|
execFileSyncImpl = execFileSync,
|
|
132
150
|
osImpl = os,
|
|
151
|
+
nodePath = process.execPath,
|
|
152
|
+
cliPath = path.resolve(__dirname, "..", "bin", "remodex.js"),
|
|
133
153
|
waitForPairing = false,
|
|
134
154
|
pairingTimeoutMs = DEFAULT_PAIRING_WAIT_TIMEOUT_MS,
|
|
135
155
|
pairingPollIntervalMs = DEFAULT_PAIRING_WAIT_INTERVAL_MS,
|
|
136
|
-
...startOptions
|
|
137
156
|
} = {}) {
|
|
138
157
|
assertDarwinPlatform(platform);
|
|
139
158
|
const plistPath = resolveLaunchAgentPlistPath({ env, osImpl });
|
|
@@ -144,10 +163,11 @@ async function restartMacOSBridgeService({
|
|
|
144
163
|
fsImpl,
|
|
145
164
|
execFileSyncImpl,
|
|
146
165
|
osImpl,
|
|
166
|
+
nodePath,
|
|
167
|
+
cliPath,
|
|
147
168
|
waitForPairing,
|
|
148
169
|
pairingTimeoutMs,
|
|
149
170
|
pairingPollIntervalMs,
|
|
150
|
-
...startOptions,
|
|
151
171
|
});
|
|
152
172
|
}
|
|
153
173
|
|
|
@@ -156,7 +176,16 @@ async function restartMacOSBridgeService({
|
|
|
156
176
|
clearPairingSession({ env, fsImpl });
|
|
157
177
|
}
|
|
158
178
|
|
|
159
|
-
|
|
179
|
+
ensureRemodexStateDir({ env, fsImpl, osImpl });
|
|
180
|
+
ensureRemodexLogsDir({ env, fsImpl, osImpl });
|
|
181
|
+
writeLaunchAgentPlist({
|
|
182
|
+
env,
|
|
183
|
+
fsImpl,
|
|
184
|
+
osImpl,
|
|
185
|
+
nodePath,
|
|
186
|
+
cliPath,
|
|
187
|
+
});
|
|
188
|
+
restartLaunchAgent({
|
|
160
189
|
env,
|
|
161
190
|
execFileSyncImpl,
|
|
162
191
|
plistPath,
|
|
@@ -204,6 +233,31 @@ function stopMacOSBridgeService({
|
|
|
204
233
|
clearBridgeStatus({ env, fsImpl });
|
|
205
234
|
}
|
|
206
235
|
|
|
236
|
+
// Removes launchd ownership of the bridge (unload + plist) while preserving daemon config,
|
|
237
|
+
// logs, device trust, and pairing identity for a future reinstall.
|
|
238
|
+
function uninstallMacOSBridgeService({
|
|
239
|
+
env = process.env,
|
|
240
|
+
platform = process.platform,
|
|
241
|
+
execFileSyncImpl = execFileSync,
|
|
242
|
+
fsImpl = fs,
|
|
243
|
+
osImpl = os,
|
|
244
|
+
processImpl = process,
|
|
245
|
+
} = {}) {
|
|
246
|
+
assertDarwinPlatform(platform);
|
|
247
|
+
const plistPath = resolveLaunchAgentPlistPath({ env, osImpl });
|
|
248
|
+
const removed = fsImpl.existsSync(plistPath);
|
|
249
|
+
// Stop first: a real bootout failure throws here and leaves the plist on disk.
|
|
250
|
+
stopMacOSBridgeService({
|
|
251
|
+
env,
|
|
252
|
+
platform,
|
|
253
|
+
execFileSyncImpl,
|
|
254
|
+
fsImpl,
|
|
255
|
+
processImpl,
|
|
256
|
+
});
|
|
257
|
+
fsImpl.rmSync(plistPath, { force: true });
|
|
258
|
+
return { plistPath, removed };
|
|
259
|
+
}
|
|
260
|
+
|
|
207
261
|
// Revokes pairing immediately on macOS by stopping the daemon before rotating identity/trust state.
|
|
208
262
|
function resetMacOSBridgePairing({
|
|
209
263
|
env = process.env,
|
|
@@ -374,9 +428,9 @@ function buildLaunchAgentPlist({
|
|
|
374
428
|
<string>${escapeXml(SERVICE_LABEL)}</string>
|
|
375
429
|
<key>ProgramArguments</key>
|
|
376
430
|
<array>
|
|
377
|
-
|
|
378
|
-
<string>${escapeXml(
|
|
379
|
-
|
|
431
|
+
${buildLaunchAgentProgramArguments({ nodePath, cliPath })
|
|
432
|
+
.map((argument) => ` <string>${escapeXml(argument)}</string>`)
|
|
433
|
+
.join("\n")}
|
|
380
434
|
</array>
|
|
381
435
|
<key>RunAtLoad</key>
|
|
382
436
|
<true/>
|
|
@@ -455,31 +509,6 @@ function restartLaunchAgent({
|
|
|
455
509
|
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
456
510
|
}
|
|
457
511
|
|
|
458
|
-
function kickstartLaunchAgent({
|
|
459
|
-
env = process.env,
|
|
460
|
-
execFileSyncImpl = execFileSync,
|
|
461
|
-
plistPath,
|
|
462
|
-
} = {}) {
|
|
463
|
-
try {
|
|
464
|
-
execFileSyncImpl("launchctl", [
|
|
465
|
-
"kickstart",
|
|
466
|
-
"-k",
|
|
467
|
-
launchAgentLabelDomain(env),
|
|
468
|
-
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
469
|
-
} catch {
|
|
470
|
-
execFileSyncImpl("launchctl", [
|
|
471
|
-
"bootstrap",
|
|
472
|
-
launchAgentDomain(env),
|
|
473
|
-
plistPath,
|
|
474
|
-
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
475
|
-
execFileSyncImpl("launchctl", [
|
|
476
|
-
"kickstart",
|
|
477
|
-
"-k",
|
|
478
|
-
launchAgentLabelDomain(env),
|
|
479
|
-
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
512
|
function bootoutLaunchAgent({
|
|
484
513
|
env = process.env,
|
|
485
514
|
execFileSyncImpl = execFileSync,
|
|
@@ -675,6 +704,7 @@ function shortFingerprint(value) {
|
|
|
675
704
|
module.exports = {
|
|
676
705
|
buildTrustedDeviceSummary,
|
|
677
706
|
buildLaunchAgentPlist,
|
|
707
|
+
buildLaunchAgentProgramArguments,
|
|
678
708
|
getMacOSBridgeServiceStatus,
|
|
679
709
|
mergeBridgeStatusForDaemon,
|
|
680
710
|
printMacOSBridgePairingQr,
|
|
@@ -685,4 +715,5 @@ module.exports = {
|
|
|
685
715
|
runMacOSBridgeService,
|
|
686
716
|
startMacOSBridgeService,
|
|
687
717
|
stopMacOSBridgeService,
|
|
718
|
+
uninstallMacOSBridgeService,
|
|
688
719
|
};
|
|
@@ -13,6 +13,10 @@ const {
|
|
|
13
13
|
} = require("./rollout-watch");
|
|
14
14
|
const { resolveCodexGeneratedImagesRoot } = require("./codex-home");
|
|
15
15
|
const { buildApplyPatchFileChangeItem } = require("./apply-patch-changes");
|
|
16
|
+
const {
|
|
17
|
+
expandExecWrapperToolCall,
|
|
18
|
+
isOrchestrationWaitCall,
|
|
19
|
+
} = require("./codex-tool-wrapper");
|
|
16
20
|
const {
|
|
17
21
|
TERMINAL_TASK_EVENT_TYPES,
|
|
18
22
|
terminalEventClosesTrackedTurn,
|
|
@@ -56,6 +60,8 @@ function createRolloutLiveMirrorController({
|
|
|
56
60
|
now = () => Date.now(),
|
|
57
61
|
setIntervalFn = setInterval,
|
|
58
62
|
clearIntervalFn = clearInterval,
|
|
63
|
+
setImmediateFn = setImmediate,
|
|
64
|
+
clearImmediateFn = clearImmediate,
|
|
59
65
|
pollIntervalMs = DEFAULT_POLL_INTERVAL_MS,
|
|
60
66
|
lookupTimeoutMs = DEFAULT_LOOKUP_TIMEOUT_MS,
|
|
61
67
|
idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,
|
|
@@ -110,6 +116,8 @@ function createRolloutLiveMirrorController({
|
|
|
110
116
|
now,
|
|
111
117
|
setIntervalFn,
|
|
112
118
|
clearIntervalFn,
|
|
119
|
+
setImmediateFn,
|
|
120
|
+
clearImmediateFn,
|
|
113
121
|
pollIntervalMs,
|
|
114
122
|
lookupTimeoutMs,
|
|
115
123
|
idleTimeoutMs,
|
|
@@ -157,6 +165,8 @@ function createThreadRolloutLiveMirror({
|
|
|
157
165
|
now,
|
|
158
166
|
setIntervalFn,
|
|
159
167
|
clearIntervalFn,
|
|
168
|
+
setImmediateFn,
|
|
169
|
+
clearImmediateFn,
|
|
160
170
|
pollIntervalMs,
|
|
161
171
|
lookupTimeoutMs,
|
|
162
172
|
idleTimeoutMs,
|
|
@@ -166,6 +176,7 @@ function createThreadRolloutLiveMirror({
|
|
|
166
176
|
onStop = () => {},
|
|
167
177
|
}) {
|
|
168
178
|
const startedAt = now();
|
|
179
|
+
let lookupStartedAt = startedAt;
|
|
169
180
|
const state = createMirrorState(threadId);
|
|
170
181
|
|
|
171
182
|
let isStopped = false;
|
|
@@ -182,7 +193,10 @@ function createThreadRolloutLiveMirror({
|
|
|
182
193
|
let wasSuppressed = false;
|
|
183
194
|
|
|
184
195
|
const intervalId = setIntervalFn(tick, pollIntervalMs);
|
|
185
|
-
|
|
196
|
+
let initialTickId = setImmediateFn(() => {
|
|
197
|
+
initialTickId = null;
|
|
198
|
+
tick();
|
|
199
|
+
});
|
|
186
200
|
|
|
187
201
|
function tick() {
|
|
188
202
|
if (isStopped) {
|
|
@@ -191,9 +205,30 @@ function createThreadRolloutLiveMirror({
|
|
|
191
205
|
|
|
192
206
|
try {
|
|
193
207
|
const currentTime = now();
|
|
208
|
+
const suppressedBeforeScan = isSuppressed();
|
|
209
|
+
if (suppressedBeforeScan) {
|
|
210
|
+
if (!wasSuppressed) {
|
|
211
|
+
rolloutPath = null;
|
|
212
|
+
lastSize = 0;
|
|
213
|
+
partialLine = "";
|
|
214
|
+
didBootstrap = false;
|
|
215
|
+
resetRunState(state);
|
|
216
|
+
}
|
|
217
|
+
wasSuppressed = true;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (wasSuppressed) {
|
|
221
|
+
rolloutPath = null;
|
|
222
|
+
lastSize = 0;
|
|
223
|
+
partialLine = "";
|
|
224
|
+
didBootstrap = false;
|
|
225
|
+
resetRunState(state);
|
|
226
|
+
lookupStartedAt = currentTime;
|
|
227
|
+
wasSuppressed = false;
|
|
228
|
+
}
|
|
194
229
|
|
|
195
230
|
if (!rolloutPath) {
|
|
196
|
-
if (currentTime -
|
|
231
|
+
if (currentTime - lookupStartedAt >= lookupTimeoutMs) {
|
|
197
232
|
stop();
|
|
198
233
|
return;
|
|
199
234
|
}
|
|
@@ -209,20 +244,21 @@ function createThreadRolloutLiveMirror({
|
|
|
209
244
|
|
|
210
245
|
const rolloutStat = fsModule.statSync(rolloutPath);
|
|
211
246
|
const fileSize = rolloutStat.size;
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
// from a stale connected snapshot.
|
|
247
|
+
// Re-check ownership with the rollout's activity time before bootstrapping.
|
|
248
|
+
// If another source owns the thread, leave the file untouched until that
|
|
249
|
+
// ownership expires.
|
|
216
250
|
const suppressed = isSuppressed({
|
|
217
251
|
fallbackActivityAt: Number(rolloutStat.mtimeMs) || 0,
|
|
218
252
|
});
|
|
219
|
-
if (
|
|
253
|
+
if (suppressed) {
|
|
254
|
+
rolloutPath = null;
|
|
220
255
|
lastSize = 0;
|
|
221
256
|
partialLine = "";
|
|
222
257
|
didBootstrap = false;
|
|
223
258
|
resetRunState(state);
|
|
259
|
+
wasSuppressed = true;
|
|
260
|
+
return;
|
|
224
261
|
}
|
|
225
|
-
wasSuppressed = suppressed;
|
|
226
262
|
if (!didBootstrap) {
|
|
227
263
|
didBootstrap = true;
|
|
228
264
|
bootstrapFromExistingRollout({
|
|
@@ -354,6 +390,10 @@ function createThreadRolloutLiveMirror({
|
|
|
354
390
|
// final partial-line flush must never leak the poll interval.
|
|
355
391
|
isStopped = true;
|
|
356
392
|
clearIntervalFn(intervalId);
|
|
393
|
+
if (initialTickId != null) {
|
|
394
|
+
clearImmediateFn(initialTickId);
|
|
395
|
+
initialTickId = null;
|
|
396
|
+
}
|
|
357
397
|
if (partialLine) {
|
|
358
398
|
const flushLine = partialLine;
|
|
359
399
|
partialLine = "";
|
|
@@ -369,8 +409,8 @@ function createThreadRolloutLiveMirror({
|
|
|
369
409
|
// Only a healthy, actively-tailed run with a real id counts: synthetic ids
|
|
370
410
|
// are not actionable app-server turn ids, and suppressed/awaiting states
|
|
371
411
|
// mean the mirror does not actually know what is running. While another live
|
|
372
|
-
// source owns the thread the
|
|
373
|
-
//
|
|
412
|
+
// source owns the thread the mirror has no parsed file state, so reporting a
|
|
413
|
+
// turn id would resurrect exactly the state the bridge muted.
|
|
374
414
|
function getActiveTurnId() {
|
|
375
415
|
if (
|
|
376
416
|
isStopped
|
|
@@ -950,6 +990,7 @@ function synthesizeNotificationsFromRolloutEntry(entry, state, { nowMs = Date.no
|
|
|
950
990
|
state.commandCalls.clear();
|
|
951
991
|
state.applyPatchCalls.clear();
|
|
952
992
|
state.emittedPatchApplyEndCalls.clear();
|
|
993
|
+
state.wrappedExecCallIdsByOuterId.clear();
|
|
953
994
|
|
|
954
995
|
const startedParams = {
|
|
955
996
|
threadId: state.threadId,
|
|
@@ -1081,12 +1122,12 @@ function synthesizeNotificationsFromRolloutEntry(entry, state, { nowMs = Date.no
|
|
|
1081
1122
|
}
|
|
1082
1123
|
|
|
1083
1124
|
if (itemType === "functioncall") {
|
|
1084
|
-
notifications.push(...
|
|
1125
|
+
notifications.push(...projectedToolStartNotifications(state, payload));
|
|
1085
1126
|
return notifications;
|
|
1086
1127
|
}
|
|
1087
1128
|
|
|
1088
1129
|
if (itemType === "customtoolcall") {
|
|
1089
|
-
notifications.push(...
|
|
1130
|
+
notifications.push(...projectedToolStartNotifications(state, payload));
|
|
1090
1131
|
return notifications;
|
|
1091
1132
|
}
|
|
1092
1133
|
|
|
@@ -1326,6 +1367,29 @@ function extractResponseItemMessageText(payload) {
|
|
|
1326
1367
|
return responseItemMessageText(payload);
|
|
1327
1368
|
}
|
|
1328
1369
|
|
|
1370
|
+
function projectedToolStartNotifications(state, payload) {
|
|
1371
|
+
if (isOrchestrationWaitCall(payload)) {
|
|
1372
|
+
return [];
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
const projectedPayloads = expandExecWrapperToolCall(payload);
|
|
1376
|
+
const outerCallId = projectedPayloads[0]?.remodexWrappedExecCallId;
|
|
1377
|
+
if (outerCallId && projectedPayloads.length > 1) {
|
|
1378
|
+
state.wrappedExecCallIdsByOuterId.set(
|
|
1379
|
+
outerCallId,
|
|
1380
|
+
projectedPayloads.map((projectedPayload) => (
|
|
1381
|
+
readString(projectedPayload.call_id) || readString(projectedPayload.callId)
|
|
1382
|
+
)).filter(Boolean)
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
return projectedPayloads.flatMap((projectedPayload) => (
|
|
1387
|
+
normalizeRolloutItemType(projectedPayload.type) === "customtoolcall"
|
|
1388
|
+
? customToolStartNotifications(state, projectedPayload)
|
|
1389
|
+
: toolStartNotifications(state, projectedPayload)
|
|
1390
|
+
));
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1329
1393
|
function toolStartNotifications(state, payload) {
|
|
1330
1394
|
if (!state.activeTurnId) {
|
|
1331
1395
|
return [];
|
|
@@ -1383,6 +1447,7 @@ function toolStartNotifications(state, payload) {
|
|
|
1383
1447
|
toolName,
|
|
1384
1448
|
command: resolveToolCommand(toolName, argumentsObject),
|
|
1385
1449
|
cwd: resolveToolWorkingDirectory(argumentsObject, state),
|
|
1450
|
+
wrappedExecCall: Boolean(payload.remodexWrappedExecCallId),
|
|
1386
1451
|
});
|
|
1387
1452
|
|
|
1388
1453
|
if (isCommandToolName(toolName)) {
|
|
@@ -1411,6 +1476,8 @@ function toolStartNotifications(state, payload) {
|
|
|
1411
1476
|
threadId: state.threadId,
|
|
1412
1477
|
turnId: state.activeTurnId,
|
|
1413
1478
|
call_id: callId,
|
|
1479
|
+
itemId: callId,
|
|
1480
|
+
status: "inProgress",
|
|
1414
1481
|
message: activityMessage,
|
|
1415
1482
|
}),
|
|
1416
1483
|
];
|
|
@@ -1461,6 +1528,7 @@ function customToolStartNotifications(state, payload) {
|
|
|
1461
1528
|
toolName,
|
|
1462
1529
|
command: toolName,
|
|
1463
1530
|
cwd: readString(state.sessionMeta?.cwd) || "",
|
|
1531
|
+
wrappedExecCall: Boolean(payload.remodexWrappedExecCallId),
|
|
1464
1532
|
});
|
|
1465
1533
|
}
|
|
1466
1534
|
|
|
@@ -1470,6 +1538,10 @@ function customToolStartNotifications(state, payload) {
|
|
|
1470
1538
|
threadId: state.threadId,
|
|
1471
1539
|
turnId: state.activeTurnId,
|
|
1472
1540
|
call_id: callId,
|
|
1541
|
+
...(!state.applyPatchCalls.has(callId) ? {
|
|
1542
|
+
itemId: callId,
|
|
1543
|
+
status: "inProgress",
|
|
1544
|
+
} : {}),
|
|
1473
1545
|
message: activityMessage,
|
|
1474
1546
|
}),
|
|
1475
1547
|
];
|
|
@@ -1544,8 +1616,28 @@ function toolOutputNotifications(state, payload) {
|
|
|
1544
1616
|
return [];
|
|
1545
1617
|
}
|
|
1546
1618
|
|
|
1619
|
+
const wrappedCallIds = state.wrappedExecCallIdsByOuterId.get(callId);
|
|
1620
|
+
if (Array.isArray(wrappedCallIds) && wrappedCallIds.length > 0) {
|
|
1621
|
+
state.wrappedExecCallIdsByOuterId.delete(callId);
|
|
1622
|
+
const outputRecipientId = wrappedCallIds.find((nestedCallId) => (
|
|
1623
|
+
isCommandToolName(state.commandCalls.get(nestedCallId)?.toolName)
|
|
1624
|
+
)) || wrappedCallIds[0];
|
|
1625
|
+
return wrappedCallIds.flatMap((nestedCallId) => toolOutputNotifications(state, {
|
|
1626
|
+
...payload,
|
|
1627
|
+
call_id: nestedCallId,
|
|
1628
|
+
callId: nestedCallId,
|
|
1629
|
+
output: nestedCallId === outputRecipientId ? payload.output : "",
|
|
1630
|
+
}));
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1547
1633
|
const toolCall = state.commandCalls.get(callId);
|
|
1548
1634
|
if (!toolCall) {
|
|
1635
|
+
if (state.applyPatchCalls.has(callId)) {
|
|
1636
|
+
return patchApplyEndNotifications(state, {
|
|
1637
|
+
...payload,
|
|
1638
|
+
status: readString(payload.status) || "completed",
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1549
1641
|
return [];
|
|
1550
1642
|
}
|
|
1551
1643
|
|
|
@@ -1555,13 +1647,16 @@ function toolOutputNotifications(state, payload) {
|
|
|
1555
1647
|
threadId: state.threadId,
|
|
1556
1648
|
turnId: state.activeTurnId,
|
|
1557
1649
|
call_id: callId,
|
|
1650
|
+
itemId: callId,
|
|
1651
|
+
status: "completed",
|
|
1558
1652
|
message: genericToolCompletionMessage(toolCall.toolName),
|
|
1559
1653
|
}));
|
|
1560
1654
|
state.commandCalls.delete(callId);
|
|
1561
1655
|
return notifications;
|
|
1562
1656
|
}
|
|
1563
1657
|
|
|
1564
|
-
const
|
|
1658
|
+
const rawOutput = extractToolOutputText(payload.output);
|
|
1659
|
+
const output = toolCall.wrappedExecCall ? stripExecOutputEnvelope(rawOutput) : rawOutput;
|
|
1565
1660
|
const notifications = [...ensureThinkingNotifications(state)];
|
|
1566
1661
|
if (output) {
|
|
1567
1662
|
notifications.push(createNotification("codex/event/exec_command_output_delta", {
|
|
@@ -1587,6 +1682,38 @@ function toolOutputNotifications(state, payload) {
|
|
|
1587
1682
|
return notifications;
|
|
1588
1683
|
}
|
|
1589
1684
|
|
|
1685
|
+
function extractToolOutputText(value) {
|
|
1686
|
+
if (typeof value === "string") {
|
|
1687
|
+
return value;
|
|
1688
|
+
}
|
|
1689
|
+
if (Array.isArray(value)) {
|
|
1690
|
+
return value.map(extractToolOutputText).join("");
|
|
1691
|
+
}
|
|
1692
|
+
if (!value || typeof value !== "object") {
|
|
1693
|
+
return "";
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
for (const key of ["text", "output_text", "outputText"]) {
|
|
1697
|
+
if (typeof value[key] === "string") {
|
|
1698
|
+
return value[key];
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
for (const key of ["content", "output", "result"]) {
|
|
1702
|
+
const text = extractToolOutputText(value[key]);
|
|
1703
|
+
if (text) {
|
|
1704
|
+
return text;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return "";
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
function stripExecOutputEnvelope(output) {
|
|
1711
|
+
return readString(output).replace(
|
|
1712
|
+
/^Script [^\n]*\nWall time [^\n]*\nOutput:\n?/,
|
|
1713
|
+
""
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1590
1717
|
function imageGenerationNotifications(state, payload, { preferCallId = false } = {}) {
|
|
1591
1718
|
if (!state.activeTurnId) {
|
|
1592
1719
|
return [];
|
|
@@ -1759,6 +1886,7 @@ function createMirrorState(threadId) {
|
|
|
1759
1886
|
commandCalls: new Map(),
|
|
1760
1887
|
applyPatchCalls: new Map(),
|
|
1761
1888
|
emittedPatchApplyEndCalls: new Set(),
|
|
1889
|
+
wrappedExecCallIdsByOuterId: new Map(),
|
|
1762
1890
|
emittedAgentMessageKeys: new Set(),
|
|
1763
1891
|
agentMessageOccurrencesByBaseKey: new Map(),
|
|
1764
1892
|
pendingEventAgentMessageOccurrencesByBaseKey: new Map(),
|
|
@@ -2091,6 +2219,7 @@ function resetRunState(state) {
|
|
|
2091
2219
|
state.commandCalls.clear();
|
|
2092
2220
|
state.applyPatchCalls.clear();
|
|
2093
2221
|
state.emittedPatchApplyEndCalls.clear();
|
|
2222
|
+
state.wrappedExecCallIdsByOuterId.clear();
|
|
2094
2223
|
state.emittedAgentMessageKeys.clear();
|
|
2095
2224
|
state.agentMessageOccurrencesByBaseKey.clear();
|
|
2096
2225
|
state.pendingEventAgentMessageOccurrencesByBaseKey.clear();
|