viveworker 0.5.3 → 0.6.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/README.md +178 -27
- package/package.json +7 -2
- package/scripts/a2a-cli.mjs +11 -6
- package/scripts/share-cli.mjs +576 -0
- package/scripts/viveworker-bridge.mjs +305 -14
- package/scripts/viveworker.mjs +771 -130
- package/web/app.css +100 -0
- package/web/app.js +184 -5
- package/web/i18n.js +204 -14
|
@@ -29,7 +29,7 @@ const sessionCookieName = "viveworker_session";
|
|
|
29
29
|
const deviceCookieName = "viveworker_device";
|
|
30
30
|
const historyKinds = new Set(["completion", "assistant_final", "plan_ready", "approval", "plan", "choice", "info", "moltbook_reply", "moltbook_draft", "thread_share", "a2a_task", "a2a_task_result"]);
|
|
31
31
|
const timelineMessageKinds = new Set(["user_message", "assistant_commentary", "assistant_final"]);
|
|
32
|
-
const timelineKinds = new Set([...timelineMessageKinds, "approval", "plan", "choice", "plan_ready", "file_event", "moltbook_reply", "moltbook_draft", "thread_share", "a2a_task", "a2a_task_result"]);
|
|
32
|
+
const timelineKinds = new Set([...timelineMessageKinds, "ambient_suggestions", "approval", "plan", "choice", "plan_ready", "file_event", "moltbook_reply", "moltbook_draft", "thread_share", "a2a_task", "a2a_task_result"]);
|
|
33
33
|
const SQLITE_COMPLETION_BATCH_SIZE = 200;
|
|
34
34
|
const DEFAULT_DEVICE_TRUST_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
35
35
|
const MAX_PAIRED_DEVICES = 200;
|
|
@@ -161,6 +161,7 @@ runtime.recentHistoryItems = initialHistoryItems;
|
|
|
161
161
|
runtime.recentTimelineEntries = initialTimelineEntries;
|
|
162
162
|
state.recentHistoryItems = initialHistoryItems;
|
|
163
163
|
state.recentTimelineEntries = initialTimelineEntries;
|
|
164
|
+
const backfilledAmbientSuggestionsStateChanged = backfillAmbientSuggestionsState({ config, runtime, state });
|
|
164
165
|
const migratedRecentCodeEventsStateChanged = migrateRecentCodeEventsState({ config, runtime, state });
|
|
165
166
|
const restoredTimelineImagePathsStateChanged = await backfillPersistedTimelineImagePaths({ config, runtime, state });
|
|
166
167
|
const backfilledMoltbookInboxChanged = await backfillMoltbookInboxHistory({ config, runtime, state });
|
|
@@ -291,6 +292,8 @@ function kindTitle(locale, kind) {
|
|
|
291
292
|
return t(locale, "server.title.assistantCommentary");
|
|
292
293
|
case "assistant_final":
|
|
293
294
|
return t(locale, "server.title.assistantFinal");
|
|
295
|
+
case "ambient_suggestions":
|
|
296
|
+
return t(locale, "server.title.ambientSuggestions");
|
|
294
297
|
case "approval":
|
|
295
298
|
return t(locale, "server.title.approval");
|
|
296
299
|
case "plan":
|
|
@@ -319,6 +322,117 @@ function kindTitle(locale, kind) {
|
|
|
319
322
|
}
|
|
320
323
|
}
|
|
321
324
|
|
|
325
|
+
function normalizeAmbientSuggestion(raw) {
|
|
326
|
+
if (!isPlainObject(raw)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const title = cleanText(raw.title ?? "");
|
|
331
|
+
const prompt = normalizeLongText(raw.prompt ?? "");
|
|
332
|
+
if (!title || !prompt) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const threadAction = isPlainObject(raw.threadAction)
|
|
337
|
+
? {
|
|
338
|
+
...(cleanText(raw.threadAction.type ?? "") ? { type: cleanText(raw.threadAction.type) } : {}),
|
|
339
|
+
}
|
|
340
|
+
: null;
|
|
341
|
+
const appIds = Array.isArray(raw.appIds)
|
|
342
|
+
? raw.appIds.map((value) => cleanText(value ?? "")).filter(Boolean)
|
|
343
|
+
: [];
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
...(cleanText(raw.id ?? "") ? { id: cleanText(raw.id) } : {}),
|
|
347
|
+
title,
|
|
348
|
+
prompt,
|
|
349
|
+
...(cleanText(raw.description ?? "") ? { description: cleanText(raw.description) } : {}),
|
|
350
|
+
...(threadAction && threadAction.type ? { threadAction } : {}),
|
|
351
|
+
...(appIds.length > 0 ? { appIds } : {}),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function normalizeAmbientSuggestions(raw) {
|
|
356
|
+
if (!Array.isArray(raw)) {
|
|
357
|
+
return [];
|
|
358
|
+
}
|
|
359
|
+
return raw.map((entry) => normalizeAmbientSuggestion(entry)).filter(Boolean);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function parseAmbientSuggestionsEnvelope(value) {
|
|
363
|
+
const normalized = cleanText(value ?? "");
|
|
364
|
+
if (!normalized) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let payload;
|
|
369
|
+
try {
|
|
370
|
+
payload = JSON.parse(normalized);
|
|
371
|
+
} catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (!isPlainObject(payload)) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const hasSuggestionsField = Array.isArray(payload.suggestions);
|
|
380
|
+
const hasExcludeField = Array.isArray(payload.exclude);
|
|
381
|
+
if (!hasSuggestionsField && !hasExcludeField) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const suggestions = hasSuggestionsField ? normalizeAmbientSuggestions(payload.suggestions) : [];
|
|
386
|
+
if (suggestions.length > 0) {
|
|
387
|
+
return { type: "suggestions", suggestions };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
type: "metadata",
|
|
392
|
+
hasSuggestionsField,
|
|
393
|
+
hasExcludeField,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function parseAmbientSuggestionsPayload(value) {
|
|
398
|
+
const envelope = parseAmbientSuggestionsEnvelope(value);
|
|
399
|
+
return envelope?.type === "suggestions" ? envelope.suggestions : null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function isAmbientSuggestionsMetadataPayload(value) {
|
|
403
|
+
return parseAmbientSuggestionsEnvelope(value) !== null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function ambientSuggestionsSummary(locale, suggestions) {
|
|
407
|
+
const count = Math.max(0, Array.isArray(suggestions) ? suggestions.length : 0);
|
|
408
|
+
if (count <= 0) {
|
|
409
|
+
return "";
|
|
410
|
+
}
|
|
411
|
+
const firstTitle = cleanText(suggestions[0]?.title || "");
|
|
412
|
+
return t(locale, "summary.ambientSuggestions", {
|
|
413
|
+
count,
|
|
414
|
+
firstTitle,
|
|
415
|
+
more: Math.max(0, count - 1),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function ambientSuggestionsMessageText(locale, suggestions) {
|
|
420
|
+
if (!Array.isArray(suggestions) || suggestions.length === 0) {
|
|
421
|
+
return "";
|
|
422
|
+
}
|
|
423
|
+
return [
|
|
424
|
+
kindTitle(locale, "ambient_suggestions"),
|
|
425
|
+
"",
|
|
426
|
+
...suggestions.flatMap((suggestion, index) => {
|
|
427
|
+
const description = cleanText(suggestion.description || "");
|
|
428
|
+
return [
|
|
429
|
+
`${index + 1}. ${cleanText(suggestion.title || "")}`,
|
|
430
|
+
...(description ? [description] : []),
|
|
431
|
+
];
|
|
432
|
+
}),
|
|
433
|
+
].join("\n");
|
|
434
|
+
}
|
|
435
|
+
|
|
322
436
|
function looksLikeGeneratedThreadTitle(value) {
|
|
323
437
|
const normalized = cleanText(value || "");
|
|
324
438
|
if (!normalized.includes("|")) {
|
|
@@ -2250,6 +2364,8 @@ function timelineKindSortPriority(kind) {
|
|
|
2250
2364
|
case "plan_ready":
|
|
2251
2365
|
case "choice":
|
|
2252
2366
|
return 60;
|
|
2367
|
+
case "ambient_suggestions":
|
|
2368
|
+
return 52;
|
|
2253
2369
|
case "file_event":
|
|
2254
2370
|
return 45;
|
|
2255
2371
|
case "assistant_final":
|
|
@@ -2281,6 +2397,10 @@ function normalizeTimelineEntry(raw) {
|
|
|
2281
2397
|
const diffText = normalizeTimelineDiffText(raw.diffText ?? "");
|
|
2282
2398
|
const diffSource = normalizeTimelineDiffSource(raw.diffSource ?? "");
|
|
2283
2399
|
const diffCounts = diffLineCounts(diffText);
|
|
2400
|
+
const suggestions = normalizeAmbientSuggestions(raw.suggestions ?? []);
|
|
2401
|
+
if (kind === "ambient_suggestions" && suggestions.length === 0) {
|
|
2402
|
+
return null;
|
|
2403
|
+
}
|
|
2284
2404
|
const summary =
|
|
2285
2405
|
normalizeNotificationText(raw.summary ?? "") ||
|
|
2286
2406
|
formatNotificationBody(messageText, 180) ||
|
|
@@ -2351,6 +2471,7 @@ function normalizeTimelineEntry(raw) {
|
|
|
2351
2471
|
// A2A task-specific fields
|
|
2352
2472
|
...(raw.instruction != null ? { instruction: cleanText(raw.instruction) } : {}),
|
|
2353
2473
|
...(raw.taskStatus != null ? { taskStatus: cleanText(raw.taskStatus) } : {}),
|
|
2474
|
+
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
2354
2475
|
};
|
|
2355
2476
|
}
|
|
2356
2477
|
|
|
@@ -2525,6 +2646,9 @@ function historyItemFromEvent(event) {
|
|
|
2525
2646
|
|
|
2526
2647
|
const kind = event.kind === "task_complete" ? "completion" : "plan_ready";
|
|
2527
2648
|
const messageText = normalizeLongText(event.detailText || event.message || "");
|
|
2649
|
+
if (kind === "completion" && isAmbientSuggestionsMetadataPayload(messageText)) {
|
|
2650
|
+
return null;
|
|
2651
|
+
}
|
|
2528
2652
|
return normalizeHistoryItem({
|
|
2529
2653
|
stableId: event.id,
|
|
2530
2654
|
kind,
|
|
@@ -2558,6 +2682,74 @@ function recordHistoryItem({ config, runtime, state, item }) {
|
|
|
2558
2682
|
return changed;
|
|
2559
2683
|
}
|
|
2560
2684
|
|
|
2685
|
+
function backfillAmbientSuggestionsState({ config, runtime, state }) {
|
|
2686
|
+
const nextTimelineEntries = normalizeTimelineEntries(
|
|
2687
|
+
(runtime.recentTimelineEntries ?? []).map((entry) => {
|
|
2688
|
+
const entryKind = cleanText(entry?.kind || "");
|
|
2689
|
+
if (entryKind === "ambient_suggestions") {
|
|
2690
|
+
const suggestions = normalizeAmbientSuggestions(entry?.suggestions ?? []);
|
|
2691
|
+
if (suggestions.length === 0) {
|
|
2692
|
+
return null;
|
|
2693
|
+
}
|
|
2694
|
+
return {
|
|
2695
|
+
...entry,
|
|
2696
|
+
title: kindTitle(DEFAULT_LOCALE, "ambient_suggestions"),
|
|
2697
|
+
summary: ambientSuggestionsSummary(DEFAULT_LOCALE, suggestions),
|
|
2698
|
+
messageText: ambientSuggestionsMessageText(DEFAULT_LOCALE, suggestions),
|
|
2699
|
+
suggestions,
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
if (entryKind !== "assistant_final") {
|
|
2703
|
+
return entry;
|
|
2704
|
+
}
|
|
2705
|
+
const envelope = parseAmbientSuggestionsEnvelope(entry?.messageText ?? "");
|
|
2706
|
+
if (!envelope) {
|
|
2707
|
+
return entry;
|
|
2708
|
+
}
|
|
2709
|
+
if (envelope.type !== "suggestions") {
|
|
2710
|
+
return null;
|
|
2711
|
+
}
|
|
2712
|
+
const suggestions = envelope.suggestions;
|
|
2713
|
+
return {
|
|
2714
|
+
...entry,
|
|
2715
|
+
kind: "ambient_suggestions",
|
|
2716
|
+
title: kindTitle(DEFAULT_LOCALE, "ambient_suggestions"),
|
|
2717
|
+
summary: ambientSuggestionsSummary(DEFAULT_LOCALE, suggestions),
|
|
2718
|
+
messageText: ambientSuggestionsMessageText(DEFAULT_LOCALE, suggestions),
|
|
2719
|
+
suggestions,
|
|
2720
|
+
};
|
|
2721
|
+
}),
|
|
2722
|
+
config.maxTimelineEntries
|
|
2723
|
+
);
|
|
2724
|
+
|
|
2725
|
+
const nextHistoryItems = normalizeHistoryItems(
|
|
2726
|
+
(runtime.recentHistoryItems ?? []).filter((item) => {
|
|
2727
|
+
const kind = cleanText(item?.kind || "");
|
|
2728
|
+
if (kind !== "assistant_final" && kind !== "completion") {
|
|
2729
|
+
return true;
|
|
2730
|
+
}
|
|
2731
|
+
return !isAmbientSuggestionsMetadataPayload(item?.messageText ?? "");
|
|
2732
|
+
}),
|
|
2733
|
+
config.maxHistoryItems
|
|
2734
|
+
);
|
|
2735
|
+
|
|
2736
|
+
const timelineChanged =
|
|
2737
|
+
JSON.stringify(nextTimelineEntries) !== JSON.stringify(runtime.recentTimelineEntries ?? []);
|
|
2738
|
+
const historyChanged =
|
|
2739
|
+
JSON.stringify(nextHistoryItems) !== JSON.stringify(runtime.recentHistoryItems ?? []);
|
|
2740
|
+
|
|
2741
|
+
if (timelineChanged) {
|
|
2742
|
+
runtime.recentTimelineEntries = nextTimelineEntries;
|
|
2743
|
+
state.recentTimelineEntries = nextTimelineEntries;
|
|
2744
|
+
}
|
|
2745
|
+
if (historyChanged) {
|
|
2746
|
+
runtime.recentHistoryItems = nextHistoryItems;
|
|
2747
|
+
state.recentHistoryItems = nextHistoryItems;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
return timelineChanged || historyChanged;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2561
2753
|
// Flip a previously-recorded history item's readOnly flag without disturbing
|
|
2562
2754
|
// its ordering or other fields. Used by moltbook_reply / moltbook_draft flows
|
|
2563
2755
|
// to transition pending → resolved so the entry starts matching the completed
|
|
@@ -4140,6 +4332,20 @@ function buildSqliteTimelineEntry({ row, config, runtime }) {
|
|
|
4140
4332
|
if (!messageText) {
|
|
4141
4333
|
return null;
|
|
4142
4334
|
}
|
|
4335
|
+
const ambientSuggestionsEnvelope = kind === "assistant_final" ? parseAmbientSuggestionsEnvelope(messageText) : null;
|
|
4336
|
+
if (ambientSuggestionsEnvelope?.type === "metadata") {
|
|
4337
|
+
return null;
|
|
4338
|
+
}
|
|
4339
|
+
const ambientSuggestions = ambientSuggestionsEnvelope?.type === "suggestions"
|
|
4340
|
+
? ambientSuggestionsEnvelope.suggestions
|
|
4341
|
+
: null;
|
|
4342
|
+
const entryKind = ambientSuggestions ? "ambient_suggestions" : kind;
|
|
4343
|
+
const entryMessageText = ambientSuggestions
|
|
4344
|
+
? ambientSuggestionsMessageText(config.defaultLocale, ambientSuggestions)
|
|
4345
|
+
: messageText;
|
|
4346
|
+
const entrySummary = ambientSuggestions
|
|
4347
|
+
? ambientSuggestionsSummary(config.defaultLocale, ambientSuggestions)
|
|
4348
|
+
: formatNotificationBody(messageText, 180) || messageText;
|
|
4143
4349
|
|
|
4144
4350
|
const createdAtMs = Math.max(0, Number(row.ts) || 0) * 1000 || Date.now();
|
|
4145
4351
|
const threadLabel = getNativeThreadLabel({
|
|
@@ -4147,17 +4353,18 @@ function buildSqliteTimelineEntry({ row, config, runtime }) {
|
|
|
4147
4353
|
conversationId: threadId,
|
|
4148
4354
|
cwd: "",
|
|
4149
4355
|
});
|
|
4150
|
-
const stableId = messageTimelineStableId(
|
|
4356
|
+
const stableId = messageTimelineStableId(entryKind, threadId, item.id || row.id, entryMessageText, createdAtMs);
|
|
4151
4357
|
|
|
4152
4358
|
return normalizeTimelineEntry({
|
|
4153
4359
|
stableId,
|
|
4154
4360
|
token: historyToken(stableId),
|
|
4155
|
-
kind,
|
|
4361
|
+
kind: entryKind,
|
|
4156
4362
|
threadId,
|
|
4157
4363
|
threadLabel,
|
|
4158
|
-
title: threadLabel || kindTitle(config.defaultLocale,
|
|
4159
|
-
summary:
|
|
4160
|
-
messageText,
|
|
4364
|
+
title: ambientSuggestions ? kindTitle(config.defaultLocale, entryKind) : threadLabel || kindTitle(config.defaultLocale, entryKind),
|
|
4365
|
+
summary: entrySummary,
|
|
4366
|
+
messageText: entryMessageText,
|
|
4367
|
+
...(ambientSuggestions ? { suggestions: ambientSuggestions } : {}),
|
|
4161
4368
|
createdAtMs,
|
|
4162
4369
|
readOnly: true,
|
|
4163
4370
|
});
|
|
@@ -4609,7 +4816,11 @@ async function processScannedEvent({ config, runtime, state, event }) {
|
|
|
4609
4816
|
item: historyItemFromEvent(event),
|
|
4610
4817
|
}) || dirty;
|
|
4611
4818
|
|
|
4612
|
-
|
|
4819
|
+
const isAmbientSuggestionsCompletion =
|
|
4820
|
+
event.kind === "task_complete" &&
|
|
4821
|
+
isAmbientSuggestionsMetadataPayload(normalizeLongText(event.detailText || event.message || ""));
|
|
4822
|
+
|
|
4823
|
+
if (event.kind === "task_complete" && !isAmbientSuggestionsCompletion) {
|
|
4613
4824
|
dirty =
|
|
4614
4825
|
(await deliverWebPushItem({
|
|
4615
4826
|
config,
|
|
@@ -4626,7 +4837,7 @@ async function processScannedEvent({ config, runtime, state, event }) {
|
|
|
4626
4837
|
})) || dirty;
|
|
4627
4838
|
}
|
|
4628
4839
|
|
|
4629
|
-
if (config.enableNtfy) {
|
|
4840
|
+
if (config.enableNtfy && !isAmbientSuggestionsCompletion) {
|
|
4630
4841
|
try {
|
|
4631
4842
|
await publishNtfy(config, event);
|
|
4632
4843
|
} catch (error) {
|
|
@@ -9351,7 +9562,18 @@ function buildCompletedInboxItems(runtime, state, config, locale) {
|
|
|
9351
9562
|
const items = normalizeHistoryItems(state.recentHistoryItems ?? runtime.recentHistoryItems, config.maxHistoryItems);
|
|
9352
9563
|
runtime.recentHistoryItems = items;
|
|
9353
9564
|
const completedKinds = new Set(["assistant_final", "approval", "moltbook_reply", "moltbook_draft", "thread_share", "a2a_task_result"]);
|
|
9354
|
-
|
|
9565
|
+
|
|
9566
|
+
// Infrequent kinds (A2A, thread_share) get evicted from the fixed-size
|
|
9567
|
+
// history FIFO by the high volume of approval/assistant_final entries.
|
|
9568
|
+
// Fall back to recentTimelineEntries so completed A2A tasks are never lost
|
|
9569
|
+
// while they're still within the timeline window.
|
|
9570
|
+
const infrequentKinds = new Set(["a2a_task_result", "thread_share"]);
|
|
9571
|
+
const historyStableIds = new Set(items.map((i) => i.stableId).filter(Boolean));
|
|
9572
|
+
const timelineEntries = (runtime.recentTimelineEntries ?? [])
|
|
9573
|
+
.filter((e) => infrequentKinds.has(cleanText(e?.kind || "")) && !historyStableIds.has(e.stableId));
|
|
9574
|
+
const merged = [...items, ...timelineEntries];
|
|
9575
|
+
|
|
9576
|
+
return merged
|
|
9355
9577
|
.filter((item) => {
|
|
9356
9578
|
const k = cleanText(item?.kind || "");
|
|
9357
9579
|
if (!completedKinds.has(k)) return false;
|
|
@@ -9631,22 +9853,44 @@ function buildOperationalTimelineEntries(runtime, state, config, locale) {
|
|
|
9631
9853
|
}
|
|
9632
9854
|
|
|
9633
9855
|
for (const historyItem of normalizeHistoryItems(state.recentHistoryItems ?? runtime.recentHistoryItems, config.maxHistoryItems)) {
|
|
9634
|
-
|
|
9856
|
+
let historyKind = historyItem.kind;
|
|
9857
|
+
let historySummary = historyItem.summary;
|
|
9858
|
+
let historyMessageText = historyItem.messageText;
|
|
9859
|
+
let historyTitle = historyItem.threadLabel ? formatTitle(kindTitle(locale, historyItem.kind), historyItem.threadLabel) : historyItem.title;
|
|
9860
|
+
let historySuggestions = [];
|
|
9861
|
+
|
|
9862
|
+
if (historyItem.kind === "assistant_final" || historyItem.kind === "completion") {
|
|
9863
|
+
const ambientSuggestionsEnvelope = parseAmbientSuggestionsEnvelope(historyItem.messageText ?? "");
|
|
9864
|
+
if (ambientSuggestionsEnvelope?.type === "metadata") {
|
|
9865
|
+
continue;
|
|
9866
|
+
}
|
|
9867
|
+
if (ambientSuggestionsEnvelope?.type === "suggestions") {
|
|
9868
|
+
const ambientSuggestions = ambientSuggestionsEnvelope.suggestions;
|
|
9869
|
+
historyKind = "ambient_suggestions";
|
|
9870
|
+
historyTitle = kindTitle(locale, historyKind);
|
|
9871
|
+
historySummary = ambientSuggestionsSummary(locale, ambientSuggestions);
|
|
9872
|
+
historyMessageText = ambientSuggestionsMessageText(locale, ambientSuggestions);
|
|
9873
|
+
historySuggestions = ambientSuggestions;
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9876
|
+
|
|
9877
|
+
if (!timelineKinds.has(historyKind)) {
|
|
9635
9878
|
continue;
|
|
9636
9879
|
}
|
|
9637
9880
|
items.push(
|
|
9638
9881
|
normalizeTimelineEntry({
|
|
9639
9882
|
stableId: historyItem.stableId,
|
|
9640
9883
|
token: historyItem.token,
|
|
9641
|
-
kind:
|
|
9884
|
+
kind: historyKind,
|
|
9642
9885
|
threadId: cleanText(extractConversationIdFromStableId(historyItem.stableId) || ""),
|
|
9643
9886
|
threadLabel: historyItem.threadLabel,
|
|
9644
|
-
title:
|
|
9645
|
-
summary:
|
|
9646
|
-
messageText:
|
|
9887
|
+
title: historyTitle,
|
|
9888
|
+
summary: historySummary,
|
|
9889
|
+
messageText: historyMessageText,
|
|
9647
9890
|
outcome: historyItem.outcome,
|
|
9648
9891
|
createdAtMs: historyItem.createdAtMs,
|
|
9649
9892
|
provider: normalizeProvider(historyItem.provider),
|
|
9893
|
+
...(historySuggestions.length > 0 && historyKind === "ambient_suggestions" ? { suggestions: historySuggestions } : {}),
|
|
9650
9894
|
})
|
|
9651
9895
|
);
|
|
9652
9896
|
}
|
|
@@ -10070,6 +10314,25 @@ function buildTimelineMessageDetail(entry, locale, runtime = null) {
|
|
|
10070
10314
|
};
|
|
10071
10315
|
}
|
|
10072
10316
|
|
|
10317
|
+
function buildAmbientSuggestionsDetail(entry, locale) {
|
|
10318
|
+
const suggestions = normalizeAmbientSuggestions(entry?.suggestions ?? []);
|
|
10319
|
+
return {
|
|
10320
|
+
kind: "ambient_suggestions",
|
|
10321
|
+
token: cleanText(entry?.token || ""),
|
|
10322
|
+
threadId: cleanText(entry?.threadId || ""),
|
|
10323
|
+
title: cleanText(entry?.title || "") || kindTitle(locale, "ambient_suggestions"),
|
|
10324
|
+
threadLabel: cleanText(entry?.threadLabel || ""),
|
|
10325
|
+
createdAtMs: Number(entry?.createdAtMs) || 0,
|
|
10326
|
+
messageHtml: renderMessageHtml(
|
|
10327
|
+
t(locale, "detail.ambientSuggestions.copy"),
|
|
10328
|
+
`<p>${escapeHtml(t(locale, "detail.detailUnavailable"))}</p>`
|
|
10329
|
+
),
|
|
10330
|
+
suggestions,
|
|
10331
|
+
readOnly: true,
|
|
10332
|
+
actions: [],
|
|
10333
|
+
};
|
|
10334
|
+
}
|
|
10335
|
+
|
|
10073
10336
|
function buildTimelineFileEventDetail(entry, locale) {
|
|
10074
10337
|
const fileEventType = normalizeTimelineFileEventType(entry?.fileEventType ?? "");
|
|
10075
10338
|
return {
|
|
@@ -10791,6 +11054,33 @@ async function buildApiItemDetail({ config, runtime, state, kind, token, locale
|
|
|
10791
11054
|
const entry = timelineEntryByToken(runtime, token, kind);
|
|
10792
11055
|
return entry ? buildTimelineFileEventDetail(entry, locale) : null;
|
|
10793
11056
|
}
|
|
11057
|
+
if (kind === "ambient_suggestions") {
|
|
11058
|
+
const entry = timelineEntryByToken(runtime, token, kind);
|
|
11059
|
+
if (entry) {
|
|
11060
|
+
return buildAmbientSuggestionsDetail(entry, locale);
|
|
11061
|
+
}
|
|
11062
|
+
const historicalSource = (runtime.recentHistoryItems ?? []).find((item) => {
|
|
11063
|
+
if (cleanText(item?.token || "") !== cleanText(token || "")) {
|
|
11064
|
+
return false;
|
|
11065
|
+
}
|
|
11066
|
+
const itemKind = cleanText(item?.kind || "");
|
|
11067
|
+
if (itemKind !== "assistant_final" && itemKind !== "completion") {
|
|
11068
|
+
return false;
|
|
11069
|
+
}
|
|
11070
|
+
return parseAmbientSuggestionsPayload(item?.messageText ?? "") !== null;
|
|
11071
|
+
});
|
|
11072
|
+
if (!historicalSource) {
|
|
11073
|
+
return null;
|
|
11074
|
+
}
|
|
11075
|
+
return buildAmbientSuggestionsDetail({
|
|
11076
|
+
token: historicalSource.token,
|
|
11077
|
+
threadId: historyItemThreadId(historicalSource),
|
|
11078
|
+
threadLabel: historicalSource.threadLabel,
|
|
11079
|
+
title: kindTitle(locale, "ambient_suggestions"),
|
|
11080
|
+
createdAtMs: historicalSource.createdAtMs,
|
|
11081
|
+
suggestions: parseAmbientSuggestionsPayload(historicalSource.messageText ?? "") ?? [],
|
|
11082
|
+
}, locale);
|
|
11083
|
+
}
|
|
10794
11084
|
if (timelineMessageKinds.has(kind)) {
|
|
10795
11085
|
const entry = timelineEntryByToken(runtime, token, kind);
|
|
10796
11086
|
if (!entry) return null;
|
|
@@ -16213,6 +16503,7 @@ async function main() {
|
|
|
16213
16503
|
migratedPairedDevicesStateChanged ||
|
|
16214
16504
|
restoredPendingPlanStateChanged ||
|
|
16215
16505
|
restoredTimelineImagePathsStateChanged ||
|
|
16506
|
+
backfilledAmbientSuggestionsStateChanged ||
|
|
16216
16507
|
migratedRecentCodeEventsStateChanged ||
|
|
16217
16508
|
restoredPendingUserInputStateChanged ||
|
|
16218
16509
|
backfilledMoltbookInboxChanged ||
|