@ouro.bot/cli 0.1.0-alpha.609 → 0.1.0-alpha.611
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/changelog.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.611",
|
|
6
|
+
"changes": [
|
|
7
|
+
"BlueBubbles recovery: stale captured inbound sidecars are now marked superseded when the chat session has already advanced, preventing old iMessage recovery entries from keeping runtime health in warn or replaying obsolete chat turns."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.610",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Fix the inner-dialog rest loop incident: skip empty rest-only assistant turns when deriving checkpoints, surface meaningful tool-only actions in thoughts/status output, compact idle heartbeat rest triplets, and avoid stale session repair churn during active-session scans."
|
|
14
|
+
]
|
|
15
|
+
},
|
|
4
16
|
{
|
|
5
17
|
"version": "0.1.0-alpha.609",
|
|
6
18
|
"changes": [
|
|
@@ -344,12 +344,79 @@ function extractSettleAnswer(messages) {
|
|
|
344
344
|
}
|
|
345
345
|
return "";
|
|
346
346
|
}
|
|
347
|
+
function parseToolArguments(argumentsValue) {
|
|
348
|
+
if (!argumentsValue)
|
|
349
|
+
return {};
|
|
350
|
+
try {
|
|
351
|
+
const parsed = JSON.parse(argumentsValue);
|
|
352
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
353
|
+
? parsed
|
|
354
|
+
: {};
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return {};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function toolArgumentText(args, keys) {
|
|
361
|
+
for (const key of keys) {
|
|
362
|
+
const value = args[key];
|
|
363
|
+
if (typeof value === "string" && value.trim()) {
|
|
364
|
+
return value.trim();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return "";
|
|
368
|
+
}
|
|
369
|
+
function truncateToolSummary(summary) {
|
|
370
|
+
if (summary.length <= 220)
|
|
371
|
+
return summary;
|
|
372
|
+
return `${summary.slice(0, 217)}...`;
|
|
373
|
+
}
|
|
374
|
+
function summarizeToolAction(name, argumentsValue) {
|
|
375
|
+
if (!name)
|
|
376
|
+
return null;
|
|
377
|
+
const args = parseToolArguments(argumentsValue);
|
|
378
|
+
if (name === "surface") {
|
|
379
|
+
const message = toolArgumentText(args, ["message", "text", "content"]);
|
|
380
|
+
return message ? `surfaced: ${message}` : null;
|
|
381
|
+
}
|
|
382
|
+
if (name === "ponder") {
|
|
383
|
+
const thought = toolArgumentText(args, ["summary", "question", "topic", "prompt"]);
|
|
384
|
+
return thought ? `pondered: ${thought}` : null;
|
|
385
|
+
}
|
|
386
|
+
if (name === "diary_write") {
|
|
387
|
+
const note = toolArgumentText(args, ["text", "content", "note", "entry"]);
|
|
388
|
+
return note ? `diary: ${note}` : null;
|
|
389
|
+
}
|
|
390
|
+
if (name === "let_go") {
|
|
391
|
+
const reason = toolArgumentText(args, ["reason", "note", "status"]);
|
|
392
|
+
return reason ? `let go: ${reason}` : null;
|
|
393
|
+
}
|
|
394
|
+
if (name === "rest") {
|
|
395
|
+
const note = toolArgumentText(args, ["note", "status"]);
|
|
396
|
+
return note ? `rested: ${note}` : null;
|
|
397
|
+
}
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
function extractToolActionSummary(messages) {
|
|
401
|
+
for (let k = messages.length - 1; k >= 0; k--) {
|
|
402
|
+
const msg = messages[k];
|
|
403
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
|
|
404
|
+
continue;
|
|
405
|
+
for (let i = msg.tool_calls.length - 1; i >= 0; i--) {
|
|
406
|
+
const toolFunction = extractToolFunction(msg.tool_calls[i]);
|
|
407
|
+
const summary = summarizeToolAction(toolFunction?.name, toolFunction?.arguments);
|
|
408
|
+
if (summary)
|
|
409
|
+
return truncateToolSummary(summary);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return "";
|
|
413
|
+
}
|
|
347
414
|
function extractThoughtResponseFromMessages(messages) {
|
|
348
415
|
const assistantMsgs = messages.filter((message) => message.role === "assistant");
|
|
349
416
|
const lastAssistant = assistantMsgs.reverse().find((message) => contentToText(message.content).trim().length > 0);
|
|
350
417
|
return lastAssistant
|
|
351
418
|
? contentToText(lastAssistant.content).trim()
|
|
352
|
-
: extractSettleAnswer(messages);
|
|
419
|
+
: extractSettleAnswer(messages) || extractToolActionSummary(messages);
|
|
353
420
|
}
|
|
354
421
|
function parseInnerDialogSession(sessionPath) {
|
|
355
422
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -56,7 +56,7 @@ function resolveFriendName(friendId, friendsDir, agentName) {
|
|
|
56
56
|
return friendId;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
function parseFriendActivity(sessionPath) {
|
|
59
|
+
function parseFriendActivity(sessionPath, activeThresholdMs, nowMs) {
|
|
60
60
|
let mtimeMs;
|
|
61
61
|
try {
|
|
62
62
|
mtimeMs = fs.statSync(sessionPath).mtimeMs;
|
|
@@ -64,6 +64,9 @@ function parseFriendActivity(sessionPath) {
|
|
|
64
64
|
catch {
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
|
+
if (Number.isFinite(activeThresholdMs) && nowMs - mtimeMs > activeThresholdMs) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
67
70
|
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(sessionPath);
|
|
68
71
|
const chronology = envelope ? (0, session_events_1.deriveSessionChronology)(envelope.events) : null;
|
|
69
72
|
const explicit = envelope?.state.lastFriendActivityAt;
|
|
@@ -152,7 +155,7 @@ function listSessionActivity(query) {
|
|
|
152
155
|
continue;
|
|
153
156
|
}
|
|
154
157
|
const sessionPath = path.join(channelPath, keyFile);
|
|
155
|
-
const activity = parseFriendActivity(sessionPath);
|
|
158
|
+
const activity = parseFriendActivity(sessionPath, activeThresholdMs, nowMs);
|
|
156
159
|
if (!activity)
|
|
157
160
|
continue;
|
|
158
161
|
if (nowMs - activity.lastActivityMs > activeThresholdMs)
|
package/dist/mind/context.js
CHANGED
|
@@ -54,6 +54,7 @@ Object.defineProperty(exports, "detectDuplicateToolCallIds", { enumerable: true,
|
|
|
54
54
|
Object.defineProperty(exports, "migrateToolNames", { enumerable: true, get: function () { return session_events_2.migrateToolNames; } });
|
|
55
55
|
Object.defineProperty(exports, "repairSessionMessages", { enumerable: true, get: function () { return session_events_2.repairSessionMessages; } });
|
|
56
56
|
Object.defineProperty(exports, "validateSessionMessages", { enumerable: true, get: function () { return session_events_2.validateSessionMessages; } });
|
|
57
|
+
const IDLE_REST_ONLY_KEEP_TURNS = 20;
|
|
57
58
|
function buildTrimmableBlocks(messages) {
|
|
58
59
|
const blocks = [];
|
|
59
60
|
let i = 0;
|
|
@@ -95,6 +96,89 @@ function getSystemMessageIndices(messages) {
|
|
|
95
96
|
function buildTrimmedMessages(messages, kept) {
|
|
96
97
|
return messages.filter((m, idx) => m.role === "system" || kept.has(idx));
|
|
97
98
|
}
|
|
99
|
+
function messageContentToText(content) {
|
|
100
|
+
if (typeof content === "string")
|
|
101
|
+
return content;
|
|
102
|
+
if (!Array.isArray(content))
|
|
103
|
+
return "";
|
|
104
|
+
return content
|
|
105
|
+
.map((part) => {
|
|
106
|
+
if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
|
|
107
|
+
return part.text;
|
|
108
|
+
}
|
|
109
|
+
return "";
|
|
110
|
+
})
|
|
111
|
+
.join("\n");
|
|
112
|
+
}
|
|
113
|
+
function toolCallName(toolCall) {
|
|
114
|
+
return toolCall.function.name;
|
|
115
|
+
}
|
|
116
|
+
function toolCallId(toolCall) {
|
|
117
|
+
return toolCall.id;
|
|
118
|
+
}
|
|
119
|
+
function isIdleHeartbeatRestUserMessage(message) {
|
|
120
|
+
if (message.role !== "user")
|
|
121
|
+
return false;
|
|
122
|
+
const text = messageContentToText(message.content);
|
|
123
|
+
return text.includes("...time passing. anything stirring?")
|
|
124
|
+
&& !text.includes("[pending from ")
|
|
125
|
+
&& !text.includes("## task:");
|
|
126
|
+
}
|
|
127
|
+
function isEmptyRestOnlyAssistantMessage(message) {
|
|
128
|
+
if (message.role !== "assistant")
|
|
129
|
+
return null;
|
|
130
|
+
if (messageContentToText(message.content).trim())
|
|
131
|
+
return null;
|
|
132
|
+
if (!Array.isArray(message.tool_calls) || message.tool_calls.length !== 1)
|
|
133
|
+
return null;
|
|
134
|
+
const onlyToolCall = message.tool_calls[0];
|
|
135
|
+
if (toolCallName(onlyToolCall) !== "rest")
|
|
136
|
+
return null;
|
|
137
|
+
return toolCallId(onlyToolCall);
|
|
138
|
+
}
|
|
139
|
+
function isMatchingToolResult(message, toolId) {
|
|
140
|
+
return message.tool_call_id === toolId;
|
|
141
|
+
}
|
|
142
|
+
function compactIdleRestOnlyTurns(messages, keepTurns = IDLE_REST_ONLY_KEEP_TURNS) {
|
|
143
|
+
const blocks = [];
|
|
144
|
+
let i = 0;
|
|
145
|
+
while (i < messages.length - 2) {
|
|
146
|
+
const userMessage = messages[i];
|
|
147
|
+
const assistantMessage = messages[i + 1];
|
|
148
|
+
const toolMessage = messages[i + 2];
|
|
149
|
+
const restToolId = isIdleHeartbeatRestUserMessage(userMessage)
|
|
150
|
+
? isEmptyRestOnlyAssistantMessage(assistantMessage)
|
|
151
|
+
: null;
|
|
152
|
+
if (restToolId && isMatchingToolResult(toolMessage, restToolId)) {
|
|
153
|
+
blocks.push({ start: i, end: i + 2 });
|
|
154
|
+
i += 3;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
i++;
|
|
158
|
+
}
|
|
159
|
+
if (blocks.length <= keepTurns)
|
|
160
|
+
return messages;
|
|
161
|
+
const drop = new Set();
|
|
162
|
+
for (const block of blocks.slice(0, blocks.length - keepTurns)) {
|
|
163
|
+
for (let index = block.start; index <= block.end; index++) {
|
|
164
|
+
drop.add(index);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const compacted = messages.filter((_message, index) => !drop.has(index));
|
|
168
|
+
(0, runtime_1.emitNervesEvent)({
|
|
169
|
+
component: "mind",
|
|
170
|
+
event: "mind.session_idle_rest_compaction",
|
|
171
|
+
message: "compacted old idle rest-only inner turns",
|
|
172
|
+
meta: {
|
|
173
|
+
removedTurns: blocks.length - keepTurns,
|
|
174
|
+
keptTurns: keepTurns,
|
|
175
|
+
removedMessages: drop.size,
|
|
176
|
+
originalCount: messages.length,
|
|
177
|
+
finalCount: compacted.length,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
return compacted;
|
|
181
|
+
}
|
|
98
182
|
function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
99
183
|
const targetTokens = Math.floor(maxTokens * (1 - contextMargin / 100));
|
|
100
184
|
const estimatedBefore = (0, token_estimate_1.estimateTokensForMessages)(messages);
|
|
@@ -284,9 +368,10 @@ function postTurnTrim(messages, usage, hooks) {
|
|
|
284
368
|
}
|
|
285
369
|
}
|
|
286
370
|
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
287
|
-
const currentIngressTimes = messages.map(session_events_1.getIngressTime);
|
|
288
371
|
const currentMessages = (0, session_events_1.sanitizeProviderMessages)(messages);
|
|
289
|
-
const
|
|
372
|
+
const currentIngressTimes = currentMessages.map(session_events_1.getIngressTime);
|
|
373
|
+
const tokenTrimmedMessages = trimMessages(currentMessages, maxTokens, contextMargin, usage?.input_tokens);
|
|
374
|
+
const trimmedMessages = compactIdleRestOnlyTurns(tokenTrimmedMessages);
|
|
290
375
|
messages.splice(0, messages.length, ...trimmedMessages);
|
|
291
376
|
return { currentMessages, trimmedMessages, currentIngressTimes, maxTokens, contextMargin };
|
|
292
377
|
}
|
|
@@ -1514,7 +1514,11 @@ function listPendingCapturedInboundMessages(agentName) {
|
|
|
1514
1514
|
seenMessageGuids.add(entry.messageGuid);
|
|
1515
1515
|
return true;
|
|
1516
1516
|
})
|
|
1517
|
-
.filter((entry) =>
|
|
1517
|
+
.filter((entry) => {
|
|
1518
|
+
if ((0, processed_log_1.hasProcessedBlueBubblesMessageGuid)(agentName, entry.messageGuid))
|
|
1519
|
+
return false;
|
|
1520
|
+
return !markCapturedInboundSuperseded(agentName, entry);
|
|
1521
|
+
});
|
|
1518
1522
|
}
|
|
1519
1523
|
function listPendingRecoveryEntries(agentName) {
|
|
1520
1524
|
const pendingByGuid = new Map();
|
|
@@ -1546,6 +1550,67 @@ function parseTimestampMs(value) {
|
|
|
1546
1550
|
const parsed = Date.parse(value);
|
|
1547
1551
|
return Number.isFinite(parsed) ? parsed : null;
|
|
1548
1552
|
}
|
|
1553
|
+
function eventRecordedAtMs(event) {
|
|
1554
|
+
return parseTimestampMs(event.time?.recordedAt ?? undefined);
|
|
1555
|
+
}
|
|
1556
|
+
function blueBubblesSessionPathsForKey(agentName, sessionKey) {
|
|
1557
|
+
const sessionsRoot = path.join((0, identity_1.getAgentRoot)(agentName), "state", "sessions");
|
|
1558
|
+
const fileName = `${(0, config_1.sanitizeKey)(sessionKey)}.json`;
|
|
1559
|
+
let friendDirs;
|
|
1560
|
+
try {
|
|
1561
|
+
friendDirs = fs.readdirSync(sessionsRoot, { withFileTypes: true });
|
|
1562
|
+
}
|
|
1563
|
+
catch {
|
|
1564
|
+
return [];
|
|
1565
|
+
}
|
|
1566
|
+
return friendDirs
|
|
1567
|
+
.filter((entry) => entry.isDirectory())
|
|
1568
|
+
.map((entry) => path.join(sessionsRoot, entry.name, "bluebubbles", fileName))
|
|
1569
|
+
.filter((filePath) => {
|
|
1570
|
+
try {
|
|
1571
|
+
return fs.statSync(filePath).isFile();
|
|
1572
|
+
}
|
|
1573
|
+
catch {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
function hasNewerBlueBubblesSessionActivity(agentName, sessionKey, recordedAt) {
|
|
1579
|
+
const recordedAtMs = parseTimestampMs(recordedAt);
|
|
1580
|
+
if (recordedAtMs === null)
|
|
1581
|
+
return false;
|
|
1582
|
+
for (const sessionFilePath of blueBubblesSessionPathsForKey(agentName, sessionKey)) {
|
|
1583
|
+
const session = (0, context_1.loadSession)(sessionFilePath);
|
|
1584
|
+
for (const event of session?.events ?? []) {
|
|
1585
|
+
if (event.role === "system")
|
|
1586
|
+
continue;
|
|
1587
|
+
const nextRecordedAtMs = eventRecordedAtMs(event);
|
|
1588
|
+
if (nextRecordedAtMs !== null && nextRecordedAtMs > recordedAtMs) {
|
|
1589
|
+
return true;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return false;
|
|
1594
|
+
}
|
|
1595
|
+
function markCapturedInboundSuperseded(agentName, entry) {
|
|
1596
|
+
if (!entry.messageGuid.trim())
|
|
1597
|
+
return false;
|
|
1598
|
+
if (!hasNewerBlueBubblesSessionActivity(agentName, entry.sessionKey, entry.recordedAt))
|
|
1599
|
+
return false;
|
|
1600
|
+
(0, processed_log_1.recordProcessedBlueBubblesMessage)(agentName, inboundEntryToRecoveryEvent(entry), entry.source, "recovery-superseded");
|
|
1601
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1602
|
+
component: "senses",
|
|
1603
|
+
event: "senses.bluebubbles_recovery_skip",
|
|
1604
|
+
message: "skipped stale captured bluebubbles recovery because the session already advanced",
|
|
1605
|
+
meta: {
|
|
1606
|
+
messageGuid: entry.messageGuid,
|
|
1607
|
+
sessionKey: entry.sessionKey,
|
|
1608
|
+
source: entry.source,
|
|
1609
|
+
dedupeReason: "session_superseded",
|
|
1610
|
+
},
|
|
1611
|
+
});
|
|
1612
|
+
return true;
|
|
1613
|
+
}
|
|
1549
1614
|
function resolveBlueBubblesCatchUpSince(previousState, nowMs = Date.now()) {
|
|
1550
1615
|
if (previousState.upstreamStatus === "error") {
|
|
1551
1616
|
return nowMs - BLUEBUBBLES_RECOVERY_CATCHUP_LOOKBACK_MS;
|
|
@@ -183,13 +183,10 @@ function contentToText(content) {
|
|
|
183
183
|
.join("\n");
|
|
184
184
|
return text.trim();
|
|
185
185
|
}
|
|
186
|
-
function
|
|
187
|
-
const
|
|
188
|
-
if (!lastAssistant)
|
|
189
|
-
return "no prior checkpoint recorded";
|
|
190
|
-
const assistantText = contentToText(lastAssistant.content);
|
|
186
|
+
function checkpointTextFromAssistantContent(content) {
|
|
187
|
+
const assistantText = contentToText(content);
|
|
191
188
|
if (!assistantText)
|
|
192
|
-
return
|
|
189
|
+
return null;
|
|
193
190
|
const cleanedLines = assistantText
|
|
194
191
|
.split("\n")
|
|
195
192
|
.map((line) => line.replace(/<\/?think>/gi, "").trim())
|
|
@@ -198,14 +195,102 @@ function deriveResumeCheckpoint(messages) {
|
|
|
198
195
|
.find((line) => /^checkpoint\s*:/i.test(line));
|
|
199
196
|
if (explicitCheckpoint) {
|
|
200
197
|
const parsed = explicitCheckpoint.replace(/^checkpoint\s*:\s*/i, "").trim();
|
|
201
|
-
return parsed ||
|
|
198
|
+
return parsed || null;
|
|
202
199
|
}
|
|
203
200
|
const firstLine = cleanedLines[0];
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
return firstLine ?? null;
|
|
202
|
+
}
|
|
203
|
+
function truncateCheckpointText(text) {
|
|
204
|
+
if (text.length <= 220)
|
|
205
|
+
return text;
|
|
206
|
+
return `${text.slice(0, 217)}...`;
|
|
207
|
+
}
|
|
208
|
+
function parseToolArguments(argumentsValue) {
|
|
209
|
+
if (!argumentsValue)
|
|
210
|
+
return {};
|
|
211
|
+
try {
|
|
212
|
+
const parsed = JSON.parse(argumentsValue);
|
|
213
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
214
|
+
? parsed
|
|
215
|
+
: {};
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function toolArgumentText(args, keys) {
|
|
222
|
+
for (const key of keys) {
|
|
223
|
+
const value = args[key];
|
|
224
|
+
if (typeof value === "string" && value.trim()) {
|
|
225
|
+
return value.trim();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return "";
|
|
229
|
+
}
|
|
230
|
+
function summarizeToolAction(name, argumentsValue) {
|
|
231
|
+
if (!name)
|
|
232
|
+
return null;
|
|
233
|
+
const args = parseToolArguments(argumentsValue);
|
|
234
|
+
if (name === "surface") {
|
|
235
|
+
const message = toolArgumentText(args, ["message", "text", "content"]);
|
|
236
|
+
return message ? `surfaced: ${message}` : null;
|
|
237
|
+
}
|
|
238
|
+
if (name === "ponder") {
|
|
239
|
+
const thought = toolArgumentText(args, ["summary", "question", "topic", "prompt"]);
|
|
240
|
+
return thought ? `pondered: ${thought}` : null;
|
|
241
|
+
}
|
|
242
|
+
if (name === "diary_write") {
|
|
243
|
+
const note = toolArgumentText(args, ["text", "content", "note", "entry"]);
|
|
244
|
+
return note ? `diary: ${note}` : null;
|
|
245
|
+
}
|
|
246
|
+
if (name === "let_go") {
|
|
247
|
+
const reason = toolArgumentText(args, ["reason", "note", "status"]);
|
|
248
|
+
return reason ? `let go: ${reason}` : null;
|
|
249
|
+
}
|
|
250
|
+
if (name === "rest") {
|
|
251
|
+
const note = toolArgumentText(args, ["note", "status"]);
|
|
252
|
+
return note ? `rested: ${note}` : null;
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
function extractToolFunction(toolCall) {
|
|
257
|
+
if (!toolCall || typeof toolCall !== "object" || !("function" in toolCall))
|
|
258
|
+
return null;
|
|
259
|
+
const maybeFunction = toolCall.function;
|
|
260
|
+
if (!maybeFunction || typeof maybeFunction !== "object")
|
|
261
|
+
return null;
|
|
262
|
+
const name = "name" in maybeFunction && typeof maybeFunction.name === "string"
|
|
263
|
+
? maybeFunction.name
|
|
264
|
+
: undefined;
|
|
265
|
+
const argumentsValue = "arguments" in maybeFunction && typeof maybeFunction.arguments === "string"
|
|
266
|
+
? maybeFunction.arguments
|
|
267
|
+
: undefined;
|
|
268
|
+
return { name, arguments: argumentsValue };
|
|
269
|
+
}
|
|
270
|
+
function checkpointTextFromAssistantToolCalls(message) {
|
|
271
|
+
if (message.role !== "assistant" || !Array.isArray(message.tool_calls))
|
|
272
|
+
return null;
|
|
273
|
+
for (let i = message.tool_calls.length - 1; i >= 0; i--) {
|
|
274
|
+
const toolFunction = extractToolFunction(message.tool_calls[i]);
|
|
275
|
+
const summary = summarizeToolAction(toolFunction?.name, toolFunction?.arguments);
|
|
276
|
+
if (summary)
|
|
277
|
+
return summary;
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
function deriveResumeCheckpoint(messages) {
|
|
282
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
283
|
+
const message = messages[i];
|
|
284
|
+
if (message.role !== "assistant")
|
|
285
|
+
continue;
|
|
286
|
+
const textCheckpoint = checkpointTextFromAssistantContent(message.content);
|
|
287
|
+
if (textCheckpoint)
|
|
288
|
+
return truncateCheckpointText(textCheckpoint);
|
|
289
|
+
const toolCheckpoint = checkpointTextFromAssistantToolCalls(message);
|
|
290
|
+
if (toolCheckpoint)
|
|
291
|
+
return truncateCheckpointText(toolCheckpoint);
|
|
292
|
+
}
|
|
293
|
+
return "no prior checkpoint recorded";
|
|
209
294
|
}
|
|
210
295
|
function extractAssistantPreview(messages, maxLength = 120) {
|
|
211
296
|
const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
|