@yeaft/webchat-agent 1.0.198 → 1.0.199
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/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +83 -83
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +1 -1
- package/yeaft/conversation/persist.js +180 -36
- package/yeaft/engine.js +3 -1
- package/yeaft/tools/types.js +1 -1
- package/yeaft/web-bridge.js +19 -10
|
Binary file
|
package/package.json
CHANGED
|
@@ -57,6 +57,10 @@ let DEFAULT_RECENT_TURNS = 20;
|
|
|
57
57
|
const RECENT_SESSION_SCAN_BASE_CAP = 64;
|
|
58
58
|
const RECENT_SESSION_SCAN_PER_TURN_CAP = 4;
|
|
59
59
|
const RECENT_SESSION_SCAN_MAX_CAP = 256;
|
|
60
|
+
// A normal Engine loop executes at most 30 tools, but parallel VPs can append
|
|
61
|
+
// many rows between a call and its result. One extra normal delta page keeps the
|
|
62
|
+
// scan bounded while leaving enough room to close a legitimate interleaved arc.
|
|
63
|
+
const DELTA_TOOL_PAIR_EXTENSION_CAP = 500;
|
|
60
64
|
|
|
61
65
|
|
|
62
66
|
const SEGMENT_INDEX_FILE = 'index.json';
|
|
@@ -235,6 +239,90 @@ function recentSessionScanCap(turnsLimit) {
|
|
|
235
239
|
return Math.min(RECENT_SESSION_SCAN_MAX_CAP, RECENT_SESSION_SCAN_BASE_CAP + turns * RECENT_SESSION_SCAN_PER_TURN_CAP);
|
|
236
240
|
}
|
|
237
241
|
|
|
242
|
+
function askUserToolIdentity(row, toolCallId) {
|
|
243
|
+
if (!row || typeof toolCallId !== 'string' || !toolCallId) return null;
|
|
244
|
+
return [
|
|
245
|
+
row.sessionId || '',
|
|
246
|
+
row.speakerVpId || '',
|
|
247
|
+
row.turnId || '',
|
|
248
|
+
row.threadId || 'main',
|
|
249
|
+
toolCallId,
|
|
250
|
+
].join('\u0000');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function parseAskUserResult(toolCall, toolResult) {
|
|
254
|
+
if (!toolCall || toolCall.name !== 'AskUser' || typeof toolCall.id !== 'string' || !toolCall.id
|
|
255
|
+
|| !toolResult || toolResult.isError) return null;
|
|
256
|
+
let payload = toolResult.content;
|
|
257
|
+
if (typeof payload === 'string') {
|
|
258
|
+
try { payload = JSON.parse(payload); } catch { return null; }
|
|
259
|
+
}
|
|
260
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
261
|
+
|
|
262
|
+
const question = typeof toolCall.input?.question === 'string'
|
|
263
|
+
? toolCall.input.question
|
|
264
|
+
: (typeof payload.question === 'string' ? payload.question : '');
|
|
265
|
+
const options = Array.isArray(toolCall.input?.options)
|
|
266
|
+
? toolCall.input.options.filter(option => typeof option === 'string')
|
|
267
|
+
: [];
|
|
268
|
+
if (payload.timedOut === true) {
|
|
269
|
+
return {
|
|
270
|
+
toolCallId: toolCall.id,
|
|
271
|
+
status: 'expired',
|
|
272
|
+
question,
|
|
273
|
+
options,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (!payload.answers || typeof payload.answers !== 'object' || Array.isArray(payload.answers)) return null;
|
|
277
|
+
return {
|
|
278
|
+
toolCallId: toolCall.id,
|
|
279
|
+
status: 'answered',
|
|
280
|
+
question,
|
|
281
|
+
options,
|
|
282
|
+
answers: payload.answers,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function projectVisibleSessionMessages(messages) {
|
|
287
|
+
const rows = Array.isArray(messages) ? messages : [];
|
|
288
|
+
const toolResults = new Map();
|
|
289
|
+
for (const row of rows) {
|
|
290
|
+
if (row?.role !== 'tool') continue;
|
|
291
|
+
const identity = askUserToolIdentity(row, row.toolCallId);
|
|
292
|
+
if (identity) toolResults.set(identity, row);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const visible = [];
|
|
296
|
+
for (const row of rows) {
|
|
297
|
+
if (!row || (row.role !== 'user' && row.role !== 'assistant')) continue;
|
|
298
|
+
if (row.role !== 'assistant' || !Array.isArray(row.toolCalls) || row.toolCalls.length === 0) {
|
|
299
|
+
if (row.role === 'assistant' && !row.content && !row.attachments && !row.images
|
|
300
|
+
&& !row.toolSummaryCount && !row.askUserResults) continue;
|
|
301
|
+
visible.push(row);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const askUserResults = [];
|
|
306
|
+
let omittedToolCount = 0;
|
|
307
|
+
for (const toolCall of row.toolCalls) {
|
|
308
|
+
const identity = askUserToolIdentity(row, toolCall?.id);
|
|
309
|
+
const result = parseAskUserResult(toolCall, identity ? toolResults.get(identity) : null);
|
|
310
|
+
if (result) askUserResults.push(result);
|
|
311
|
+
else omittedToolCount += 1;
|
|
312
|
+
}
|
|
313
|
+
const { toolCalls, ...rest } = row;
|
|
314
|
+
const projected = {
|
|
315
|
+
...rest,
|
|
316
|
+
...(omittedToolCount > 0 ? { toolSummaryCount: omittedToolCount } : {}),
|
|
317
|
+
...(askUserResults.length > 0 ? { askUserResults } : {}),
|
|
318
|
+
};
|
|
319
|
+
if (!projected.content && !projected.attachments && !projected.images
|
|
320
|
+
&& !projected.toolSummaryCount && !projected.askUserResults) continue;
|
|
321
|
+
visible.push(projected);
|
|
322
|
+
}
|
|
323
|
+
return visible;
|
|
324
|
+
}
|
|
325
|
+
|
|
238
326
|
// ─── Frontmatter helpers ─────────────────────────────────────
|
|
239
327
|
|
|
240
328
|
/**
|
|
@@ -1346,14 +1434,15 @@ export class ConversationStore {
|
|
|
1346
1434
|
const cutoff = Number.isFinite(beforeSeq) ? beforeSeq : Infinity;
|
|
1347
1435
|
const page = this.#loadRecentSessionWindow(sessionId, turnsLimit, {
|
|
1348
1436
|
beforeSeq: cutoff,
|
|
1349
|
-
roles:
|
|
1350
|
-
stripAssistantToolCalls:
|
|
1437
|
+
roles: null,
|
|
1438
|
+
stripAssistantToolCalls: false,
|
|
1351
1439
|
});
|
|
1352
|
-
|
|
1440
|
+
const messages = projectVisibleSessionMessages(page.messages);
|
|
1441
|
+
if (messages.length === 0) return { messages: [], oldestSeq: null, hasMore: page.truncated };
|
|
1353
1442
|
|
|
1354
|
-
const oldestSeq =
|
|
1443
|
+
const oldestSeq = messages.length ? parseSeqFromId(messages[0].id) : null;
|
|
1355
1444
|
return {
|
|
1356
|
-
messages
|
|
1445
|
+
messages,
|
|
1357
1446
|
oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
|
|
1358
1447
|
hasMore: page.truncated,
|
|
1359
1448
|
};
|
|
@@ -1368,8 +1457,8 @@ export class ConversationStore {
|
|
|
1368
1457
|
* cursor. When no visible rows changed after `afterSeq`, keep the cursor at
|
|
1369
1458
|
* least at `afterSeq` so an empty delta still completes the in-flight sync
|
|
1370
1459
|
* without downgrading the client to a cursor-less loaded state. Hidden rows
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
1460
|
+
* advance the cursor only at pair-safe boundaries; a cursor must never cross
|
|
1461
|
+
* an assistant tool call before all of that call's result rows are included.
|
|
1373
1462
|
*
|
|
1374
1463
|
* @param {string} sessionId
|
|
1375
1464
|
* @param {number|null} afterSeq — exclusive lower bound
|
|
@@ -1382,19 +1471,81 @@ export class ConversationStore {
|
|
|
1382
1471
|
const cutoff = Number.isFinite(afterSeq) && afterSeq >= 0 ? afterSeq : null;
|
|
1383
1472
|
if (cutoff === null) return { messages: [], latestSeq: null };
|
|
1384
1473
|
const after = [];
|
|
1385
|
-
|
|
1474
|
+
const pendingToolResultIds = new Set();
|
|
1475
|
+
const completedBeforeCursor = new Set();
|
|
1476
|
+
const boundaryAssistants = [];
|
|
1477
|
+
let boundaryLookbackRows = 0;
|
|
1478
|
+
for (const previous of this.#iterateSessionRows(sessionId, { beforeSeq: cutoff + 1, desc: true })) {
|
|
1479
|
+
if (!previous || previous.sessionId !== sessionId) continue;
|
|
1480
|
+
boundaryLookbackRows += 1;
|
|
1481
|
+
if (boundaryLookbackRows > DELTA_TOOL_PAIR_EXTENSION_CAP) break;
|
|
1482
|
+
if (isHiddenConversationRow(previous)) continue;
|
|
1483
|
+
if (previous.role === 'tool' && typeof previous.toolCallId === 'string') {
|
|
1484
|
+
completedBeforeCursor.add(previous.toolCallId);
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (previous.role === 'assistant' && Array.isArray(previous.toolCalls) && previous.toolCalls.length > 0) {
|
|
1488
|
+
const pendingIds = previous.toolCalls
|
|
1489
|
+
.map(toolCall => toolCall?.id)
|
|
1490
|
+
.filter(toolCallId => typeof toolCallId === 'string'
|
|
1491
|
+
&& toolCallId
|
|
1492
|
+
&& !completedBeforeCursor.has(toolCallId));
|
|
1493
|
+
if (pendingIds.length > 0) boundaryAssistants.push({ message: previous, pendingIds });
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
if (previous.role === 'user') break;
|
|
1497
|
+
}
|
|
1498
|
+
boundaryAssistants.sort((a, b) => compareMessagesBySeq(a.message, b.message));
|
|
1499
|
+
for (const boundary of boundaryAssistants) {
|
|
1500
|
+
after.push(boundary.message);
|
|
1501
|
+
for (const toolCallId of boundary.pendingIds) pendingToolResultIds.add(toolCallId);
|
|
1502
|
+
}
|
|
1503
|
+
const earliestBoundarySeq = boundaryAssistants.length > 0
|
|
1504
|
+
? parseSeqFromId(boundaryAssistants[0].message.id)
|
|
1505
|
+
: null;
|
|
1506
|
+
let safeCursorSeq = Number.isFinite(earliestBoundarySeq)
|
|
1507
|
+
? Math.max(0, earliestBoundarySeq - 1)
|
|
1508
|
+
: cutoff;
|
|
1509
|
+
let visibleRows = after.length;
|
|
1510
|
+
let extensionRows = 0;
|
|
1386
1511
|
for (const m of this.#iterateSessionRows(sessionId, { afterSeq: cutoff, desc: false })) {
|
|
1387
1512
|
if (!m || m.sessionId !== sessionId) continue;
|
|
1388
1513
|
const seq = parseSeqFromId(m.id);
|
|
1389
|
-
|
|
1390
|
-
if (
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1514
|
+
const hidden = isHiddenConversationRow(m);
|
|
1515
|
+
if (!hidden) {
|
|
1516
|
+
// Keep every outstanding call open across interleaved VP rows. Session
|
|
1517
|
+
// persistence is globally sequenced, so a sibling VP may append visible
|
|
1518
|
+
// messages between an assistant call and that call's result.
|
|
1519
|
+
after.push(m);
|
|
1520
|
+
visibleRows += 1;
|
|
1521
|
+
if (m.role === 'assistant' && Array.isArray(m.toolCalls)) {
|
|
1522
|
+
for (const toolCall of m.toolCalls) {
|
|
1523
|
+
if (typeof toolCall?.id === 'string' && toolCall.id) pendingToolResultIds.add(toolCall.id);
|
|
1524
|
+
}
|
|
1525
|
+
} else if (m.role === 'tool' && typeof m.toolCallId === 'string') {
|
|
1526
|
+
pendingToolResultIds.delete(m.toolCallId);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
if (pendingToolResultIds.size === 0 && Number.isFinite(seq)) safeCursorSeq = seq;
|
|
1530
|
+
if (visibleRows < limit) continue;
|
|
1531
|
+
if (pendingToolResultIds.size === 0) break;
|
|
1532
|
+
extensionRows += 1;
|
|
1533
|
+
if (extensionRows >= DELTA_TOOL_PAIR_EXTENSION_CAP) break;
|
|
1534
|
+
}
|
|
1535
|
+
// If the extension cap stopped inside a malformed arc, return only the
|
|
1536
|
+
// prefix covered by the safe cursor. Returning later rows with an earlier
|
|
1537
|
+
// cursor would make the next delta repeat visible messages unnecessarily.
|
|
1538
|
+
const pairSafeRows = pendingToolResultIds.size === 0
|
|
1539
|
+
? after
|
|
1540
|
+
: after.filter(message => {
|
|
1541
|
+
const seq = parseSeqFromId(message?.id);
|
|
1542
|
+
return Number.isFinite(seq) && seq <= safeCursorSeq;
|
|
1543
|
+
});
|
|
1544
|
+
const sliced = pairSanitize(pairSafeRows);
|
|
1545
|
+
// Never advance past a row the sanitizer had to drop. A malformed or
|
|
1546
|
+
// over-cap tool arc must be retried from its assistant call rather than
|
|
1547
|
+
// turning the following result into a permanent orphan on the next page.
|
|
1548
|
+
return { messages: projectVisibleSessionMessages(sliced), latestSeq: safeCursorSeq };
|
|
1398
1549
|
}
|
|
1399
1550
|
|
|
1400
1551
|
/**
|
|
@@ -1480,31 +1631,33 @@ export class ConversationStore {
|
|
|
1480
1631
|
|
|
1481
1632
|
const beforeTurns = Math.min(10, Math.max(1, Number.isFinite(opts.beforeTurns) ? Math.floor(opts.beforeTurns) : 3));
|
|
1482
1633
|
const afterTurns = Math.min(10, Math.max(1, Number.isFinite(opts.afterTurns) ? Math.floor(opts.afterTurns) : 3));
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1634
|
+
const beforeRaw = this.#loadRecentSessionWindow(sessionId, beforeTurns + 1, {
|
|
1635
|
+
beforeSeq: anchorSeq + 1,
|
|
1636
|
+
roles: null,
|
|
1637
|
+
stripAssistantToolCalls: false,
|
|
1638
|
+
});
|
|
1639
|
+
const messages = beforeRaw.messages.slice();
|
|
1485
1640
|
const seen = new Set(messages.map(message => message?.id).filter(Boolean));
|
|
1486
1641
|
let followingUserTurns = 0;
|
|
1487
1642
|
|
|
1488
1643
|
for (const message of this.#iterateSessionRows(sessionId, { afterSeq: anchorSeq, desc: false })) {
|
|
1489
1644
|
if (!message || message.sessionId !== sessionId || isHiddenConversationRow(message)) continue;
|
|
1490
|
-
if (message.role !== 'user' && message.role !== 'assistant') continue;
|
|
1491
1645
|
if (message.role === 'user') {
|
|
1492
1646
|
followingUserTurns += 1;
|
|
1493
1647
|
if (followingUserTurns > afterTurns) break;
|
|
1494
1648
|
}
|
|
1495
1649
|
if (message.id && seen.has(message.id)) continue;
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
if (projected.id) seen.add(projected.id);
|
|
1499
|
-
messages.push(projected);
|
|
1650
|
+
if (message.id) seen.add(message.id);
|
|
1651
|
+
messages.push(message);
|
|
1500
1652
|
}
|
|
1501
1653
|
|
|
1502
1654
|
messages.sort(compareMessagesBySeq);
|
|
1503
|
-
const
|
|
1655
|
+
const visibleMessages = projectVisibleSessionMessages(messages);
|
|
1656
|
+
const oldestSeq = visibleMessages.length > 0 ? parseSeqFromId(visibleMessages[0].id) : null;
|
|
1504
1657
|
return {
|
|
1505
|
-
messages,
|
|
1658
|
+
messages: visibleMessages,
|
|
1506
1659
|
oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
|
|
1507
|
-
hasMoreBefore:
|
|
1660
|
+
hasMoreBefore: beforeRaw.truncated,
|
|
1508
1661
|
};
|
|
1509
1662
|
}
|
|
1510
1663
|
|
|
@@ -2246,15 +2399,6 @@ export class ConversationStore {
|
|
|
2246
2399
|
return `${start > 0 ? '…' : ''}${text.slice(start, end)}${end < text.length ? '…' : ''}`;
|
|
2247
2400
|
}
|
|
2248
2401
|
|
|
2249
|
-
#projectVisibleMessage(message) {
|
|
2250
|
-
if (!message || (message.role !== 'user' && message.role !== 'assistant')) return null;
|
|
2251
|
-
if (message.role !== 'assistant' || !Array.isArray(message.toolCalls) || message.toolCalls.length === 0) {
|
|
2252
|
-
return message;
|
|
2253
|
-
}
|
|
2254
|
-
const { toolCalls, ...rest } = message;
|
|
2255
|
-
return { ...rest, toolSummaryCount: toolCalls.length };
|
|
2256
|
-
}
|
|
2257
|
-
|
|
2258
2402
|
#readSegmentRows(conversationDir, opts = {}) {
|
|
2259
2403
|
return this.#segmentStoreForConversationDir(conversationDir).readAll(opts);
|
|
2260
2404
|
}
|
package/yeaft/engine.js
CHANGED
|
@@ -1242,7 +1242,9 @@ export class Engine {
|
|
|
1242
1242
|
// Null in non-VP / test contexts — tools tolerate missing slots.
|
|
1243
1243
|
getCurrentTodos: vpCtx?.getCurrentTodos || null,
|
|
1244
1244
|
setCurrentTodos: vpCtx?.setCurrentTodos || null,
|
|
1245
|
-
askUser: vpCtx?.askUser
|
|
1245
|
+
askUser: typeof vpCtx?.askUser === 'function'
|
|
1246
|
+
? input => vpCtx.askUser(input, typeof vpCtx?.currentToolCall === 'function' ? vpCtx.currentToolCall() : null)
|
|
1247
|
+
: null,
|
|
1246
1248
|
// task-707: tool-callable end-turn signal. The engine threads this
|
|
1247
1249
|
// setter when constructing toolCtx so a tool (e.g. route_forward)
|
|
1248
1250
|
// can mark "after this batch, end the turn — do NOT call adapter
|
package/yeaft/tools/types.js
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* @property {number} [contextWindow] — current model's context window in
|
|
33
33
|
* tokens (used by ToolRegistry.execute to cap a single tool result at a
|
|
34
34
|
* fraction of the window so one runaway grep can't blow the wire).
|
|
35
|
-
* @property {(input: {question:string, options?:string[]}) => Promise<object>} [askUser]
|
|
35
|
+
* @property {(input: {question:string, options?:string[]}, toolCall?: {id?:string, name?:string}|null) => Promise<object>} [askUser]
|
|
36
36
|
* — host-provided interactive prompt. Resolves only after the user answers.
|
|
37
37
|
* @property {(reason?: string|object) => void} [requestEndTurn]
|
|
38
38
|
* — tool-callable signal that the current engine turn should end after
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
trimSnapshotForBudget,
|
|
62
62
|
} from './history-compact.js';
|
|
63
63
|
import { persistYeaftAttachments, attachmentsForPersistence, persistedAttachmentPreviewPayload } from './attachments.js';
|
|
64
|
-
import { ConversationStore, parseSeqFromId } from './conversation/persist.js';
|
|
64
|
+
import { ConversationStore, parseSeqFromId, projectVisibleSessionMessages } from './conversation/persist.js';
|
|
65
65
|
import { isHiddenConversationRow } from './conversation/internal-control.js';
|
|
66
66
|
import { imageMetadataForPersistence } from './image-assets.js';
|
|
67
67
|
import { sliceLastNTurns } from './turn-utils.js';
|
|
@@ -94,6 +94,7 @@ const SKILL_RELOAD_INTERVAL_MS = 2_000;
|
|
|
94
94
|
* vpId:string,
|
|
95
95
|
* threadId:string,
|
|
96
96
|
* turnId:string,
|
|
97
|
+
* toolCallId:string,
|
|
97
98
|
* question:string,
|
|
98
99
|
* options:Array<string>,
|
|
99
100
|
* createdAt:number,
|
|
@@ -1188,6 +1189,9 @@ function projectPersistedToHistoryEntry(m) {
|
|
|
1188
1189
|
if (m.clientMessageId) entry.clientMessageId = m.clientMessageId;
|
|
1189
1190
|
if (m.speakerVpId) entry.speakerVpId = m.speakerVpId;
|
|
1190
1191
|
if (m.toolCallId) entry.toolCallId = m.toolCallId;
|
|
1192
|
+
if (Array.isArray(m.askUserResults) && m.askUserResults.length > 0) {
|
|
1193
|
+
entry.askUserResults = m.askUserResults;
|
|
1194
|
+
}
|
|
1191
1195
|
if (Array.isArray(m.toolCalls) && m.toolCalls.length > 0) {
|
|
1192
1196
|
entry.toolCalls = m.toolCalls.map(tc => ({
|
|
1193
1197
|
id: tc.id,
|
|
@@ -1203,7 +1207,7 @@ function projectPersistedToHistoryEntry(m) {
|
|
|
1203
1207
|
else if (m.time) entry.ts = m.time;
|
|
1204
1208
|
if (Array.isArray(m.images) && m.images.length > 0) entry.images = m.images;
|
|
1205
1209
|
if (Array.isArray(m.attachments) && m.attachments.length > 0) entry.attachments = m.attachments;
|
|
1206
|
-
if ((entry.role === 'user' || entry.role === 'assistant') && !entry.content && !entry.attachments && !entry.images && !entry.toolCalls && !entry.toolSummaryCount) return null;
|
|
1210
|
+
if ((entry.role === 'user' || entry.role === 'assistant') && !entry.content && !entry.attachments && !entry.images && !entry.toolCalls && !entry.toolSummaryCount && !entry.askUserResults) return null;
|
|
1207
1211
|
return entry;
|
|
1208
1212
|
}
|
|
1209
1213
|
|
|
@@ -1253,7 +1257,7 @@ function loadVisibleGroupHistoryPage(store, sessionId, limit, beforeSeq = null)
|
|
|
1253
1257
|
return { messages: [], oldestSeq: null, hasMore: false };
|
|
1254
1258
|
}
|
|
1255
1259
|
|
|
1256
|
-
const visible = rows
|
|
1260
|
+
const visible = projectVisibleSessionMessages(rows)
|
|
1257
1261
|
.map(projectPersistedToVisibleHistoryEntry)
|
|
1258
1262
|
.filter(Boolean);
|
|
1259
1263
|
const messages = sliceLastNTurns(visible, limit);
|
|
@@ -1280,7 +1284,7 @@ function ensureYeaftConversationId() {
|
|
|
1280
1284
|
}
|
|
1281
1285
|
|
|
1282
1286
|
function projectVisibleHistoryChunkMessages(messages = []) {
|
|
1283
|
-
return (messages
|
|
1287
|
+
return projectVisibleSessionMessages(messages)
|
|
1284
1288
|
.map(projectPersistedToVisibleHistoryEntry)
|
|
1285
1289
|
.filter(Boolean)
|
|
1286
1290
|
.map(m => ({
|
|
@@ -1296,6 +1300,7 @@ function projectVisibleHistoryChunkMessages(messages = []) {
|
|
|
1296
1300
|
...(Array.isArray(m.attachments) && m.attachments.length > 0 ? { attachments: hydrateHistoryAttachmentPreviews(m.attachments) } : {}),
|
|
1297
1301
|
...(Array.isArray(m.images) && m.images.length > 0 ? { images: m.images } : {}),
|
|
1298
1302
|
...(m.speakerVpId ? { speakerVpId: m.speakerVpId } : {}),
|
|
1303
|
+
...(Array.isArray(m.askUserResults) && m.askUserResults.length > 0 ? { askUserResults: m.askUserResults } : {}),
|
|
1299
1304
|
...(Number.isFinite(m.toolSummaryCount) && m.toolSummaryCount > 0
|
|
1300
1305
|
? { toolSummaryCount: m.toolSummaryCount }
|
|
1301
1306
|
: (Array.isArray(m.toolCalls) && m.toolCalls.length > 0 ? { toolSummaryCount: m.toolCalls.length } : {})),
|
|
@@ -1304,9 +1309,9 @@ function projectVisibleHistoryChunkMessages(messages = []) {
|
|
|
1304
1309
|
|
|
1305
1310
|
function emitHistoryChunk({ sessionId, messages, mode = 'older', oldestSeq = null, hasMore = false, latestSeq = null, afterSeq = null, turns = null, perfTraceId = null }) {
|
|
1306
1311
|
const projectedMessages = projectVisibleHistoryChunkMessages(messages);
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1312
|
+
// Empty deltas still carry the authoritative safe cursor and clear the
|
|
1313
|
+
// browser's syncingAfterSeq fence. Dropping this envelope leaves Session
|
|
1314
|
+
// switching stuck after hidden-only or pair-unsafe rows.
|
|
1310
1315
|
sendToServer({
|
|
1311
1316
|
type: 'yeaft_history_chunk',
|
|
1312
1317
|
conversationId: yeaftConversationId,
|
|
@@ -2719,6 +2724,7 @@ function pendingUserPromptEvent(requestId, pending, extra = {}) {
|
|
|
2719
2724
|
return {
|
|
2720
2725
|
type: 'ask_user_question',
|
|
2721
2726
|
requestId,
|
|
2727
|
+
toolCallId: pending.toolCallId || null,
|
|
2722
2728
|
questions: [{
|
|
2723
2729
|
question: pending.question,
|
|
2724
2730
|
options: pending.options.map(label => ({ label, description: '' })),
|
|
@@ -2758,6 +2764,7 @@ function settlePendingUserPrompt(requestId, pending, { answers = null, timedOut
|
|
|
2758
2764
|
sendSessionEvent({
|
|
2759
2765
|
type: timedOut ? 'ask_user_expired' : 'ask_user_answered',
|
|
2760
2766
|
requestId,
|
|
2767
|
+
toolCallId: pending.toolCallId || null,
|
|
2761
2768
|
...(timedOut ? { expiredAt: Date.now() } : { answers: answers || {} }),
|
|
2762
2769
|
}, {
|
|
2763
2770
|
sessionId: pending.sessionId,
|
|
@@ -5025,7 +5032,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
5025
5032
|
// each write a copy of the user message, and history replay
|
|
5026
5033
|
// would render the user's prompt N times.
|
|
5027
5034
|
userAlreadyPersisted: true,
|
|
5028
|
-
askUser: ({ question, options }) => new Promise((resolve, reject) => {
|
|
5035
|
+
askUser: ({ question, options }, toolCall = null) => new Promise((resolve, reject) => {
|
|
5029
5036
|
const requestId = `ask_${randomUUID()}`;
|
|
5030
5037
|
const signal = vpAbort.signal;
|
|
5031
5038
|
const createdAt = Date.now();
|
|
@@ -5043,6 +5050,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
5043
5050
|
vpId,
|
|
5044
5051
|
threadId,
|
|
5045
5052
|
turnId,
|
|
5053
|
+
toolCallId: typeof toolCall?.id === 'string' ? toolCall.id : '',
|
|
5046
5054
|
question,
|
|
5047
5055
|
options: Array.isArray(options) ? options.filter(label => typeof label === 'string') : [],
|
|
5048
5056
|
createdAt,
|
|
@@ -6110,6 +6118,7 @@ export function handleYeaftAskUserAnswer(msg) {
|
|
|
6110
6118
|
if (msg.vpId && msg.vpId !== pending.vpId) return false;
|
|
6111
6119
|
if (msg.turnId && msg.turnId !== pending.turnId) return false;
|
|
6112
6120
|
if (msg.threadId && msg.threadId !== pending.threadId) return false;
|
|
6121
|
+
if (msg.toolCallId && msg.toolCallId !== pending.toolCallId) return false;
|
|
6113
6122
|
|
|
6114
6123
|
return settlePendingUserPrompt(requestId, pending, { answers: msg.answers || {} });
|
|
6115
6124
|
}
|
|
@@ -7066,10 +7075,10 @@ export const __testHooks = {
|
|
|
7066
7075
|
vpAborts.clear();
|
|
7067
7076
|
vpInboxes.clear();
|
|
7068
7077
|
},
|
|
7069
|
-
seedPendingUserPrompt({ requestId = 'ask-test', sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test', question = 'Continue?', options = [], createdAt = Date.now(), expiresAt = Date.now() + ASK_USER_TIMEOUT_MS } = {}) {
|
|
7078
|
+
seedPendingUserPrompt({ requestId = 'ask-test', sessionId = 'session-test', vpId = 'vp-test', threadId = 'main', turnId = 'turn-test', toolCallId = 'call-test', question = 'Continue?', options = [], createdAt = Date.now(), expiresAt = Date.now() + ASK_USER_TIMEOUT_MS } = {}) {
|
|
7070
7079
|
let resolved;
|
|
7071
7080
|
const promise = new Promise(resolve => { resolved = resolve; });
|
|
7072
|
-
pendingUserPrompts.set(requestId, { resolve: resolved, sessionId, vpId, threadId, turnId, question, options, createdAt, expiresAt, timer: null });
|
|
7081
|
+
pendingUserPrompts.set(requestId, { resolve: resolved, sessionId, vpId, threadId, turnId, toolCallId, question, options, createdAt, expiresAt, timer: null });
|
|
7073
7082
|
return promise;
|
|
7074
7083
|
},
|
|
7075
7084
|
replayPendingUserPrompts,
|