@makerbi/remodex 1.5.4 → 2.0.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 +77 -13
- package/package.json +1 -1
- package/src/account-status.js +7 -1
- package/src/apply-patch-changes.js +185 -0
- package/src/bootstrap-codex-cli.js +1 -1
- package/src/bridge-status.js +3 -2
- package/src/bridge.js +837 -73
- package/src/codex-transport.js +10 -10
- package/src/desktop-handler.js +14 -1
- package/src/desktop-ipc-action-follower.js +129 -0
- package/src/git-handler.js +92 -28
- package/src/index.js +4 -2
- package/src/ios-app-compatibility.js +7 -7
- package/src/macos-launch-agent.js +132 -2
- package/src/project-handler.js +162 -1
- package/src/push-notification-service-client.js +85 -37
- package/src/push-notification-tracker.js +15 -0
- package/src/qr.js +2 -5
- package/src/rollout-live-mirror.js +331 -20
- package/src/rollout-watch.js +5 -1
- package/src/secure-device-state.js +66 -2
- package/src/secure-transport.js +47 -12
- package/src/session-jsonl-history.js +850 -16
- package/src/voice-handler.js +162 -65
- package/src/workspace-handler.js +327 -14
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// FILE: session-jsonl-history.js
|
|
2
|
-
// Purpose: Reconstructs a small thread/turns/list page from local Codex session JSONL files
|
|
2
|
+
// Purpose: Reconstructs a small thread/turns/list page from local Codex session JSONL files,
|
|
3
|
+
// including desktop-local timestamp metadata for mobile history rendering.
|
|
3
4
|
|
|
4
5
|
const fs = require("fs");
|
|
6
|
+
const { buildApplyPatchFileChangeItem } = require("./apply-patch-changes");
|
|
5
7
|
|
|
6
8
|
function readThreadTurnsListPageFromSessionJsonl(filePath, {
|
|
7
9
|
threadId = "",
|
|
@@ -31,16 +33,73 @@ function readThreadTurnsListPageFromSessionJsonl(filePath, {
|
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
// Extracts thread-level context that app-server history can omit for desktop-origin runs.
|
|
37
|
+
function parseSessionJsonlMetadata(content) {
|
|
38
|
+
let threadId = "";
|
|
39
|
+
let cwd = "";
|
|
40
|
+
|
|
41
|
+
const raw = String(content || "");
|
|
42
|
+
let lineStart = 0;
|
|
43
|
+
while (lineStart < raw.length) {
|
|
44
|
+
let lineEnd = raw.indexOf("\n", lineStart);
|
|
45
|
+
if (lineEnd === -1) {
|
|
46
|
+
lineEnd = raw.length;
|
|
47
|
+
}
|
|
48
|
+
const line = raw.substring(lineStart, lineEnd).trim();
|
|
49
|
+
lineStart = lineEnd + 1;
|
|
50
|
+
if (!line) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let entry;
|
|
55
|
+
try {
|
|
56
|
+
entry = JSON.parse(line);
|
|
57
|
+
} catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (entry?.type !== "session_meta") {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const payload = objectValue(entry.payload);
|
|
66
|
+
threadId ||= normalizeString(payload?.id)
|
|
67
|
+
|| normalizeString(payload?.thread_id)
|
|
68
|
+
|| normalizeString(payload?.threadId);
|
|
69
|
+
cwd ||= normalizeString(payload?.cwd)
|
|
70
|
+
|| normalizeString(payload?.current_working_directory)
|
|
71
|
+
|| normalizeString(payload?.working_directory);
|
|
72
|
+
|
|
73
|
+
if (threadId && cwd) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { threadId, cwd };
|
|
79
|
+
}
|
|
80
|
+
|
|
34
81
|
function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
35
82
|
const turns = [];
|
|
36
83
|
const turnsById = new Map();
|
|
37
84
|
let activeTurnId = "";
|
|
38
85
|
let sessionThreadId = normalizeString(threadId);
|
|
86
|
+
let sessionCwd = "";
|
|
87
|
+
let sessionTimeZone = "";
|
|
39
88
|
const skippedCallIds = new Set();
|
|
89
|
+
const toolCallsByCallId = new Map();
|
|
90
|
+
const pendingUserMessages = [];
|
|
40
91
|
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
92
|
+
const raw = String(content || "");
|
|
93
|
+
let index = -1;
|
|
94
|
+
let lineStart = 0;
|
|
95
|
+
while (lineStart < raw.length) {
|
|
96
|
+
index += 1;
|
|
97
|
+
let lineEnd = raw.indexOf("\n", lineStart);
|
|
98
|
+
if (lineEnd === -1) {
|
|
99
|
+
lineEnd = raw.length;
|
|
100
|
+
}
|
|
101
|
+
const line = raw.substring(lineStart, lineEnd).trim();
|
|
102
|
+
lineStart = lineEnd + 1;
|
|
44
103
|
if (!line) {
|
|
45
104
|
continue;
|
|
46
105
|
}
|
|
@@ -57,6 +116,27 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
57
116
|
sessionThreadId ||= normalizeString(payload?.id)
|
|
58
117
|
|| normalizeString(payload?.thread_id)
|
|
59
118
|
|| normalizeString(payload?.threadId);
|
|
119
|
+
sessionCwd ||= normalizeString(payload?.cwd);
|
|
120
|
+
sessionTimeZone ||= normalizeString(payload?.timezone)
|
|
121
|
+
|| normalizeString(payload?.timeZone)
|
|
122
|
+
|| normalizeString(payload?.time_zone);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (entry?.type === "turn_context") {
|
|
127
|
+
const payload = objectValue(entry.payload);
|
|
128
|
+
sessionCwd = normalizeString(payload?.cwd) || sessionCwd;
|
|
129
|
+
sessionTimeZone = normalizeString(payload?.timezone)
|
|
130
|
+
|| normalizeString(payload?.timeZone)
|
|
131
|
+
|| normalizeString(payload?.time_zone)
|
|
132
|
+
|| sessionTimeZone;
|
|
133
|
+
activeTurnId = normalizeString(payload?.turn_id)
|
|
134
|
+
|| normalizeString(payload?.turnId)
|
|
135
|
+
|| activeTurnId;
|
|
136
|
+
if (activeTurnId) {
|
|
137
|
+
const turn = ensureTurn(turns, turnsById, activeTurnId, sessionThreadId, entry.timestamp);
|
|
138
|
+
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
139
|
+
}
|
|
60
140
|
continue;
|
|
61
141
|
}
|
|
62
142
|
|
|
@@ -68,7 +148,9 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
68
148
|
|| normalizeString(payload?.turnId)
|
|
69
149
|
|| activeTurnId
|
|
70
150
|
|| `turn-line-${index + 1}`;
|
|
71
|
-
ensureTurn(turns, turnsById, activeTurnId, sessionThreadId, entry.timestamp);
|
|
151
|
+
const turn = ensureTurn(turns, turnsById, activeTurnId, sessionThreadId, entry.timestamp);
|
|
152
|
+
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
153
|
+
flushPendingUserMessagesToTurn(turn, pendingUserMessages);
|
|
72
154
|
continue;
|
|
73
155
|
}
|
|
74
156
|
|
|
@@ -80,11 +162,18 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
80
162
|
sessionThreadId,
|
|
81
163
|
entry.timestamp
|
|
82
164
|
);
|
|
165
|
+
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
83
166
|
turn.status = "completed";
|
|
167
|
+
activeTurnId = "";
|
|
84
168
|
continue;
|
|
85
169
|
}
|
|
86
170
|
|
|
87
|
-
if (eventType === "
|
|
171
|
+
if (eventType === "item_completed") {
|
|
172
|
+
const completedItem = objectValue(payload?.item);
|
|
173
|
+
if (!completedItem) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
88
177
|
const turn = ensureTurn(
|
|
89
178
|
turns,
|
|
90
179
|
turnsById,
|
|
@@ -92,12 +181,35 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
92
181
|
sessionThreadId,
|
|
93
182
|
entry.timestamp
|
|
94
183
|
);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
role: "user",
|
|
99
|
-
text: normalizeString(payload?.message) || normalizeString(payload?.text),
|
|
184
|
+
const item = normalizeResponseItemForHistory(completedItem, index + 1, {
|
|
185
|
+
cwd: sessionCwd,
|
|
186
|
+
toolCallsByCallId,
|
|
100
187
|
});
|
|
188
|
+
if (item) {
|
|
189
|
+
applyHistoryTimeZone(item, sessionTimeZone);
|
|
190
|
+
turn.items.push(item);
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (eventType === "user_message") {
|
|
196
|
+
const explicitTurnId = normalizeString(payload?.turn_id) || normalizeString(payload?.turnId);
|
|
197
|
+
const item = createUserMessageHistoryItem(payload, index + 1, entry.timestamp);
|
|
198
|
+
applyHistoryTimeZone(item, sessionTimeZone);
|
|
199
|
+
if (!explicitTurnId && !activeTurnId) {
|
|
200
|
+
pushPendingUserMessage(pendingUserMessages, item);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const turn = ensureTurn(
|
|
205
|
+
turns,
|
|
206
|
+
turnsById,
|
|
207
|
+
explicitTurnId || activeTurnId || `turn-line-${index + 1}`,
|
|
208
|
+
sessionThreadId,
|
|
209
|
+
entry.timestamp
|
|
210
|
+
);
|
|
211
|
+
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
212
|
+
addHistoryItemToTurn(turn, item);
|
|
101
213
|
continue;
|
|
102
214
|
}
|
|
103
215
|
|
|
@@ -111,6 +223,7 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
111
223
|
if (!payload) {
|
|
112
224
|
continue;
|
|
113
225
|
}
|
|
226
|
+
rememberToolCallForHistory(payload, toolCallsByCallId);
|
|
114
227
|
if (shouldSkipResponseItemForHistory(payload, skippedCallIds)) {
|
|
115
228
|
continue;
|
|
116
229
|
}
|
|
@@ -121,9 +234,24 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
121
234
|
sessionThreadId,
|
|
122
235
|
entry.timestamp
|
|
123
236
|
);
|
|
124
|
-
|
|
237
|
+
applyHistoryTimeZone(turn, sessionTimeZone);
|
|
238
|
+
const item = normalizeResponseItemForHistory(payload, index + 1, {
|
|
239
|
+
cwd: sessionCwd,
|
|
240
|
+
toolCallsByCallId,
|
|
241
|
+
});
|
|
125
242
|
if (item) {
|
|
126
|
-
turn
|
|
243
|
+
if (shouldSkipDuplicateProposedPlanMessage(turn, item)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const itemTimestamp = historyItemTimestamp(item, entry.timestamp);
|
|
247
|
+
if (itemTimestamp && !item.createdAt) {
|
|
248
|
+
item.createdAt = itemTimestamp;
|
|
249
|
+
}
|
|
250
|
+
if (itemTimestamp && !item.timestamp) {
|
|
251
|
+
item.timestamp = itemTimestamp;
|
|
252
|
+
}
|
|
253
|
+
applyHistoryTimeZone(item, sessionTimeZone);
|
|
254
|
+
addHistoryItemToTurn(turn, item);
|
|
127
255
|
}
|
|
128
256
|
}
|
|
129
257
|
}
|
|
@@ -131,6 +259,166 @@ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
|
|
|
131
259
|
return turns.filter((turn) => turn.items.length > 0);
|
|
132
260
|
}
|
|
133
261
|
|
|
262
|
+
function createUserMessageHistoryItem(payload, lineNumber, timestamp) {
|
|
263
|
+
const createdAt = historyItemTimestamp(payload, timestamp);
|
|
264
|
+
return {
|
|
265
|
+
id: normalizeString(payload?.id) || `user-message-line-${lineNumber}`,
|
|
266
|
+
type: "user_message",
|
|
267
|
+
role: "user",
|
|
268
|
+
text: normalizeString(payload?.message) || normalizeString(payload?.text),
|
|
269
|
+
createdAt: createdAt || undefined,
|
|
270
|
+
timestamp: createdAt || undefined,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function pushPendingUserMessage(pendingUserMessages, item) {
|
|
275
|
+
if (!item || !historyUserItemText(item)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (pendingUserMessages.some((candidate) => areDuplicateUserHistoryItems(candidate, item))) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
pendingUserMessages.push(item);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function addHistoryItemToTurn(turn, item) {
|
|
285
|
+
if (!turn || !item) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (isUserHistoryItem(item)) {
|
|
290
|
+
const duplicateIndex = turn.items.findIndex((candidate) => areDuplicateUserHistoryItems(candidate, item));
|
|
291
|
+
if (duplicateIndex !== -1) {
|
|
292
|
+
turn.items[duplicateIndex] = mergeDuplicateUserHistoryItems(turn.items[duplicateIndex], item);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
turn.items.push(item);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function mergeDuplicateUserHistoryItems(existing, incoming) {
|
|
301
|
+
const existingHasStructuredContent = hasStructuredUserHistoryContent(existing);
|
|
302
|
+
const incomingHasStructuredContent = hasStructuredUserHistoryContent(incoming);
|
|
303
|
+
const preferStructured = existingHasStructuredContent !== incomingHasStructuredContent;
|
|
304
|
+
const preferIncoming = preferStructured
|
|
305
|
+
? incomingHasStructuredContent
|
|
306
|
+
: normalizeHistoryToken(incoming?.type) === "usermessage"
|
|
307
|
+
&& normalizeHistoryToken(existing?.type) !== "usermessage";
|
|
308
|
+
const base = preferIncoming ? incoming : existing;
|
|
309
|
+
const fallback = preferIncoming ? existing : incoming;
|
|
310
|
+
return {
|
|
311
|
+
...base,
|
|
312
|
+
content: Array.isArray(base?.content) ? base.content : fallback?.content,
|
|
313
|
+
attachments: Array.isArray(base?.attachments) ? base.attachments : fallback?.attachments,
|
|
314
|
+
createdAt: historyItemTimestamp(base, historyItemTimestamp(fallback)) || undefined,
|
|
315
|
+
timestamp: historyItemTimestamp(base, historyItemTimestamp(fallback)) || undefined,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function hasStructuredUserHistoryContent(item) {
|
|
320
|
+
const content = Array.isArray(item?.content) ? item.content : [];
|
|
321
|
+
return content
|
|
322
|
+
.map((entry) => objectValue(entry))
|
|
323
|
+
.filter(Boolean)
|
|
324
|
+
.some((entry) => {
|
|
325
|
+
const type = normalizeHistoryToken(entry.type);
|
|
326
|
+
return type === "skill"
|
|
327
|
+
|| type === "mention"
|
|
328
|
+
|| type === "image"
|
|
329
|
+
|| type === "inputimage";
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function areDuplicateUserHistoryItems(first, second) {
|
|
334
|
+
if (!isUserHistoryItem(first) || !isUserHistoryItem(second)) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
const firstText = historyUserItemText(first);
|
|
338
|
+
const secondText = historyUserItemText(second);
|
|
339
|
+
if (!firstText || !secondText) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
if (firstText === secondText) {
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
const firstKey = canonicalUserHistoryTextKey(firstText);
|
|
346
|
+
const secondKey = canonicalUserHistoryTextKey(secondText);
|
|
347
|
+
if (firstKey.hasMentions && firstKey.key === secondKey.key) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
return Boolean(
|
|
351
|
+
firstKey.text
|
|
352
|
+
&& firstKey.text === secondKey.text
|
|
353
|
+
&& (firstKey.hasMentions || secondKey.hasMentions)
|
|
354
|
+
&& sameUserHistoryTimestamp(first, second)
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function sameUserHistoryTimestamp(first, second) {
|
|
359
|
+
const firstTimestamp = historyItemTimestamp(first);
|
|
360
|
+
const secondTimestamp = historyItemTimestamp(second);
|
|
361
|
+
return Boolean(firstTimestamp && secondTimestamp && firstTimestamp === secondTimestamp);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function historyItemTimestamp(item, fallbackTimestamp = "") {
|
|
365
|
+
return firstNonEmptyString([
|
|
366
|
+
normalizeString(item?.createdAt),
|
|
367
|
+
normalizeString(item?.created_at),
|
|
368
|
+
normalizeString(item?.startedAt),
|
|
369
|
+
normalizeString(item?.started_at),
|
|
370
|
+
normalizeString(item?.completedAt),
|
|
371
|
+
normalizeString(item?.completed_at),
|
|
372
|
+
normalizeString(item?.endedAt),
|
|
373
|
+
normalizeString(item?.ended_at),
|
|
374
|
+
normalizeString(item?.timestamp),
|
|
375
|
+
normalizeString(item?.time),
|
|
376
|
+
normalizeString(fallbackTimestamp),
|
|
377
|
+
]);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function isUserHistoryItem(item) {
|
|
381
|
+
return normalizeHistoryToken(item?.type) === "usermessage"
|
|
382
|
+
|| normalizeString(item?.role).toLowerCase() === "user";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function historyUserItemText(item) {
|
|
386
|
+
return normalizeString(item?.text)
|
|
387
|
+
|| normalizeString(item?.message)
|
|
388
|
+
|| responseItemMessageText(item);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function shouldSkipDuplicateProposedPlanMessage(turn, item) {
|
|
392
|
+
if (!turn || !item || normalizeHistoryToken(item.type) !== "message") {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const role = normalizeString(item.role).toLowerCase();
|
|
397
|
+
if (role && role !== "assistant") {
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!responseItemMessageText(item).includes("<proposed_plan>")) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return turn.items.some((candidate) => (
|
|
406
|
+
normalizeHistoryToken(candidate?.type) === "plan"
|
|
407
|
+
&& candidate?.remodexJsonlProgressPlan !== true
|
|
408
|
+
));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function flushPendingUserMessagesToTurn(turn, pendingUserMessages) {
|
|
412
|
+
if (!turn || pendingUserMessages.length === 0) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (const item of pendingUserMessages.splice(0)) {
|
|
417
|
+
applyHistoryTimeZone(item, normalizeString(turn.timeZone) || normalizeString(turn.timezone));
|
|
418
|
+
addHistoryItemToTurn(turn, item);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
134
422
|
function ensureTurn(turns, turnsById, turnId, threadId, timestamp) {
|
|
135
423
|
const normalizedTurnId = normalizeString(turnId) || `turn-${turns.length + 1}`;
|
|
136
424
|
let turn = turnsById.get(normalizedTurnId);
|
|
@@ -151,12 +439,51 @@ function ensureTurn(turns, turnsById, turnId, threadId, timestamp) {
|
|
|
151
439
|
return turn;
|
|
152
440
|
}
|
|
153
441
|
|
|
154
|
-
function
|
|
442
|
+
function applyHistoryTimeZone(target, timeZone) {
|
|
443
|
+
const normalizedTimeZone = normalizeString(timeZone);
|
|
444
|
+
if (!target || !normalizedTimeZone) {
|
|
445
|
+
return target;
|
|
446
|
+
}
|
|
447
|
+
if (!target.timeZoneIdentifier) {
|
|
448
|
+
target.timeZoneIdentifier = normalizedTimeZone;
|
|
449
|
+
}
|
|
450
|
+
if (!target.timeZone) {
|
|
451
|
+
target.timeZone = normalizedTimeZone;
|
|
452
|
+
}
|
|
453
|
+
if (!target.timezone) {
|
|
454
|
+
target.timezone = normalizedTimeZone;
|
|
455
|
+
}
|
|
456
|
+
return target;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function normalizeResponseItemForHistory(payload, lineNumber, { cwd = "", toolCallsByCallId = new Map() } = {}) {
|
|
155
460
|
const type = normalizeHistoryItemType(payload.type);
|
|
156
461
|
if (!type) {
|
|
157
462
|
return null;
|
|
158
463
|
}
|
|
159
464
|
|
|
465
|
+
const progressPlanItem = normalizeProgressPlanItemForHistory(payload);
|
|
466
|
+
if (progressPlanItem) {
|
|
467
|
+
return progressPlanItem;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const applyPatchItem = normalizeApplyPatchItemForHistory(payload, lineNumber, { cwd });
|
|
471
|
+
if (applyPatchItem) {
|
|
472
|
+
return applyPatchItem;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const toolOutputImageViewItem = normalizeToolOutputImageViewItemForHistory(payload, lineNumber, {
|
|
476
|
+
toolCallsByCallId,
|
|
477
|
+
});
|
|
478
|
+
if (toolOutputImageViewItem) {
|
|
479
|
+
return toolOutputImageViewItem;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const readableToolItem = normalizeReadableToolItemForHistory(payload, lineNumber, { cwd });
|
|
483
|
+
if (readableToolItem) {
|
|
484
|
+
return readableToolItem;
|
|
485
|
+
}
|
|
486
|
+
|
|
160
487
|
const item = {
|
|
161
488
|
...payload,
|
|
162
489
|
id: normalizeString(payload.id)
|
|
@@ -173,6 +500,423 @@ function normalizeResponseItemForHistory(payload, lineNumber) {
|
|
|
173
500
|
return item;
|
|
174
501
|
}
|
|
175
502
|
|
|
503
|
+
// Converts `view_image` tool output blobs into a lightweight local image reference.
|
|
504
|
+
function normalizeToolOutputImageViewItemForHistory(payload, lineNumber, { toolCallsByCallId = new Map() } = {}) {
|
|
505
|
+
const type = normalizeHistoryItemType(payload.type);
|
|
506
|
+
if (normalizeHistoryToken(type) !== "toolcalloutput") {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const callId = normalizeString(payload.call_id)
|
|
511
|
+
|| normalizeString(payload.callId)
|
|
512
|
+
|| normalizeString(payload.id);
|
|
513
|
+
const toolCall = callId ? toolCallsByCallId.get(callId) : null;
|
|
514
|
+
if (!toolCall || normalizeString(toolCall.toolName).toLowerCase() !== "view_image") {
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const imagePath = normalizeString(toolCall.imagePath);
|
|
519
|
+
if (!imagePath || !toolCallOutputContainsInlineImage(payload.output)) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
id: `${callId || `tool-output-line-${lineNumber}`}-image-view`,
|
|
525
|
+
type: "imageView",
|
|
526
|
+
status: normalizeString(payload.status) || "completed",
|
|
527
|
+
path: imagePath,
|
|
528
|
+
call_id: callId || undefined,
|
|
529
|
+
tool_name: toolCall.toolName,
|
|
530
|
+
remodexJsonlToolOutputImage: true,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Enriches raw tool-call JSONL records so mobile history can render useful rows.
|
|
535
|
+
function normalizeReadableToolItemForHistory(payload, lineNumber, { cwd = "" } = {}) {
|
|
536
|
+
const type = normalizeHistoryItemType(payload.type);
|
|
537
|
+
const typeToken = normalizeHistoryToken(type);
|
|
538
|
+
if (typeToken !== "toolcall" && typeToken !== "customtoolcall") {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const toolName = normalizeString(payload.name)
|
|
543
|
+
|| normalizeString(payload.tool_name)
|
|
544
|
+
|| normalizeString(payload.toolName);
|
|
545
|
+
if (!toolName) {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const callId = normalizeString(payload.call_id)
|
|
550
|
+
|| normalizeString(payload.callId)
|
|
551
|
+
|| normalizeString(payload.id);
|
|
552
|
+
const argumentsObject = parseToolArguments(
|
|
553
|
+
payload.arguments !== undefined ? payload.arguments : payload.input
|
|
554
|
+
);
|
|
555
|
+
const id = callId || normalizeString(payload.id) || `tool-call-line-${lineNumber}`;
|
|
556
|
+
const status = normalizeString(payload.status) || "completed";
|
|
557
|
+
|
|
558
|
+
if (isCommandToolName(toolName)) {
|
|
559
|
+
return {
|
|
560
|
+
...payload,
|
|
561
|
+
id,
|
|
562
|
+
type: "commandExecution",
|
|
563
|
+
status,
|
|
564
|
+
command: resolveToolCommand(toolName, argumentsObject),
|
|
565
|
+
cwd: resolveToolWorkingDirectory(argumentsObject, { cwd }),
|
|
566
|
+
call_id: callId || undefined,
|
|
567
|
+
tool_name: toolName,
|
|
568
|
+
arguments: payload.arguments,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const message = readableToolActivityMessage(toolName, argumentsObject, payload);
|
|
573
|
+
if (!message) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return {
|
|
578
|
+
...payload,
|
|
579
|
+
id,
|
|
580
|
+
type: "tool_call",
|
|
581
|
+
status,
|
|
582
|
+
message,
|
|
583
|
+
call_id: callId || undefined,
|
|
584
|
+
tool_name: toolName,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function rememberToolCallForHistory(payload, toolCallsByCallId) {
|
|
589
|
+
const typeToken = normalizeHistoryToken(normalizeHistoryItemType(payload?.type));
|
|
590
|
+
if (typeToken !== "toolcall" && typeToken !== "customtoolcall") {
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const callId = normalizeString(payload.call_id)
|
|
595
|
+
|| normalizeString(payload.callId)
|
|
596
|
+
|| normalizeString(payload.id);
|
|
597
|
+
const toolName = normalizeString(payload.name)
|
|
598
|
+
|| normalizeString(payload.tool_name)
|
|
599
|
+
|| normalizeString(payload.toolName);
|
|
600
|
+
if (!callId || !toolName) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const argumentsObject = parseToolArguments(
|
|
605
|
+
payload.arguments !== undefined ? payload.arguments : payload.input
|
|
606
|
+
);
|
|
607
|
+
toolCallsByCallId.set(callId, {
|
|
608
|
+
toolName,
|
|
609
|
+
imagePath: resolveToolImagePath(toolName, argumentsObject, payload),
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function resolveToolImagePath(toolName, argumentsObject, payload) {
|
|
614
|
+
if (normalizeString(toolName).toLowerCase() !== "view_image") {
|
|
615
|
+
return "";
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return firstNonEmptyString([
|
|
619
|
+
normalizeString(argumentsObject.path),
|
|
620
|
+
normalizeString(argumentsObject.filePath),
|
|
621
|
+
normalizeString(argumentsObject.file_path),
|
|
622
|
+
normalizeString(argumentsObject.localPath),
|
|
623
|
+
normalizeString(argumentsObject.local_path),
|
|
624
|
+
normalizeString(payload.path),
|
|
625
|
+
normalizeString(payload.filePath),
|
|
626
|
+
normalizeString(payload.file_path),
|
|
627
|
+
normalizeString(payload.localPath),
|
|
628
|
+
normalizeString(payload.local_path),
|
|
629
|
+
]);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function toolCallOutputContainsInlineImage(rawOutput) {
|
|
633
|
+
const parsedOutput = typeof rawOutput === "string"
|
|
634
|
+
? safeParseJSON(rawOutput) || rawOutput
|
|
635
|
+
: rawOutput;
|
|
636
|
+
return containsInlineImageDataURL(parsedOutput);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function containsInlineImageDataURL(value) {
|
|
640
|
+
if (typeof value === "string") {
|
|
641
|
+
return value.toLowerCase().startsWith("data:image");
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (Array.isArray(value)) {
|
|
645
|
+
return value.some(containsInlineImageDataURL);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (value && typeof value === "object") {
|
|
649
|
+
return Object.values(value).some(containsInlineImageDataURL);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function normalizeApplyPatchItemForHistory(payload, lineNumber, { cwd = "" } = {}) {
|
|
656
|
+
const type = normalizeHistoryItemType(payload.type);
|
|
657
|
+
if (normalizeString(payload.name) !== "apply_patch" || normalizeHistoryToken(type) !== "customtoolcall") {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const callId = normalizeString(payload.call_id)
|
|
662
|
+
|| normalizeString(payload.callId)
|
|
663
|
+
|| normalizeString(payload.id);
|
|
664
|
+
const item = buildApplyPatchFileChangeItem({
|
|
665
|
+
callId,
|
|
666
|
+
patch: normalizeString(payload.input),
|
|
667
|
+
status: normalizeString(payload.status) || "completed",
|
|
668
|
+
idFallback: callId || `apply-patch-line-${lineNumber}`,
|
|
669
|
+
cwd,
|
|
670
|
+
});
|
|
671
|
+
return item ? { ...payload, ...item } : null;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function normalizeProgressPlanItemForHistory(payload) {
|
|
675
|
+
const type = normalizeHistoryItemType(payload.type);
|
|
676
|
+
if (!isInternalProgressPlanCall(payload) || normalizeHistoryToken(type) !== "toolcall") {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const argumentsObject = parseToolArguments(payload.arguments);
|
|
681
|
+
const explanation = normalizeString(argumentsObject.explanation);
|
|
682
|
+
const plan = normalizeHistoryPlanSteps(argumentsObject.plan);
|
|
683
|
+
if (!explanation && plan.length === 0) {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return {
|
|
688
|
+
id: normalizeString(payload.call_id)
|
|
689
|
+
|| normalizeString(payload.callId)
|
|
690
|
+
|| normalizeString(payload.id)
|
|
691
|
+
|| undefined,
|
|
692
|
+
type: "plan",
|
|
693
|
+
text: explanation || "Planning...",
|
|
694
|
+
explanation: explanation || undefined,
|
|
695
|
+
plan,
|
|
696
|
+
remodexJsonlProgressPlan: true,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function normalizeHistoryPlanSteps(rawPlan) {
|
|
701
|
+
if (!Array.isArray(rawPlan)) {
|
|
702
|
+
return [];
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return rawPlan.flatMap((rawStep) => {
|
|
706
|
+
const stepObject = objectValue(rawStep);
|
|
707
|
+
const step = normalizeString(stepObject?.step);
|
|
708
|
+
const status = normalizeHistoryPlanStatus(stepObject?.status);
|
|
709
|
+
return step && status ? [{ step, status }] : [];
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function normalizeHistoryPlanStatus(rawStatus) {
|
|
714
|
+
const normalized = normalizeString(rawStatus);
|
|
715
|
+
switch (normalized) {
|
|
716
|
+
case "pending":
|
|
717
|
+
case "in_progress":
|
|
718
|
+
case "inProgress":
|
|
719
|
+
case "completed":
|
|
720
|
+
return normalized;
|
|
721
|
+
default:
|
|
722
|
+
return "";
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function parseToolArguments(rawArguments) {
|
|
727
|
+
const parsed = typeof rawArguments === "string"
|
|
728
|
+
? safeParseJSON(normalizeString(rawArguments))
|
|
729
|
+
: rawArguments;
|
|
730
|
+
return objectValue(parsed) || {};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function resolveToolCommand(toolName, argumentsObject) {
|
|
734
|
+
if (!isCommandToolName(toolName)) {
|
|
735
|
+
return toolName;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
return firstNonEmptyString([
|
|
739
|
+
normalizeString(argumentsObject.cmd),
|
|
740
|
+
normalizeString(argumentsObject.command),
|
|
741
|
+
normalizeString(argumentsObject.raw_command),
|
|
742
|
+
normalizeString(argumentsObject.rawCommand),
|
|
743
|
+
normalizeString(argumentsObject.input),
|
|
744
|
+
]) || toolName;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function resolveToolWorkingDirectory(argumentsObject, { cwd = "" } = {}) {
|
|
748
|
+
return firstNonEmptyString([
|
|
749
|
+
normalizeString(argumentsObject.workdir),
|
|
750
|
+
normalizeString(argumentsObject.cwd),
|
|
751
|
+
normalizeString(argumentsObject.working_directory),
|
|
752
|
+
normalizeString(argumentsObject.workingDirectory),
|
|
753
|
+
normalizeString(cwd),
|
|
754
|
+
]) || "";
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function isCommandToolName(toolName) {
|
|
758
|
+
const normalized = normalizeString(toolName).toLowerCase();
|
|
759
|
+
return normalized === "exec_command" || normalized === "shell_command";
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function readableToolActivityMessage(toolName, argumentsObject, payload) {
|
|
763
|
+
const normalized = normalizeString(toolName).toLowerCase();
|
|
764
|
+
switch (normalized) {
|
|
765
|
+
case "write_stdin":
|
|
766
|
+
return "Write to terminal";
|
|
767
|
+
case "read_thread_terminal":
|
|
768
|
+
return "Read terminal output";
|
|
769
|
+
case "view_image": {
|
|
770
|
+
const imagePath = firstNonEmptyString([
|
|
771
|
+
normalizeString(argumentsObject.path),
|
|
772
|
+
normalizeString(payload.path),
|
|
773
|
+
]);
|
|
774
|
+
return imagePath ? `Open image ${compactHistoryPath(imagePath)}` : "Open image";
|
|
775
|
+
}
|
|
776
|
+
case "open":
|
|
777
|
+
case "browser.open":
|
|
778
|
+
return readableTargetMessage("Open", argumentsObject, payload);
|
|
779
|
+
case "click":
|
|
780
|
+
case "browser.click":
|
|
781
|
+
return readableTargetMessage("Click", argumentsObject, payload);
|
|
782
|
+
case "find":
|
|
783
|
+
case "browser.find":
|
|
784
|
+
return readableTargetMessage("Find", argumentsObject, payload);
|
|
785
|
+
case "screenshot":
|
|
786
|
+
case "browser.screenshot":
|
|
787
|
+
return "Capture screenshot";
|
|
788
|
+
case "web.run":
|
|
789
|
+
case "search_query":
|
|
790
|
+
case "image_query":
|
|
791
|
+
return readableSearchMessage(argumentsObject, payload);
|
|
792
|
+
case "weather":
|
|
793
|
+
return readableLocationMessage("Check weather", argumentsObject, payload);
|
|
794
|
+
case "finance":
|
|
795
|
+
return readableSymbolMessage("Check market data", argumentsObject, payload);
|
|
796
|
+
case "sports":
|
|
797
|
+
return readableLocationMessage("Check sports", argumentsObject, payload);
|
|
798
|
+
case "automation_update":
|
|
799
|
+
return "Update automation";
|
|
800
|
+
case "update_goal":
|
|
801
|
+
return "Update goal";
|
|
802
|
+
case "create_goal":
|
|
803
|
+
return "Create goal";
|
|
804
|
+
case "get_goal":
|
|
805
|
+
return "Read goal";
|
|
806
|
+
case "request_user_input":
|
|
807
|
+
return "Request input";
|
|
808
|
+
default:
|
|
809
|
+
return `Run ${humanizeToolName(toolName)}`;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function readableTargetMessage(verb, argumentsObject, payload) {
|
|
814
|
+
const target = firstNonEmptyString([
|
|
815
|
+
normalizeString(argumentsObject.ref_id),
|
|
816
|
+
normalizeString(argumentsObject.refId),
|
|
817
|
+
normalizeString(argumentsObject.url),
|
|
818
|
+
normalizeString(argumentsObject.pattern),
|
|
819
|
+
normalizeString(argumentsObject.query),
|
|
820
|
+
normalizeString(payload.ref_id),
|
|
821
|
+
normalizeString(payload.url),
|
|
822
|
+
]);
|
|
823
|
+
return target ? `${verb} ${compactHistoryPath(target)}` : verb;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function readableSearchMessage(argumentsObject, payload) {
|
|
827
|
+
const query = firstSearchQuery(argumentsObject) || firstSearchQuery(payload);
|
|
828
|
+
return query ? `Search ${query}` : "Search web";
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function firstSearchQuery(object) {
|
|
832
|
+
const direct = normalizeString(object.q)
|
|
833
|
+
|| normalizeString(object.query)
|
|
834
|
+
|| normalizeString(object.search_query);
|
|
835
|
+
if (direct) {
|
|
836
|
+
return direct;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
let searchArray = null;
|
|
840
|
+
if (Array.isArray(object.search_query)) {
|
|
841
|
+
searchArray = object.search_query;
|
|
842
|
+
} else if (Array.isArray(object.image_query)) {
|
|
843
|
+
searchArray = object.image_query;
|
|
844
|
+
}
|
|
845
|
+
if (!searchArray) {
|
|
846
|
+
return "";
|
|
847
|
+
}
|
|
848
|
+
for (const item of searchArray) {
|
|
849
|
+
const query = normalizeString(objectValue(item)?.q)
|
|
850
|
+
|| normalizeString(objectValue(item)?.query);
|
|
851
|
+
if (query) {
|
|
852
|
+
return query;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return "";
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function readableLocationMessage(verb, argumentsObject, payload) {
|
|
859
|
+
const target = firstNonEmptyString([
|
|
860
|
+
normalizeString(argumentsObject.location),
|
|
861
|
+
normalizeString(argumentsObject.team),
|
|
862
|
+
normalizeString(argumentsObject.league),
|
|
863
|
+
normalizeString(payload.location),
|
|
864
|
+
]);
|
|
865
|
+
return target ? `${verb} ${target}` : verb;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function readableSymbolMessage(verb, argumentsObject, payload) {
|
|
869
|
+
const target = firstNonEmptyString([
|
|
870
|
+
normalizeString(argumentsObject.ticker),
|
|
871
|
+
normalizeString(argumentsObject.symbol),
|
|
872
|
+
normalizeString(payload.ticker),
|
|
873
|
+
]);
|
|
874
|
+
return target ? `${verb} ${target}` : verb;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function humanizeToolName(toolName) {
|
|
878
|
+
return normalizeString(toolName)
|
|
879
|
+
.replace(/^[^.]+\./, "")
|
|
880
|
+
.replace(/[_-]+/g, " ")
|
|
881
|
+
.replace(/\s+/g, " ")
|
|
882
|
+
.trim() || "tool";
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function compactHistoryPath(path) {
|
|
886
|
+
const text = normalizeString(path);
|
|
887
|
+
if (!text) {
|
|
888
|
+
return "";
|
|
889
|
+
}
|
|
890
|
+
const normalized = text.replace(/\\/g, "/");
|
|
891
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
892
|
+
if (parts.length <= 2) {
|
|
893
|
+
return text;
|
|
894
|
+
}
|
|
895
|
+
const prefix = normalized.startsWith("/") ? "…/" : "";
|
|
896
|
+
return `${prefix}${parts.slice(-2).join("/")}`;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function firstNonEmptyString(values) {
|
|
900
|
+
for (const value of values) {
|
|
901
|
+
const normalized = normalizeString(value);
|
|
902
|
+
if (normalized) {
|
|
903
|
+
return normalized;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return "";
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function safeParseJSON(rawValue) {
|
|
910
|
+
if (!rawValue) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
try {
|
|
914
|
+
return JSON.parse(rawValue);
|
|
915
|
+
} catch {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
176
920
|
// Filters desktop transcript internals that are stored as response items but are not chat history.
|
|
177
921
|
function shouldSkipResponseItemForHistory(payload, skippedCallIds) {
|
|
178
922
|
const type = normalizeHistoryItemType(payload.type);
|
|
@@ -189,6 +933,13 @@ function shouldSkipResponseItemForHistory(payload, skippedCallIds) {
|
|
|
189
933
|
return true;
|
|
190
934
|
}
|
|
191
935
|
|
|
936
|
+
if (type === "tool_call" && isInternalProgressPlanCall(payload)) {
|
|
937
|
+
if (callId) {
|
|
938
|
+
skippedCallIds.add(callId);
|
|
939
|
+
}
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
|
|
192
943
|
if (type !== "message") {
|
|
193
944
|
return false;
|
|
194
945
|
}
|
|
@@ -214,6 +965,10 @@ function isSubagentOrchestrationCall(payload) {
|
|
|
214
965
|
|| name === "close_agent";
|
|
215
966
|
}
|
|
216
967
|
|
|
968
|
+
function isInternalProgressPlanCall(payload) {
|
|
969
|
+
return normalizeString(payload.name).toLowerCase() === "update_plan";
|
|
970
|
+
}
|
|
971
|
+
|
|
217
972
|
function isSubagentNotificationMessage(payload) {
|
|
218
973
|
const text = responseItemMessageText(payload).trimStart();
|
|
219
974
|
return text.startsWith("<subagent_notification>");
|
|
@@ -229,13 +984,84 @@ function responseItemMessageText(payload) {
|
|
|
229
984
|
return content
|
|
230
985
|
.map((item) => objectValue(item))
|
|
231
986
|
.filter(Boolean)
|
|
232
|
-
.map((item) =>
|
|
987
|
+
.map((item) => responseItemContentText(item))
|
|
233
988
|
.filter(Boolean)
|
|
234
989
|
.join("\n");
|
|
235
990
|
}
|
|
236
991
|
|
|
992
|
+
function responseItemContentText(item) {
|
|
993
|
+
const type = normalizeHistoryToken(item?.type);
|
|
994
|
+
if (type === "skill") {
|
|
995
|
+
const skillName = normalizeString(item.id) || normalizeString(item.name);
|
|
996
|
+
return skillName ? `$${skillName}` : "";
|
|
997
|
+
}
|
|
998
|
+
if (type === "mention") {
|
|
999
|
+
const mentionName = normalizeString(item.name) || normalizeString(item.id);
|
|
1000
|
+
return mentionName ? `@${mentionName}` : "";
|
|
1001
|
+
}
|
|
1002
|
+
return normalizeString(item.text) || normalizeString(objectValue(item.data)?.text);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function canonicalUserHistoryTextKey(text) {
|
|
1006
|
+
const mentions = { skills: new Set(), plugins: new Set() };
|
|
1007
|
+
let body = normalizeString(text).replace(
|
|
1008
|
+
/(^|\s)([$/@])([A-Za-z0-9][A-Za-z0-9._-]*)(?=[\s,.;:!?)\]}>]|$)/g,
|
|
1009
|
+
(match, prefix, trigger, rawName) => {
|
|
1010
|
+
const name = normalizeString(rawName).toLowerCase();
|
|
1011
|
+
if (!name) {
|
|
1012
|
+
return match;
|
|
1013
|
+
}
|
|
1014
|
+
if (trigger === "$" || trigger === "/") {
|
|
1015
|
+
mentions.skills.add(name);
|
|
1016
|
+
} else if (trigger === "@") {
|
|
1017
|
+
mentions.plugins.add(name);
|
|
1018
|
+
}
|
|
1019
|
+
return prefix || "";
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
|
|
1023
|
+
for (const skill of mentions.skills) {
|
|
1024
|
+
body = removeBoundedUserMentionPhrase(body, `$${skill}`);
|
|
1025
|
+
body = removeBoundedUserMentionPhrase(body, `/${skill}`);
|
|
1026
|
+
body = removeBoundedUserMentionPhrase(body, displayNameForUserMention(skill));
|
|
1027
|
+
}
|
|
1028
|
+
for (const plugin of mentions.plugins) {
|
|
1029
|
+
body = removeBoundedUserMentionPhrase(body, `@${plugin}`);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const normalizedBody = body.trim().replace(/\s+/g, " ").toLowerCase();
|
|
1033
|
+
const skills = [...mentions.skills].sort();
|
|
1034
|
+
const plugins = [...mentions.plugins].sort();
|
|
1035
|
+
return {
|
|
1036
|
+
hasMentions: skills.length > 0 || plugins.length > 0,
|
|
1037
|
+
text: normalizedBody,
|
|
1038
|
+
key: `${normalizedBody}|skills:${skills.join(",")}|plugins:${plugins.join(",")}`,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function removeBoundedUserMentionPhrase(text, phrase) {
|
|
1043
|
+
const normalizedPhrase = normalizeString(phrase);
|
|
1044
|
+
if (!normalizedPhrase) {
|
|
1045
|
+
return text;
|
|
1046
|
+
}
|
|
1047
|
+
const pattern = new RegExp(`(^|\\s)${escapeRegExp(normalizedPhrase)}(?=[\\s,.;:!?)\\]}>]|$)`, "gi");
|
|
1048
|
+
return text.replace(pattern, (match, prefix) => prefix || "");
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function displayNameForUserMention(name) {
|
|
1052
|
+
return normalizeString(name)
|
|
1053
|
+
.split(/[-_]+/)
|
|
1054
|
+
.filter(Boolean)
|
|
1055
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
1056
|
+
.join(" ");
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function escapeRegExp(value) {
|
|
1060
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1061
|
+
}
|
|
1062
|
+
|
|
237
1063
|
function normalizeHistoryItemType(rawType) {
|
|
238
|
-
const normalized =
|
|
1064
|
+
const normalized = normalizeHistoryToken(rawType);
|
|
239
1065
|
if (!normalized) {
|
|
240
1066
|
return "";
|
|
241
1067
|
}
|
|
@@ -245,9 +1071,16 @@ function normalizeHistoryItemType(rawType) {
|
|
|
245
1071
|
if (normalized === "functioncalloutput") {
|
|
246
1072
|
return "tool_call_output";
|
|
247
1073
|
}
|
|
1074
|
+
if (normalized === "plan") {
|
|
1075
|
+
return "plan";
|
|
1076
|
+
}
|
|
248
1077
|
return rawType;
|
|
249
1078
|
}
|
|
250
1079
|
|
|
1080
|
+
function normalizeHistoryToken(rawType) {
|
|
1081
|
+
return normalizeString(rawType).toLowerCase().replace(/[\s_-]+/g, "");
|
|
1082
|
+
}
|
|
1083
|
+
|
|
251
1084
|
function objectValue(value) {
|
|
252
1085
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
253
1086
|
}
|
|
@@ -257,6 +1090,7 @@ function normalizeString(value) {
|
|
|
257
1090
|
}
|
|
258
1091
|
|
|
259
1092
|
module.exports = {
|
|
1093
|
+
parseSessionJsonlMetadata,
|
|
260
1094
|
parseSessionJsonlTurns,
|
|
261
1095
|
readThreadTurnsListPageFromSessionJsonl,
|
|
262
1096
|
};
|