@makerbi/remodex 2.3.2 → 2.5.6
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 +80 -61
- package/src/codex-desktop-refresher.js +173 -8
- package/src/codex-tool-wrapper.js +523 -0
- package/src/desktop-ipc-action-follower.js +412 -129
- package/src/desktop-ipc-live-owner.js +148 -6
- package/src/desktop-ipc-owner-transport.js +143 -73
- package/src/desktop-ipc-shared.js +273 -46
- 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 +282 -22
- package/src/rollout-watch.js +176 -54
- package/src/session-jsonl-history.js +77 -33
- 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
|
@@ -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,
|
|
@@ -132,9 +140,17 @@ function createRolloutLiveMirrorController({
|
|
|
132
140
|
mirrorsByThreadId.clear();
|
|
133
141
|
}
|
|
134
142
|
|
|
143
|
+
// The real turn id this mirror is actively tailing, or null. Lets the bridge
|
|
144
|
+
// answer the phone's turn-state probe from mirror truth when the bounded
|
|
145
|
+
// canonical page reads a busy run as closed.
|
|
146
|
+
function getActiveTurnId(threadId) {
|
|
147
|
+
return mirrorsByThreadId.get(threadId)?.getActiveTurnId() || null;
|
|
148
|
+
}
|
|
149
|
+
|
|
135
150
|
return {
|
|
136
151
|
observeInbound,
|
|
137
152
|
stopAll,
|
|
153
|
+
getActiveTurnId,
|
|
138
154
|
};
|
|
139
155
|
}
|
|
140
156
|
|
|
@@ -149,6 +165,8 @@ function createThreadRolloutLiveMirror({
|
|
|
149
165
|
now,
|
|
150
166
|
setIntervalFn,
|
|
151
167
|
clearIntervalFn,
|
|
168
|
+
setImmediateFn,
|
|
169
|
+
clearImmediateFn,
|
|
152
170
|
pollIntervalMs,
|
|
153
171
|
lookupTimeoutMs,
|
|
154
172
|
idleTimeoutMs,
|
|
@@ -158,6 +176,7 @@ function createThreadRolloutLiveMirror({
|
|
|
158
176
|
onStop = () => {},
|
|
159
177
|
}) {
|
|
160
178
|
const startedAt = now();
|
|
179
|
+
let lookupStartedAt = startedAt;
|
|
161
180
|
const state = createMirrorState(threadId);
|
|
162
181
|
|
|
163
182
|
let isStopped = false;
|
|
@@ -174,7 +193,10 @@ function createThreadRolloutLiveMirror({
|
|
|
174
193
|
let wasSuppressed = false;
|
|
175
194
|
|
|
176
195
|
const intervalId = setIntervalFn(tick, pollIntervalMs);
|
|
177
|
-
|
|
196
|
+
let initialTickId = setImmediateFn(() => {
|
|
197
|
+
initialTickId = null;
|
|
198
|
+
tick();
|
|
199
|
+
});
|
|
178
200
|
|
|
179
201
|
function tick() {
|
|
180
202
|
if (isStopped) {
|
|
@@ -183,9 +205,30 @@ function createThreadRolloutLiveMirror({
|
|
|
183
205
|
|
|
184
206
|
try {
|
|
185
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
|
+
}
|
|
186
229
|
|
|
187
230
|
if (!rolloutPath) {
|
|
188
|
-
if (currentTime -
|
|
231
|
+
if (currentTime - lookupStartedAt >= lookupTimeoutMs) {
|
|
189
232
|
stop();
|
|
190
233
|
return;
|
|
191
234
|
}
|
|
@@ -201,20 +244,21 @@ function createThreadRolloutLiveMirror({
|
|
|
201
244
|
|
|
202
245
|
const rolloutStat = fsModule.statSync(rolloutPath);
|
|
203
246
|
const fileSize = rolloutStat.size;
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
// 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.
|
|
208
250
|
const suppressed = isSuppressed({
|
|
209
251
|
fallbackActivityAt: Number(rolloutStat.mtimeMs) || 0,
|
|
210
252
|
});
|
|
211
|
-
if (
|
|
253
|
+
if (suppressed) {
|
|
254
|
+
rolloutPath = null;
|
|
212
255
|
lastSize = 0;
|
|
213
256
|
partialLine = "";
|
|
214
257
|
didBootstrap = false;
|
|
215
258
|
resetRunState(state);
|
|
259
|
+
wasSuppressed = true;
|
|
260
|
+
return;
|
|
216
261
|
}
|
|
217
|
-
wasSuppressed = suppressed;
|
|
218
262
|
if (!didBootstrap) {
|
|
219
263
|
didBootstrap = true;
|
|
220
264
|
bootstrapFromExistingRollout({
|
|
@@ -346,6 +390,10 @@ function createThreadRolloutLiveMirror({
|
|
|
346
390
|
// final partial-line flush must never leak the poll interval.
|
|
347
391
|
isStopped = true;
|
|
348
392
|
clearIntervalFn(intervalId);
|
|
393
|
+
if (initialTickId != null) {
|
|
394
|
+
clearImmediateFn(initialTickId);
|
|
395
|
+
initialTickId = null;
|
|
396
|
+
}
|
|
349
397
|
if (partialLine) {
|
|
350
398
|
const flushLine = partialLine;
|
|
351
399
|
partialLine = "";
|
|
@@ -358,9 +406,30 @@ function createThreadRolloutLiveMirror({
|
|
|
358
406
|
onStop();
|
|
359
407
|
}
|
|
360
408
|
|
|
409
|
+
// Only a healthy, actively-tailed run with a real id counts: synthetic ids
|
|
410
|
+
// are not actionable app-server turn ids, and suppressed/awaiting states
|
|
411
|
+
// mean the mirror does not actually know what is running. While another live
|
|
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.
|
|
414
|
+
function getActiveTurnId() {
|
|
415
|
+
if (
|
|
416
|
+
isStopped
|
|
417
|
+
|| wasSuppressed
|
|
418
|
+
|| state.isDesktopOrigin === false
|
|
419
|
+
|| state.awaitingCoherentBoundary
|
|
420
|
+
|| state.suppressLiveActivityUntilGrowth
|
|
421
|
+
|| state.activeTurnIdIsSynthetic
|
|
422
|
+
|| state.pendingSyntheticTerminalTurnId
|
|
423
|
+
) {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
return state.activeTurnId || null;
|
|
427
|
+
}
|
|
428
|
+
|
|
361
429
|
return {
|
|
362
430
|
bump,
|
|
363
431
|
stop,
|
|
432
|
+
getActiveTurnId,
|
|
364
433
|
};
|
|
365
434
|
}
|
|
366
435
|
|
|
@@ -401,12 +470,30 @@ function bootstrapFromExistingRollout({
|
|
|
401
470
|
fsModule,
|
|
402
471
|
});
|
|
403
472
|
if (!bootstrapWindow) {
|
|
404
|
-
// The active run starts outside the bounded bootstrap window. Do not emit
|
|
405
|
-
// a plausible-looking tail: canonical history remains the baseline and
|
|
406
|
-
// this mirror will still consume future growth normally.
|
|
407
473
|
state.awaitingCoherentBoundary = true;
|
|
408
474
|
return;
|
|
409
475
|
}
|
|
476
|
+
if (!bootstrapWindow.coherent) {
|
|
477
|
+
// The active run starts outside the bounded bootstrap window. Do not emit
|
|
478
|
+
// a plausible-looking tail: canonical history remains the baseline. But do
|
|
479
|
+
// not go dark either — a long busy run would stop mirroring tool activity
|
|
480
|
+
// until its next turn boundary. Attach to the run in place instead, so
|
|
481
|
+
// growth from here on keeps streaming live.
|
|
482
|
+
const attached = attachToActiveRunFromTruncatedTail({
|
|
483
|
+
contents: bootstrapWindow.alignedContents,
|
|
484
|
+
boundary: bootstrapWindow.boundary,
|
|
485
|
+
state,
|
|
486
|
+
rolloutPath,
|
|
487
|
+
fsModule,
|
|
488
|
+
sendApplicationResponse,
|
|
489
|
+
nowMs,
|
|
490
|
+
staleActiveRunMaxAgeMs,
|
|
491
|
+
});
|
|
492
|
+
if (!attached) {
|
|
493
|
+
state.awaitingCoherentBoundary = true;
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
410
497
|
const { tailStart, contents: bootstrapContents } = bootstrapWindow;
|
|
411
498
|
let initialContents = bootstrapContents;
|
|
412
499
|
if (!initialContents) {
|
|
@@ -532,7 +619,8 @@ function bootstrapFromExistingRollout({
|
|
|
532
619
|
// Expands backwards only until the newest active task has its opening user
|
|
533
620
|
// message. Every expansion reads just the newly needed prefix, so a 30MB file
|
|
534
621
|
// is read at most once rather than once per retry. The hard cap keeps bootstrap
|
|
535
|
-
// work/memory bounded;
|
|
622
|
+
// work/memory bounded; a capped window without a coherent opener comes back
|
|
623
|
+
// with `coherent: false` and must never be replayed as history.
|
|
536
624
|
function readCoherentBootstrapWindow({ rolloutPath, fileSize, fsModule }) {
|
|
537
625
|
const maxBytes = Math.min(fileSize, DEFAULT_BOOTSTRAP_MAX_BYTES);
|
|
538
626
|
let windowBytes = Math.min(fileSize, DEFAULT_BOOTSTRAP_TAIL_BYTES);
|
|
@@ -549,10 +637,16 @@ function readCoherentBootstrapWindow({ rolloutPath, fileSize, fsModule }) {
|
|
|
549
637
|
// some legitimate system/continuation turns have no materialized user row.
|
|
550
638
|
// The opener requirement only protects a truncated tail.
|
|
551
639
|
if (!boundary.hasActiveRun || boundary.hasOpeningUser || tailStart === 0) {
|
|
552
|
-
return { tailStart, contents };
|
|
640
|
+
return { tailStart, contents, coherent: true };
|
|
553
641
|
}
|
|
554
642
|
if (windowBytes >= maxBytes || tailStart === 0) {
|
|
555
|
-
return
|
|
643
|
+
return {
|
|
644
|
+
tailStart,
|
|
645
|
+
contents,
|
|
646
|
+
coherent: false,
|
|
647
|
+
alignedContents,
|
|
648
|
+
boundary,
|
|
649
|
+
};
|
|
556
650
|
}
|
|
557
651
|
|
|
558
652
|
const nextWindowBytes = Math.min(maxBytes, windowBytes * 2);
|
|
@@ -584,12 +678,21 @@ function inspectBootstrapRunBoundary(contents) {
|
|
|
584
678
|
// closing terminal is evidence of an unknown active boundary, not permission
|
|
585
679
|
// to replay a partial conversation.
|
|
586
680
|
let unboundedActivitySinceTerminal = false;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
681
|
+
// Attach metadata for the incoherent-window case, so the caller never has to
|
|
682
|
+
// re-parse the (up to 64MB) window a second time.
|
|
683
|
+
let newestTaskStartedLineIndex = -1;
|
|
684
|
+
let lastEntryTimestamp = "";
|
|
685
|
+
|
|
686
|
+
const lines = contents.split("\n");
|
|
687
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
688
|
+
const parsed = safeParseJSON(lines[lineIndex].trim());
|
|
590
689
|
if (!parsed) {
|
|
591
690
|
continue;
|
|
592
691
|
}
|
|
692
|
+
const entryTimestamp = readString(parsed.timestamp);
|
|
693
|
+
if (entryTimestamp) {
|
|
694
|
+
lastEntryTimestamp = entryTimestamp;
|
|
695
|
+
}
|
|
593
696
|
const taskEventType = parsed?.type === "event_msg"
|
|
594
697
|
? readString(parsed?.payload?.type)
|
|
595
698
|
: "";
|
|
@@ -611,6 +714,7 @@ function inspectBootstrapRunBoundary(contents) {
|
|
|
611
714
|
hasOpeningUser = pendingUserBeforeStart;
|
|
612
715
|
hasTurnOutputSinceStart = false;
|
|
613
716
|
pendingUserBeforeStart = false;
|
|
717
|
+
newestTaskStartedLineIndex = lineIndex;
|
|
614
718
|
continue;
|
|
615
719
|
}
|
|
616
720
|
if (!activeTurnId) {
|
|
@@ -653,6 +757,8 @@ function inspectBootstrapRunBoundary(contents) {
|
|
|
653
757
|
return {
|
|
654
758
|
hasActiveRun: Boolean(activeTurnId) || unboundedActivitySinceTerminal,
|
|
655
759
|
hasOpeningUser,
|
|
760
|
+
newestTaskStartedLineIndex,
|
|
761
|
+
lastEntryTimestamp,
|
|
656
762
|
};
|
|
657
763
|
}
|
|
658
764
|
|
|
@@ -667,6 +773,58 @@ function isBootstrapNeutralRecord(entry, taskEventType = "") {
|
|
|
667
773
|
|| taskEventType === "context_updated";
|
|
668
774
|
}
|
|
669
775
|
|
|
776
|
+
// Attaches mid-run when the active run's opener is beyond the bounded window:
|
|
777
|
+
// nothing already in the tail is emitted (it stays canonical-history
|
|
778
|
+
// territory), but run state is hydrated so subsequent growth mirrors live.
|
|
779
|
+
// Returns false when the tail proves the visible runs all closed — trailing
|
|
780
|
+
// bytes then belong to an unknown older boundary and stay suppressed.
|
|
781
|
+
function attachToActiveRunFromTruncatedTail({
|
|
782
|
+
contents,
|
|
783
|
+
boundary,
|
|
784
|
+
state,
|
|
785
|
+
rolloutPath,
|
|
786
|
+
fsModule,
|
|
787
|
+
sendApplicationResponse,
|
|
788
|
+
nowMs,
|
|
789
|
+
staleActiveRunMaxAgeMs,
|
|
790
|
+
}) {
|
|
791
|
+
const newestTaskStartedIndex = boundary?.newestTaskStartedLineIndex ?? -1;
|
|
792
|
+
if (newestTaskStartedIndex >= 0) {
|
|
793
|
+
// Hydrate through the shared reducer so parallel-turn and terminal
|
|
794
|
+
// semantics stay authoritative for what is still open at EOF.
|
|
795
|
+
processRolloutLines(contents.split("\n").slice(newestTaskStartedIndex), state, () => {});
|
|
796
|
+
if (!state.activeTurnId) {
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
// Mid-turn tail without its task_started: adopt a synthetic turn. The
|
|
801
|
+
// first non-terminal event carrying the real id promotes it, and a
|
|
802
|
+
// mismatched terminal closes it via the synthetic-terminal path.
|
|
803
|
+
state.activeTurnId = buildSyntheticTurnId(state, { timestamp: boundary?.lastEntryTimestamp || "" });
|
|
804
|
+
state.activeTurnIdIsSynthetic = true;
|
|
805
|
+
state.reasoningItemId = buildSyntheticItemId("thinking", state.threadId, state.activeTurnId);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (isRolloutFileStale(rolloutPath, fsModule, nowMs, staleActiveRunMaxAgeMs)) {
|
|
809
|
+
// Same contract as the stale coherent bootstrap: stay silent until real
|
|
810
|
+
// growth proves the desktop process is alive again.
|
|
811
|
+
state.suppressLiveActivityUntilGrowth = true;
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// A hydrated run that already carries a pending synthetic terminal is
|
|
816
|
+
// closing, not running: announcing it as live would just be followed by the
|
|
817
|
+
// tick's synthetic turn/completed one grace period later.
|
|
818
|
+
if (!state.pendingSyntheticTerminalTurnId) {
|
|
819
|
+
sendApplicationResponse(JSON.stringify(createNotification("turn/activity", {
|
|
820
|
+
threadId: state.threadId,
|
|
821
|
+
turnId: state.activeTurnId,
|
|
822
|
+
id: state.activeTurnId,
|
|
823
|
+
})));
|
|
824
|
+
}
|
|
825
|
+
return true;
|
|
826
|
+
}
|
|
827
|
+
|
|
670
828
|
// After a bounded bootstrap cannot reach the old opener, consume only new
|
|
671
829
|
// bytes. A later real user+task_started boundary safely starts a new live run;
|
|
672
830
|
// everything before it remains canonical-history territory.
|
|
@@ -832,6 +990,7 @@ function synthesizeNotificationsFromRolloutEntry(entry, state, { nowMs = Date.no
|
|
|
832
990
|
state.commandCalls.clear();
|
|
833
991
|
state.applyPatchCalls.clear();
|
|
834
992
|
state.emittedPatchApplyEndCalls.clear();
|
|
993
|
+
state.wrappedExecCallIdsByOuterId.clear();
|
|
835
994
|
|
|
836
995
|
const startedParams = {
|
|
837
996
|
threadId: state.threadId,
|
|
@@ -963,16 +1122,16 @@ function synthesizeNotificationsFromRolloutEntry(entry, state, { nowMs = Date.no
|
|
|
963
1122
|
}
|
|
964
1123
|
|
|
965
1124
|
if (itemType === "functioncall") {
|
|
966
|
-
notifications.push(...
|
|
1125
|
+
notifications.push(...projectedToolStartNotifications(state, payload));
|
|
967
1126
|
return notifications;
|
|
968
1127
|
}
|
|
969
1128
|
|
|
970
1129
|
if (itemType === "customtoolcall") {
|
|
971
|
-
notifications.push(...
|
|
1130
|
+
notifications.push(...projectedToolStartNotifications(state, payload));
|
|
972
1131
|
return notifications;
|
|
973
1132
|
}
|
|
974
1133
|
|
|
975
|
-
if (itemType === "functioncalloutput") {
|
|
1134
|
+
if (itemType === "functioncalloutput" || itemType === "customtoolcalloutput") {
|
|
976
1135
|
notifications.push(...toolOutputNotifications(state, payload));
|
|
977
1136
|
return notifications;
|
|
978
1137
|
}
|
|
@@ -1208,6 +1367,29 @@ function extractResponseItemMessageText(payload) {
|
|
|
1208
1367
|
return responseItemMessageText(payload);
|
|
1209
1368
|
}
|
|
1210
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
|
+
|
|
1211
1393
|
function toolStartNotifications(state, payload) {
|
|
1212
1394
|
if (!state.activeTurnId) {
|
|
1213
1395
|
return [];
|
|
@@ -1265,6 +1447,7 @@ function toolStartNotifications(state, payload) {
|
|
|
1265
1447
|
toolName,
|
|
1266
1448
|
command: resolveToolCommand(toolName, argumentsObject),
|
|
1267
1449
|
cwd: resolveToolWorkingDirectory(argumentsObject, state),
|
|
1450
|
+
wrappedExecCall: Boolean(payload.remodexWrappedExecCallId),
|
|
1268
1451
|
});
|
|
1269
1452
|
|
|
1270
1453
|
if (isCommandToolName(toolName)) {
|
|
@@ -1336,6 +1519,17 @@ function customToolStartNotifications(state, payload) {
|
|
|
1336
1519
|
return notifications;
|
|
1337
1520
|
}
|
|
1338
1521
|
|
|
1522
|
+
// Custom tool calls settle through custom_tool_call_output. Without tracking
|
|
1523
|
+
// them the activity row never completes, so it lingers between command groups.
|
|
1524
|
+
if (!isCommandToolName(toolName) && !state.applyPatchCalls.has(callId)) {
|
|
1525
|
+
state.commandCalls.set(callId, {
|
|
1526
|
+
toolName,
|
|
1527
|
+
command: toolName,
|
|
1528
|
+
cwd: readString(state.sessionMeta?.cwd) || "",
|
|
1529
|
+
wrappedExecCall: Boolean(payload.remodexWrappedExecCallId),
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1339
1533
|
return [
|
|
1340
1534
|
...notifications,
|
|
1341
1535
|
createNotification("codex/event/background_event", {
|
|
@@ -1416,8 +1610,28 @@ function toolOutputNotifications(state, payload) {
|
|
|
1416
1610
|
return [];
|
|
1417
1611
|
}
|
|
1418
1612
|
|
|
1613
|
+
const wrappedCallIds = state.wrappedExecCallIdsByOuterId.get(callId);
|
|
1614
|
+
if (Array.isArray(wrappedCallIds) && wrappedCallIds.length > 0) {
|
|
1615
|
+
state.wrappedExecCallIdsByOuterId.delete(callId);
|
|
1616
|
+
const outputRecipientId = wrappedCallIds.find((nestedCallId) => (
|
|
1617
|
+
isCommandToolName(state.commandCalls.get(nestedCallId)?.toolName)
|
|
1618
|
+
)) || wrappedCallIds[0];
|
|
1619
|
+
return wrappedCallIds.flatMap((nestedCallId) => toolOutputNotifications(state, {
|
|
1620
|
+
...payload,
|
|
1621
|
+
call_id: nestedCallId,
|
|
1622
|
+
callId: nestedCallId,
|
|
1623
|
+
output: nestedCallId === outputRecipientId ? payload.output : "",
|
|
1624
|
+
}));
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1419
1627
|
const toolCall = state.commandCalls.get(callId);
|
|
1420
1628
|
if (!toolCall) {
|
|
1629
|
+
if (state.applyPatchCalls.has(callId)) {
|
|
1630
|
+
return patchApplyEndNotifications(state, {
|
|
1631
|
+
...payload,
|
|
1632
|
+
status: readString(payload.status) || "completed",
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1421
1635
|
return [];
|
|
1422
1636
|
}
|
|
1423
1637
|
|
|
@@ -1433,7 +1647,8 @@ function toolOutputNotifications(state, payload) {
|
|
|
1433
1647
|
return notifications;
|
|
1434
1648
|
}
|
|
1435
1649
|
|
|
1436
|
-
const
|
|
1650
|
+
const rawOutput = extractToolOutputText(payload.output);
|
|
1651
|
+
const output = toolCall.wrappedExecCall ? stripExecOutputEnvelope(rawOutput) : rawOutput;
|
|
1437
1652
|
const notifications = [...ensureThinkingNotifications(state)];
|
|
1438
1653
|
if (output) {
|
|
1439
1654
|
notifications.push(createNotification("codex/event/exec_command_output_delta", {
|
|
@@ -1459,6 +1674,38 @@ function toolOutputNotifications(state, payload) {
|
|
|
1459
1674
|
return notifications;
|
|
1460
1675
|
}
|
|
1461
1676
|
|
|
1677
|
+
function extractToolOutputText(value) {
|
|
1678
|
+
if (typeof value === "string") {
|
|
1679
|
+
return value;
|
|
1680
|
+
}
|
|
1681
|
+
if (Array.isArray(value)) {
|
|
1682
|
+
return value.map(extractToolOutputText).join("");
|
|
1683
|
+
}
|
|
1684
|
+
if (!value || typeof value !== "object") {
|
|
1685
|
+
return "";
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
for (const key of ["text", "output_text", "outputText"]) {
|
|
1689
|
+
if (typeof value[key] === "string") {
|
|
1690
|
+
return value[key];
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
for (const key of ["content", "output", "result"]) {
|
|
1694
|
+
const text = extractToolOutputText(value[key]);
|
|
1695
|
+
if (text) {
|
|
1696
|
+
return text;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
return "";
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function stripExecOutputEnvelope(output) {
|
|
1703
|
+
return readString(output).replace(
|
|
1704
|
+
/^Script [^\n]*\nWall time [^\n]*\nOutput:\n?/,
|
|
1705
|
+
""
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1462
1709
|
function imageGenerationNotifications(state, payload, { preferCallId = false } = {}) {
|
|
1463
1710
|
if (!state.activeTurnId) {
|
|
1464
1711
|
return [];
|
|
@@ -1631,6 +1878,7 @@ function createMirrorState(threadId) {
|
|
|
1631
1878
|
commandCalls: new Map(),
|
|
1632
1879
|
applyPatchCalls: new Map(),
|
|
1633
1880
|
emittedPatchApplyEndCalls: new Set(),
|
|
1881
|
+
wrappedExecCallIdsByOuterId: new Map(),
|
|
1634
1882
|
emittedAgentMessageKeys: new Set(),
|
|
1635
1883
|
agentMessageOccurrencesByBaseKey: new Map(),
|
|
1636
1884
|
pendingEventAgentMessageOccurrencesByBaseKey: new Map(),
|
|
@@ -1796,8 +2044,19 @@ function genericToolActivityMessage(toolName) {
|
|
|
1796
2044
|
}
|
|
1797
2045
|
}
|
|
1798
2046
|
|
|
2047
|
+
// Mirrors the wording of genericToolActivityMessage so the completion line
|
|
2048
|
+
// supersedes the start line instead of stacking a second row beside it.
|
|
1799
2049
|
function genericToolCompletionMessage(toolName) {
|
|
1800
|
-
|
|
2050
|
+
switch (readString(toolName).toLowerCase()) {
|
|
2051
|
+
case "apply_patch":
|
|
2052
|
+
return "Applied patch";
|
|
2053
|
+
case "write_stdin":
|
|
2054
|
+
return "Wrote to terminal";
|
|
2055
|
+
case "read_thread_terminal":
|
|
2056
|
+
return "Read terminal output";
|
|
2057
|
+
default:
|
|
2058
|
+
return `Completed ${readString(toolName)}`;
|
|
2059
|
+
}
|
|
1801
2060
|
}
|
|
1802
2061
|
|
|
1803
2062
|
function createNotification(method, params = {}) {
|
|
@@ -1952,6 +2211,7 @@ function resetRunState(state) {
|
|
|
1952
2211
|
state.commandCalls.clear();
|
|
1953
2212
|
state.applyPatchCalls.clear();
|
|
1954
2213
|
state.emittedPatchApplyEndCalls.clear();
|
|
2214
|
+
state.wrappedExecCallIdsByOuterId.clear();
|
|
1955
2215
|
state.emittedAgentMessageKeys.clear();
|
|
1956
2216
|
state.agentMessageOccurrencesByBaseKey.clear();
|
|
1957
2217
|
state.pendingEventAgentMessageOccurrencesByBaseKey.clear();
|