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