@makerbi/remodex 2.0.0 → 2.3.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 +1 -1
- package/package.json +2 -2
- package/src/account-status.js +5 -4
- package/src/bridge.js +1809 -689
- package/src/codex-desktop-refresher.js +35 -7
- package/src/cursor-acp-client.js +242 -0
- package/src/cursor-models.js +134 -0
- package/src/cursor-provider.js +1197 -0
- package/src/desktop-ipc-action-follower.js +2323 -123
- package/src/desktop-ipc-conversation-adapter.js +1132 -0
- package/src/desktop-ipc-conversation-projector.js +1169 -0
- package/src/desktop-ipc-live-owner.js +1790 -0
- package/src/desktop-ipc-owner-transport.js +750 -0
- package/src/desktop-ipc-shared.js +473 -0
- package/src/desktop-ipc-state-patches.js +218 -0
- package/src/opencode-models.js +108 -0
- package/src/opencode-provider.js +1151 -0
- package/src/project-handler.js +50 -7
- package/src/project-registry.js +466 -0
- package/src/push-notification-tracker.js +4 -4
- package/src/rollout-live-mirror.js +946 -78
- package/src/rollout-turn-semantics.js +20 -0
- package/src/runtime-provider-models.js +164 -0
- package/src/runtime-provider-router.js +365 -0
- package/src/scripts/codex-refresh.applescript +26 -15
- package/src/secure-transport.js +204 -9
- package/src/session-jsonl-history.js +429 -39
- package/src/thread-context-handler.js +8 -6
- package/src/thread-runtime-settings-store.js +247 -0
- package/src/voice-audio.js +344 -0
- package/src/voice-handler.js +363 -173
|
@@ -4,6 +4,19 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const { buildApplyPatchFileChangeItem } = require("./apply-patch-changes");
|
|
7
|
+
const { terminalEventClosesTrackedTurn } = require("./rollout-turn-semantics");
|
|
8
|
+
const {
|
|
9
|
+
buildRemodexSourceItemKey,
|
|
10
|
+
isContextualUserText,
|
|
11
|
+
isUserRoleItem,
|
|
12
|
+
responseItemMessageText: sharedResponseItemMessageText,
|
|
13
|
+
visibleUserPromptText,
|
|
14
|
+
} = require("./desktop-ipc-shared");
|
|
15
|
+
|
|
16
|
+
const JSONL_OLDER_HANDOFF_CURSOR = "remodex-jsonl-fallback-older-unavailable";
|
|
17
|
+
const DEFAULT_SESSION_JSONL_METADATA_HEAD_BYTES = 256 * 1024;
|
|
18
|
+
const DEFAULT_SESSION_JSONL_INITIAL_TAIL_BYTES = 4 * 1024 * 1024;
|
|
19
|
+
const DEFAULT_SESSION_JSONL_MAX_TAIL_BYTES = 64 * 1024 * 1024;
|
|
7
20
|
|
|
8
21
|
function readThreadTurnsListPageFromSessionJsonl(filePath, {
|
|
9
22
|
threadId = "",
|
|
@@ -11,13 +24,27 @@ function readThreadTurnsListPageFromSessionJsonl(filePath, {
|
|
|
11
24
|
maxLimit = 5,
|
|
12
25
|
cursor = null,
|
|
13
26
|
fsModule = fs,
|
|
27
|
+
metadataHeadBytes = DEFAULT_SESSION_JSONL_METADATA_HEAD_BYTES,
|
|
28
|
+
initialTailBytes = DEFAULT_SESSION_JSONL_INITIAL_TAIL_BYTES,
|
|
29
|
+
maxTailBytes = DEFAULT_SESSION_JSONL_MAX_TAIL_BYTES,
|
|
14
30
|
} = {}) {
|
|
15
31
|
if (!filePath || cursor != null) {
|
|
16
32
|
return null;
|
|
17
33
|
}
|
|
18
34
|
|
|
19
|
-
const
|
|
20
|
-
|
|
35
|
+
const recent = readRecentSessionJsonlTurns(filePath, {
|
|
36
|
+
threadId,
|
|
37
|
+
limit: Math.min(
|
|
38
|
+
Number.isInteger(limit) && limit > 0 ? limit : 5,
|
|
39
|
+
Number.isInteger(maxLimit) && maxLimit > 0 ? maxLimit : 5,
|
|
40
|
+
5
|
|
41
|
+
),
|
|
42
|
+
fsModule,
|
|
43
|
+
metadataHeadBytes,
|
|
44
|
+
initialTailBytes,
|
|
45
|
+
maxTailBytes,
|
|
46
|
+
});
|
|
47
|
+
const turns = recent?.turns || [];
|
|
21
48
|
if (turns.length === 0) {
|
|
22
49
|
return null;
|
|
23
50
|
}
|
|
@@ -28,11 +55,272 @@ function readThreadTurnsListPageFromSessionJsonl(filePath, {
|
|
|
28
55
|
const pageTurns = turns.slice(-safeLimit).reverse();
|
|
29
56
|
return {
|
|
30
57
|
data: pageTurns,
|
|
31
|
-
nextCursor: turns.length > pageTurns.length
|
|
58
|
+
nextCursor: recent.hasOlderTurns || turns.length > pageTurns.length
|
|
59
|
+
? JSONL_OLDER_HANDOFF_CURSOR
|
|
60
|
+
: null,
|
|
32
61
|
remodexJsonlFallback: true,
|
|
33
62
|
};
|
|
34
63
|
}
|
|
35
64
|
|
|
65
|
+
// Reads only a bounded snapshot of an append-only rollout. Truncated tails are
|
|
66
|
+
// accepted only when the latest turn's own start and visible user prompt are in
|
|
67
|
+
// the window, so a fast page can never invent ownership for cut-off items.
|
|
68
|
+
function readRecentSessionJsonlTurns(filePath, {
|
|
69
|
+
threadId = "",
|
|
70
|
+
limit = 5,
|
|
71
|
+
fsModule = fs,
|
|
72
|
+
metadataHeadBytes = DEFAULT_SESSION_JSONL_METADATA_HEAD_BYTES,
|
|
73
|
+
initialTailBytes = DEFAULT_SESSION_JSONL_INITIAL_TAIL_BYTES,
|
|
74
|
+
maxTailBytes = DEFAULT_SESSION_JSONL_MAX_TAIL_BYTES,
|
|
75
|
+
} = {}) {
|
|
76
|
+
if (!filePath) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Keep simple in-memory test doubles compatible without weakening the real
|
|
81
|
+
// filesystem path, which must never stringify a multi-gigabyte rollout.
|
|
82
|
+
if (!supportsBoundedSessionJsonlReads(fsModule)) {
|
|
83
|
+
const content = fsModule.readFileSync(filePath, "utf8");
|
|
84
|
+
const turns = parseSessionJsonlTurns(content, { threadId });
|
|
85
|
+
return turns.length > 0 ? { turns, hasOlderTurns: false, bytesRead: Buffer.byteLength(content, "utf8") } : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const stat = fsModule.statSync(filePath);
|
|
89
|
+
const snapshotSize = Math.max(0, Number(stat?.size) || 0);
|
|
90
|
+
if (snapshotSize === 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const safeLimit = Math.max(1, Math.min(Number.isInteger(limit) ? limit : 5, 5));
|
|
95
|
+
const safeMetadataHeadBytes = Math.max(1, Math.min(metadataHeadBytes, snapshotSize));
|
|
96
|
+
const safeMaximumTailBytes = Math.max(1, Math.min(maxTailBytes, snapshotSize));
|
|
97
|
+
let tailBytes = Math.max(1, Math.min(initialTailBytes, safeMaximumTailBytes));
|
|
98
|
+
const fileHandle = fsModule.openSync(filePath, "r");
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const metadataBuffer = readSessionJsonlRange(
|
|
102
|
+
fileHandle,
|
|
103
|
+
0,
|
|
104
|
+
safeMetadataHeadBytes,
|
|
105
|
+
fsModule
|
|
106
|
+
);
|
|
107
|
+
const initialMetadata = parseSessionJsonlInitialMetadata(metadataBuffer.toString("utf8"));
|
|
108
|
+
|
|
109
|
+
while (true) {
|
|
110
|
+
const rawStart = Math.max(0, snapshotSize - tailBytes);
|
|
111
|
+
const tailBuffer = readSessionJsonlRange(
|
|
112
|
+
fileHandle,
|
|
113
|
+
rawStart,
|
|
114
|
+
snapshotSize - rawStart,
|
|
115
|
+
fsModule
|
|
116
|
+
);
|
|
117
|
+
const aligned = alignSessionJsonlTailBuffer(tailBuffer, rawStart);
|
|
118
|
+
if (aligned) {
|
|
119
|
+
const content = aligned.buffer.toString("utf8");
|
|
120
|
+
const sourceLineByteOffsets = sessionJsonlLineByteOffsets(
|
|
121
|
+
aligned.buffer,
|
|
122
|
+
aligned.sourceByteOffset
|
|
123
|
+
);
|
|
124
|
+
const turns = parseSessionJsonlTurns(content, {
|
|
125
|
+
threadId,
|
|
126
|
+
initialMetadata,
|
|
127
|
+
sourceByteOffset: aligned.sourceByteOffset,
|
|
128
|
+
sourceLineByteOffsets,
|
|
129
|
+
});
|
|
130
|
+
const observedStartedTurnIDs = observedTaskStartedTurnIDs(content, {
|
|
131
|
+
sourceByteOffset: aligned.sourceByteOffset,
|
|
132
|
+
sourceLineByteOffsets,
|
|
133
|
+
});
|
|
134
|
+
const safeTurns = rawStart === 0
|
|
135
|
+
? turns
|
|
136
|
+
: turns.filter((turn) => (
|
|
137
|
+
observedStartedTurnIDs.has(normalizeString(turn?.id))
|
|
138
|
+
&& turnHasVisibleUserItem(turn)
|
|
139
|
+
));
|
|
140
|
+
|
|
141
|
+
if (safeTurns.length >= safeLimit || tailBytes >= safeMaximumTailBytes || rawStart === 0) {
|
|
142
|
+
if (safeTurns.length === 0) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
turns: safeTurns,
|
|
147
|
+
hasOlderTurns: rawStart > 0 || turns.length > safeTurns.length,
|
|
148
|
+
bytesRead: metadataBuffer.length + tailBuffer.length,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (tailBytes >= safeMaximumTailBytes || rawStart === 0) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
tailBytes = Math.min(safeMaximumTailBytes, tailBytes * 2);
|
|
157
|
+
}
|
|
158
|
+
} finally {
|
|
159
|
+
fsModule.closeSync(fileHandle);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function readSessionJsonlMetadataFromFile(filePath, {
|
|
164
|
+
fsModule = fs,
|
|
165
|
+
metadataHeadBytes = DEFAULT_SESSION_JSONL_METADATA_HEAD_BYTES,
|
|
166
|
+
} = {}) {
|
|
167
|
+
if (!filePath) {
|
|
168
|
+
return { threadId: "", cwd: "" };
|
|
169
|
+
}
|
|
170
|
+
if (!supportsBoundedSessionJsonlReads(fsModule)) {
|
|
171
|
+
return parseSessionJsonlMetadata(fsModule.readFileSync(filePath, "utf8"));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const stat = fsModule.statSync(filePath);
|
|
175
|
+
const snapshotSize = Math.max(0, Number(stat?.size) || 0);
|
|
176
|
+
if (snapshotSize === 0) {
|
|
177
|
+
return { threadId: "", cwd: "" };
|
|
178
|
+
}
|
|
179
|
+
const fileHandle = fsModule.openSync(filePath, "r");
|
|
180
|
+
try {
|
|
181
|
+
const head = readSessionJsonlRange(
|
|
182
|
+
fileHandle,
|
|
183
|
+
0,
|
|
184
|
+
Math.min(snapshotSize, Math.max(1, metadataHeadBytes)),
|
|
185
|
+
fsModule
|
|
186
|
+
);
|
|
187
|
+
const metadata = parseSessionJsonlInitialMetadata(head.toString("utf8"));
|
|
188
|
+
return { threadId: metadata.threadId, cwd: metadata.cwd };
|
|
189
|
+
} finally {
|
|
190
|
+
fsModule.closeSync(fileHandle);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function supportsBoundedSessionJsonlReads(fsModule) {
|
|
195
|
+
return typeof fsModule?.statSync === "function"
|
|
196
|
+
&& typeof fsModule?.openSync === "function"
|
|
197
|
+
&& typeof fsModule?.readSync === "function"
|
|
198
|
+
&& typeof fsModule?.closeSync === "function";
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function readSessionJsonlRange(fileHandle, start, length, fsModule) {
|
|
202
|
+
const safeLength = Math.max(0, length);
|
|
203
|
+
const buffer = Buffer.allocUnsafe(safeLength);
|
|
204
|
+
const bytesRead = safeLength > 0
|
|
205
|
+
? fsModule.readSync(fileHandle, buffer, 0, safeLength, start)
|
|
206
|
+
: 0;
|
|
207
|
+
return buffer.subarray(0, bytesRead);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function alignSessionJsonlTailBuffer(buffer, rawStart) {
|
|
211
|
+
if (rawStart === 0) {
|
|
212
|
+
return { buffer, sourceByteOffset: 0 };
|
|
213
|
+
}
|
|
214
|
+
const firstLineFeed = buffer.indexOf(0x0a);
|
|
215
|
+
if (firstLineFeed === -1 || firstLineFeed + 1 >= buffer.length) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
buffer: buffer.subarray(firstLineFeed + 1),
|
|
220
|
+
sourceByteOffset: rawStart + firstLineFeed + 1,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function sessionJsonlLineByteOffsets(buffer, sourceByteOffset) {
|
|
225
|
+
const offsets = [sourceByteOffset];
|
|
226
|
+
for (let index = 0; index < buffer.length; index += 1) {
|
|
227
|
+
if (buffer[index] === 0x0a && index + 1 < buffer.length) {
|
|
228
|
+
offsets.push(sourceByteOffset + index + 1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return offsets;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function observedTaskStartedTurnIDs(content, {
|
|
235
|
+
sourceByteOffset = 0,
|
|
236
|
+
sourceLineByteOffsets = null,
|
|
237
|
+
} = {}) {
|
|
238
|
+
const turnIDs = new Set();
|
|
239
|
+
const raw = String(content || "");
|
|
240
|
+
let lineIndex = -1;
|
|
241
|
+
let lineStart = 0;
|
|
242
|
+
let fallbackSourceByteOffset = sourceByteOffset;
|
|
243
|
+
while (lineStart < raw.length) {
|
|
244
|
+
lineIndex += 1;
|
|
245
|
+
let lineEnd = raw.indexOf("\n", lineStart);
|
|
246
|
+
if (lineEnd === -1) {
|
|
247
|
+
lineEnd = raw.length;
|
|
248
|
+
}
|
|
249
|
+
const currentLineStart = lineStart;
|
|
250
|
+
const lineEndWithSeparator = lineEnd < raw.length ? lineEnd + 1 : lineEnd;
|
|
251
|
+
const sourceLineNumber = sourceLineByteOffsets?.[lineIndex]
|
|
252
|
+
?? fallbackSourceByteOffset;
|
|
253
|
+
fallbackSourceByteOffset += Buffer.byteLength(
|
|
254
|
+
raw.substring(currentLineStart, lineEndWithSeparator),
|
|
255
|
+
"utf8"
|
|
256
|
+
);
|
|
257
|
+
const line = raw.substring(lineStart, lineEnd).trim();
|
|
258
|
+
lineStart = lineEnd + 1;
|
|
259
|
+
if (!line) {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const entry = JSON.parse(line);
|
|
264
|
+
const payload = objectValue(entry?.payload);
|
|
265
|
+
if (entry?.type !== "event_msg" || normalizeString(payload?.type) !== "task_started") {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const turnID = normalizeString(payload?.turn_id)
|
|
269
|
+
|| normalizeString(payload?.turnId)
|
|
270
|
+
|| `turn-line-${sourceLineNumber}`;
|
|
271
|
+
if (turnID) {
|
|
272
|
+
turnIDs.add(turnID);
|
|
273
|
+
}
|
|
274
|
+
} catch {
|
|
275
|
+
// A live rollout may end with a partial line; ignore it until the next read.
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return turnIDs;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function turnHasVisibleUserItem(turn) {
|
|
282
|
+
return Array.isArray(turn?.items) && turn.items.some((item) => isUserRoleItem(item));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function parseSessionJsonlInitialMetadata(content) {
|
|
286
|
+
let threadId = "";
|
|
287
|
+
let cwd = "";
|
|
288
|
+
let timeZone = "";
|
|
289
|
+
const raw = String(content || "");
|
|
290
|
+
let lineStart = 0;
|
|
291
|
+
while (lineStart < raw.length) {
|
|
292
|
+
let lineEnd = raw.indexOf("\n", lineStart);
|
|
293
|
+
if (lineEnd === -1) {
|
|
294
|
+
lineEnd = raw.length;
|
|
295
|
+
}
|
|
296
|
+
const line = raw.substring(lineStart, lineEnd).trim();
|
|
297
|
+
lineStart = lineEnd + 1;
|
|
298
|
+
if (!line) {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const entry = JSON.parse(line);
|
|
303
|
+
if (entry?.type !== "session_meta") {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const payload = objectValue(entry.payload);
|
|
307
|
+
threadId = normalizeString(payload?.id)
|
|
308
|
+
|| normalizeString(payload?.thread_id)
|
|
309
|
+
|| normalizeString(payload?.threadId);
|
|
310
|
+
cwd = normalizeString(payload?.cwd)
|
|
311
|
+
|| normalizeString(payload?.current_working_directory)
|
|
312
|
+
|| normalizeString(payload?.working_directory);
|
|
313
|
+
timeZone = normalizeString(payload?.timezone)
|
|
314
|
+
|| normalizeString(payload?.timeZone)
|
|
315
|
+
|| normalizeString(payload?.time_zone);
|
|
316
|
+
break;
|
|
317
|
+
} catch {
|
|
318
|
+
// Metadata is expected at the head; an incomplete oversized line is not trusted.
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { threadId, cwd, timeZone };
|
|
322
|
+
}
|
|
323
|
+
|
|
36
324
|
// Extracts thread-level context that app-server history can omit for desktop-origin runs.
|
|
37
325
|
function parseSessionJsonlMetadata(content) {
|
|
38
326
|
let threadId = "";
|
|
@@ -78,26 +366,42 @@ function parseSessionJsonlMetadata(content) {
|
|
|
78
366
|
return { threadId, cwd };
|
|
79
367
|
}
|
|
80
368
|
|
|
81
|
-
function parseSessionJsonlTurns(content, {
|
|
369
|
+
function parseSessionJsonlTurns(content, {
|
|
370
|
+
threadId = "",
|
|
371
|
+
initialMetadata = null,
|
|
372
|
+
sourceByteOffset = 0,
|
|
373
|
+
sourceLineByteOffsets = null,
|
|
374
|
+
} = {}) {
|
|
82
375
|
const turns = [];
|
|
83
376
|
const turnsById = new Map();
|
|
84
377
|
let activeTurnId = "";
|
|
85
|
-
let
|
|
86
|
-
let
|
|
87
|
-
let
|
|
378
|
+
let pendingSyntheticTerminal = null;
|
|
379
|
+
let sessionThreadId = normalizeString(threadId) || normalizeString(initialMetadata?.threadId);
|
|
380
|
+
let sessionCwd = normalizeString(initialMetadata?.cwd);
|
|
381
|
+
let sessionTimeZone = normalizeString(initialMetadata?.timeZone);
|
|
88
382
|
const skippedCallIds = new Set();
|
|
89
383
|
const toolCallsByCallId = new Map();
|
|
90
384
|
const pendingUserMessages = [];
|
|
385
|
+
const assistantAliasOccurrencesByBaseKey = new Map();
|
|
91
386
|
|
|
92
387
|
const raw = String(content || "");
|
|
93
388
|
let index = -1;
|
|
94
389
|
let lineStart = 0;
|
|
390
|
+
let fallbackSourceByteOffset = sourceByteOffset;
|
|
95
391
|
while (lineStart < raw.length) {
|
|
96
392
|
index += 1;
|
|
97
393
|
let lineEnd = raw.indexOf("\n", lineStart);
|
|
98
394
|
if (lineEnd === -1) {
|
|
99
395
|
lineEnd = raw.length;
|
|
100
396
|
}
|
|
397
|
+
const currentLineStart = lineStart;
|
|
398
|
+
const lineEndWithSeparator = lineEnd < raw.length ? lineEnd + 1 : lineEnd;
|
|
399
|
+
const sourceLineNumber = sourceLineByteOffsets?.[index]
|
|
400
|
+
?? fallbackSourceByteOffset;
|
|
401
|
+
fallbackSourceByteOffset += Buffer.byteLength(
|
|
402
|
+
raw.substring(currentLineStart, lineEndWithSeparator),
|
|
403
|
+
"utf8"
|
|
404
|
+
);
|
|
101
405
|
const line = raw.substring(lineStart, lineEnd).trim();
|
|
102
406
|
lineStart = lineEnd + 1;
|
|
103
407
|
if (!line) {
|
|
@@ -144,30 +448,53 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
144
448
|
const payload = objectValue(entry.payload);
|
|
145
449
|
const eventType = normalizeString(payload?.type);
|
|
146
450
|
if (eventType === "task_started") {
|
|
451
|
+
if (pendingSyntheticTerminal) {
|
|
452
|
+
closeSyntheticHistoryTurn(turnsById, pendingSyntheticTerminal);
|
|
453
|
+
pendingSyntheticTerminal = null;
|
|
454
|
+
activeTurnId = "";
|
|
455
|
+
}
|
|
147
456
|
activeTurnId = normalizeString(payload?.turn_id)
|
|
148
457
|
|| normalizeString(payload?.turnId)
|
|
149
458
|
|| activeTurnId
|
|
150
|
-
|| `turn-line-${
|
|
459
|
+
|| `turn-line-${sourceLineNumber}`;
|
|
151
460
|
const turn = ensureTurn(turns, turnsById, activeTurnId, sessionThreadId, entry.timestamp);
|
|
152
461
|
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
153
462
|
flushPendingUserMessagesToTurn(turn, pendingUserMessages);
|
|
154
463
|
continue;
|
|
155
464
|
}
|
|
156
465
|
|
|
157
|
-
if (eventType === "task_complete") {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
466
|
+
if (eventType === "task_complete" || eventType === "turn_aborted" || eventType === "error") {
|
|
467
|
+
const explicitTurnId = normalizeString(payload?.turn_id) || normalizeString(payload?.turnId);
|
|
468
|
+
const terminalTurnId = explicitTurnId || activeTurnId;
|
|
469
|
+
if (terminalTurnId) {
|
|
470
|
+
const turn = ensureTurn(turns, turnsById, terminalTurnId, sessionThreadId, entry.timestamp);
|
|
471
|
+
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
472
|
+
// Aborted/failed runs never write task_complete; without a terminal
|
|
473
|
+
// status here the history page would report them as still running.
|
|
474
|
+
turn.status = eventType === "task_complete"
|
|
475
|
+
? "completed"
|
|
476
|
+
: (eventType === "error" ? "failed" : "aborted");
|
|
477
|
+
}
|
|
478
|
+
// Desktop interleaves parallel turns in one rollout. A sibling turn's
|
|
479
|
+
// terminal event must not orphan the still-running turn's context:
|
|
480
|
+
// keeping activeTurnId prevents later turn-less items from spawning
|
|
481
|
+
// synthetic "turn-line-N" running turns that pin the thread as active.
|
|
482
|
+
if (terminalEventClosesTrackedTurn(explicitTurnId, activeTurnId)) {
|
|
483
|
+
activeTurnId = "";
|
|
484
|
+
pendingSyntheticTerminal = null;
|
|
485
|
+
} else if (isSyntheticHistoryTurnId(activeTurnId) && explicitTurnId) {
|
|
486
|
+
pendingSyntheticTerminal = {
|
|
487
|
+
turnId: activeTurnId,
|
|
488
|
+
status: terminalStatusForEventType(eventType),
|
|
489
|
+
};
|
|
490
|
+
}
|
|
168
491
|
continue;
|
|
169
492
|
}
|
|
170
493
|
|
|
494
|
+
if (eventType) {
|
|
495
|
+
pendingSyntheticTerminal = null;
|
|
496
|
+
}
|
|
497
|
+
|
|
171
498
|
if (eventType === "item_completed") {
|
|
172
499
|
const completedItem = objectValue(payload?.item);
|
|
173
500
|
if (!completedItem) {
|
|
@@ -177,11 +504,15 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
177
504
|
const turn = ensureTurn(
|
|
178
505
|
turns,
|
|
179
506
|
turnsById,
|
|
180
|
-
normalizeString(payload?.turn_id)
|
|
507
|
+
normalizeString(payload?.turn_id)
|
|
508
|
+
|| normalizeString(payload?.turnId)
|
|
509
|
+
|| responseItemTurnId(completedItem)
|
|
510
|
+
|| activeTurnId
|
|
511
|
+
|| `turn-line-${sourceLineNumber}`,
|
|
181
512
|
sessionThreadId,
|
|
182
513
|
entry.timestamp
|
|
183
514
|
);
|
|
184
|
-
const item = normalizeResponseItemForHistory(completedItem,
|
|
515
|
+
const item = normalizeResponseItemForHistory(completedItem, sourceLineNumber, {
|
|
185
516
|
cwd: sessionCwd,
|
|
186
517
|
toolCallsByCallId,
|
|
187
518
|
});
|
|
@@ -194,7 +525,10 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
194
525
|
|
|
195
526
|
if (eventType === "user_message") {
|
|
196
527
|
const explicitTurnId = normalizeString(payload?.turn_id) || normalizeString(payload?.turnId);
|
|
197
|
-
const item = createUserMessageHistoryItem(payload,
|
|
528
|
+
const item = createUserMessageHistoryItem(payload, sourceLineNumber, entry.timestamp);
|
|
529
|
+
if (!item.text) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
198
532
|
applyHistoryTimeZone(item, sessionTimeZone);
|
|
199
533
|
if (!explicitTurnId && !activeTurnId) {
|
|
200
534
|
pushPendingUserMessage(pendingUserMessages, item);
|
|
@@ -204,7 +538,7 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
204
538
|
const turn = ensureTurn(
|
|
205
539
|
turns,
|
|
206
540
|
turnsById,
|
|
207
|
-
explicitTurnId || activeTurnId || `turn-line-${
|
|
541
|
+
explicitTurnId || activeTurnId || `turn-line-${sourceLineNumber}`,
|
|
208
542
|
sessionThreadId,
|
|
209
543
|
entry.timestamp
|
|
210
544
|
);
|
|
@@ -219,6 +553,7 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
219
553
|
}
|
|
220
554
|
|
|
221
555
|
if (entry?.type === "response_item") {
|
|
556
|
+
pendingSyntheticTerminal = null;
|
|
222
557
|
const payload = objectValue(entry.payload);
|
|
223
558
|
if (!payload) {
|
|
224
559
|
continue;
|
|
@@ -230,12 +565,12 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
230
565
|
const turn = ensureTurn(
|
|
231
566
|
turns,
|
|
232
567
|
turnsById,
|
|
233
|
-
|
|
568
|
+
responseItemTurnId(payload) || activeTurnId || `turn-line-${sourceLineNumber}`,
|
|
234
569
|
sessionThreadId,
|
|
235
570
|
entry.timestamp
|
|
236
571
|
);
|
|
237
572
|
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
238
|
-
const item = normalizeResponseItemForHistory(payload,
|
|
573
|
+
const item = normalizeResponseItemForHistory(payload, sourceLineNumber, {
|
|
239
574
|
cwd: sessionCwd,
|
|
240
575
|
toolCallsByCallId,
|
|
241
576
|
});
|
|
@@ -251,21 +586,51 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
251
586
|
item.timestamp = itemTimestamp;
|
|
252
587
|
}
|
|
253
588
|
applyHistoryTimeZone(item, sessionTimeZone);
|
|
589
|
+
applyHistoryAssistantSourceAlias(item, turn.id, assistantAliasOccurrencesByBaseKey);
|
|
254
590
|
addHistoryItemToTurn(turn, item);
|
|
255
591
|
}
|
|
256
592
|
}
|
|
257
593
|
}
|
|
258
594
|
|
|
595
|
+
if (pendingSyntheticTerminal) {
|
|
596
|
+
closeSyntheticHistoryTurn(turnsById, pendingSyntheticTerminal);
|
|
597
|
+
}
|
|
598
|
+
|
|
259
599
|
return turns.filter((turn) => turn.items.length > 0);
|
|
260
600
|
}
|
|
261
601
|
|
|
602
|
+
function closeSyntheticHistoryTurn(turnsById, terminal) {
|
|
603
|
+
const turn = turnsById.get(terminal.turnId);
|
|
604
|
+
if (turn) {
|
|
605
|
+
turn.status = terminal.status || "completed";
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function terminalStatusForEventType(eventType) {
|
|
610
|
+
if (eventType === "turn_aborted") {
|
|
611
|
+
return "aborted";
|
|
612
|
+
}
|
|
613
|
+
if (eventType === "error") {
|
|
614
|
+
return "failed";
|
|
615
|
+
}
|
|
616
|
+
return "completed";
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function isSyntheticHistoryTurnId(turnId) {
|
|
620
|
+
return /^turn-line-\d+$/.test(normalizeString(turnId));
|
|
621
|
+
}
|
|
622
|
+
|
|
262
623
|
function createUserMessageHistoryItem(payload, lineNumber, timestamp) {
|
|
263
624
|
const createdAt = historyItemTimestamp(payload, timestamp);
|
|
264
625
|
return {
|
|
265
626
|
id: normalizeString(payload?.id) || `user-message-line-${lineNumber}`,
|
|
266
627
|
type: "user_message",
|
|
267
628
|
role: "user",
|
|
268
|
-
|
|
629
|
+
// Event-shaped user messages can carry injected context or IDE prompt
|
|
630
|
+
// wrappers too; keep only the visible request like every other path.
|
|
631
|
+
text: visibleUserPromptText(
|
|
632
|
+
normalizeString(payload?.message) || normalizeString(payload?.text)
|
|
633
|
+
),
|
|
269
634
|
createdAt: createdAt || undefined,
|
|
270
635
|
timestamp: createdAt || undefined,
|
|
271
636
|
};
|
|
@@ -378,7 +743,7 @@ function historyItemTimestamp(item, fallbackTimestamp = "") {
|
|
|
378
743
|
}
|
|
379
744
|
|
|
380
745
|
function isUserHistoryItem(item) {
|
|
381
|
-
return
|
|
746
|
+
return isUserRoleItem(item)
|
|
382
747
|
|| normalizeString(item?.role).toLowerCase() === "user";
|
|
383
748
|
}
|
|
384
749
|
|
|
@@ -500,6 +865,20 @@ function normalizeResponseItemForHistory(payload, lineNumber, { cwd = "", toolCa
|
|
|
500
865
|
return item;
|
|
501
866
|
}
|
|
502
867
|
|
|
868
|
+
function applyHistoryAssistantSourceAlias(item, turnId, occurrencesByBaseKey = new Map()) {
|
|
869
|
+
if (normalizeHistoryToken(item?.type) !== "message"
|
|
870
|
+
|| normalizeString(item?.role).toLowerCase() !== "assistant") {
|
|
871
|
+
return item;
|
|
872
|
+
}
|
|
873
|
+
const sourceKey = buildRemodexSourceItemKey(turnId, responseItemMessageText(item));
|
|
874
|
+
const occurrence = (occurrencesByBaseKey.get(sourceKey) || 0) + 1;
|
|
875
|
+
occurrencesByBaseKey.set(sourceKey, occurrence);
|
|
876
|
+
if (sourceKey && occurrence === 1) {
|
|
877
|
+
item.remodexSourceItemKey = sourceKey;
|
|
878
|
+
}
|
|
879
|
+
return item;
|
|
880
|
+
}
|
|
881
|
+
|
|
503
882
|
// Converts `view_image` tool output blobs into a lightweight local image reference.
|
|
504
883
|
function normalizeToolOutputImageViewItemForHistory(payload, lineNumber, { toolCallsByCallId = new Map() } = {}) {
|
|
505
884
|
const type = normalizeHistoryItemType(payload.type);
|
|
@@ -693,6 +1072,7 @@ function normalizeProgressPlanItemForHistory(payload) {
|
|
|
693
1072
|
text: explanation || "Planning...",
|
|
694
1073
|
explanation: explanation || undefined,
|
|
695
1074
|
plan,
|
|
1075
|
+
remodexProgressPlan: true,
|
|
696
1076
|
remodexJsonlProgressPlan: true,
|
|
697
1077
|
};
|
|
698
1078
|
}
|
|
@@ -953,6 +1333,13 @@ function shouldSkipResponseItemForHistory(payload, skippedCallIds) {
|
|
|
953
1333
|
return true;
|
|
954
1334
|
}
|
|
955
1335
|
|
|
1336
|
+
// Injected context (AGENTS.md instructions, environment_context wrappers) is
|
|
1337
|
+
// persisted as user-role response items; Codex UIs hide it at render time and
|
|
1338
|
+
// mobile history must do the same.
|
|
1339
|
+
if (role === "user" && isContextualUserText(responseItemMessageText(payload))) {
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
956
1343
|
return false;
|
|
957
1344
|
}
|
|
958
1345
|
|
|
@@ -974,19 +1361,19 @@ function isSubagentNotificationMessage(payload) {
|
|
|
974
1361
|
return text.startsWith("<subagent_notification>");
|
|
975
1362
|
}
|
|
976
1363
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1364
|
+
// Modern Codex rollouts keep response-item ownership in metadata passthrough.
|
|
1365
|
+
// A rollout can interleave parallel turns, so the process-wide active turn is
|
|
1366
|
+
// only a last resort; using it first moves tools, plans, and prose across turns.
|
|
1367
|
+
function responseItemTurnId(payload) {
|
|
1368
|
+
const metadata = objectValue(payload?.internal_chat_message_metadata_passthrough);
|
|
1369
|
+
return normalizeString(payload?.turn_id)
|
|
1370
|
+
|| normalizeString(payload?.turnId)
|
|
1371
|
+
|| normalizeString(metadata?.turn_id)
|
|
1372
|
+
|| normalizeString(metadata?.turnId);
|
|
1373
|
+
}
|
|
982
1374
|
|
|
983
|
-
|
|
984
|
-
return
|
|
985
|
-
.map((item) => objectValue(item))
|
|
986
|
-
.filter(Boolean)
|
|
987
|
-
.map((item) => responseItemContentText(item))
|
|
988
|
-
.filter(Boolean)
|
|
989
|
-
.join("\n");
|
|
1375
|
+
function responseItemMessageText(payload) {
|
|
1376
|
+
return sharedResponseItemMessageText(payload);
|
|
990
1377
|
}
|
|
991
1378
|
|
|
992
1379
|
function responseItemContentText(item) {
|
|
@@ -1090,7 +1477,10 @@ function normalizeString(value) {
|
|
|
1090
1477
|
}
|
|
1091
1478
|
|
|
1092
1479
|
module.exports = {
|
|
1480
|
+
JSONL_OLDER_HANDOFF_CURSOR,
|
|
1093
1481
|
parseSessionJsonlMetadata,
|
|
1094
1482
|
parseSessionJsonlTurns,
|
|
1483
|
+
readRecentSessionJsonlTurns,
|
|
1484
|
+
readSessionJsonlMetadataFromFile,
|
|
1095
1485
|
readThreadTurnsListPageFromSessionJsonl,
|
|
1096
1486
|
};
|
|
@@ -6,12 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
const { readLatestContextWindowUsage } = require("./rollout-watch");
|
|
8
8
|
|
|
9
|
-
function handleThreadContextRequest(rawMessage, sendResponse) {
|
|
10
|
-
let parsed;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
function handleThreadContextRequest(rawMessage, sendResponse, parsedMessage = null) {
|
|
10
|
+
let parsed = parsedMessage;
|
|
11
|
+
if (!parsed) {
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(rawMessage);
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
const method = typeof parsed?.method === "string" ? parsed.method.trim() : "";
|