@buoy-gg/ask-buoy 7.0.35 → 7.0.38
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 +73 -15
- package/lib/commonjs/blocks/AskBlocks.js +1 -338
- package/lib/commonjs/blocks/BlockRenderer.js +1 -250
- package/lib/commonjs/blocks/ChromePrototypes.js +1 -514
- package/lib/commonjs/blocks/DataBlocks.js +1 -688
- package/lib/commonjs/blocks/ExtraBlocks.js +1 -1854
- package/lib/commonjs/blocks/MediaBlocks.js +1 -140
- package/lib/commonjs/blocks/ReceiptBlocks.js +1 -586
- package/lib/commonjs/blocks/blockText.js +1 -0
- package/lib/commonjs/blocks/blockText.test.js +1 -0
- package/lib/commonjs/blocks/context.js +1 -42
- package/lib/commonjs/blocks/icons.js +1 -96
- package/lib/commonjs/blocks/index.js +1 -95
- package/lib/commonjs/blocks/listRow.test.js +1 -55
- package/lib/commonjs/blocks/pageState.js +1 -34
- package/lib/commonjs/blocks/pageState.test.js +1 -57
- package/lib/commonjs/blocks/primitives.js +1 -401
- package/lib/commonjs/blocks/primitivesIndex.js +1 -62
- package/lib/commonjs/components/ApprovalCard.js +1 -140
- package/lib/commonjs/components/AskBuoyHeader.js +1 -164
- package/lib/commonjs/components/AskBuoyIcon.js +1 -19
- package/lib/commonjs/components/AskBuoyModal.js +1 -353
- package/lib/commonjs/components/AskBuoySettings.js +1 -69
- package/lib/commonjs/components/ChangesBar.js +1 -211
- package/lib/commonjs/components/Composer.js +1 -175
- package/lib/commonjs/components/EmptyState.js +1 -154
- package/lib/commonjs/components/KeyboardPad.js +1 -0
- package/lib/commonjs/components/Markdown.js +1 -252
- package/lib/commonjs/components/MessageList.js +1 -683
- package/lib/commonjs/components/QueuedMessages.js +1 -0
- package/lib/commonjs/components/ThinkingStrip.js +1 -223
- package/lib/commonjs/components/TypingDots.js +1 -63
- package/lib/commonjs/components/markdownParse.js +1 -0
- package/lib/commonjs/components/markdownParse.test.js +1 -0
- package/lib/commonjs/components/useCopyAction.js +1 -50
- package/lib/commonjs/index.js +1 -252
- package/lib/commonjs/preset.js +1 -90
- package/lib/commonjs/store/activityGroups.js +1 -0
- package/lib/commonjs/store/activityGroups.test.js +1 -0
- package/lib/commonjs/store/askBuoySettings.js +1 -67
- package/lib/commonjs/store/askBuoyStore.js +1 -365
- package/lib/commonjs/store/blockPlacement.js +1 -0
- package/lib/commonjs/store/blockPlacement.test.js +1 -0
- package/lib/commonjs/store/queue.test.js +1 -0
- package/lib/commonjs/store/resetConversation.js +1 -0
- package/lib/commonjs/store/restorePending.test.js +1 -0
- package/lib/commonjs/store/restoredIds.test.js +1 -47
- package/lib/commonjs/store/suggestions.js +1 -0
- package/lib/commonjs/store/suggestions.test.js +1 -0
- package/lib/commonjs/store/systemLine.test.js +1 -0
- package/lib/commonjs/store/ticketPrompt.js +1 -0
- package/lib/commonjs/store/traceText.js +1 -81
- package/lib/commonjs/store/traceText.test.js +1 -112
- package/lib/commonjs/store/transcriptPersistence.js +1 -208
- package/lib/commonjs/store/transcriptPersistence.test.js +1 -185
- package/lib/commonjs/store/turnRendering.test.js +1 -0
- package/lib/commonjs/store/useAskBuoy.js +1 -400
- package/lib/commonjs/store/useAskBuoySettings.js +1 -14
- package/lib/commonjs/sync/askBuoySyncAdapter.js +1 -119
- package/lib/commonjs/sync/askBuoySyncAdapter.test.js +1 -141
- package/lib/commonjs/sync/wire.js +1 -58
- package/lib/commonjs/types.js +1 -6
- package/lib/module/blocks/AskBlocks.js +1 -331
- package/lib/module/blocks/BlockRenderer.js +1 -245
- package/lib/module/blocks/ChromePrototypes.js +1 -503
- package/lib/module/blocks/DataBlocks.js +1 -674
- package/lib/module/blocks/ExtraBlocks.js +1 -1827
- package/lib/module/blocks/MediaBlocks.js +1 -135
- package/lib/module/blocks/ReceiptBlocks.js +1 -574
- package/lib/module/blocks/blockText.js +1 -0
- package/lib/module/blocks/blockText.test.js +1 -0
- package/lib/module/blocks/context.js +1 -35
- package/lib/module/blocks/icons.js +1 -91
- package/lib/module/blocks/index.js +1 -8
- package/lib/module/blocks/listRow.test.js +1 -56
- package/lib/module/blocks/pageState.js +1 -30
- package/lib/module/blocks/pageState.test.js +1 -56
- package/lib/module/blocks/primitives.js +1 -389
- package/lib/module/blocks/primitivesIndex.js +1 -5
- package/lib/module/components/ApprovalCard.js +1 -135
- package/lib/module/components/AskBuoyHeader.js +1 -161
- package/lib/module/components/AskBuoyIcon.js +1 -8
- package/lib/module/components/AskBuoyModal.js +1 -348
- package/lib/module/components/AskBuoySettings.js +1 -64
- package/lib/module/components/ChangesBar.js +1 -206
- package/lib/module/components/Composer.js +1 -170
- package/lib/module/components/EmptyState.js +1 -149
- package/lib/module/components/KeyboardPad.js +1 -0
- package/lib/module/components/Markdown.js +1 -246
- package/lib/module/components/MessageList.js +1 -677
- package/lib/module/components/QueuedMessages.js +1 -0
- package/lib/module/components/ThinkingStrip.js +1 -218
- package/lib/module/components/TypingDots.js +1 -58
- package/lib/module/components/markdownParse.js +1 -0
- package/lib/module/components/markdownParse.test.js +1 -0
- package/lib/module/components/useCopyAction.js +1 -46
- package/lib/module/index.js +1 -34
- package/lib/module/preset.js +1 -73
- package/lib/module/store/activityGroups.js +1 -0
- package/lib/module/store/activityGroups.test.js +1 -0
- package/lib/module/store/askBuoySettings.js +1 -62
- package/lib/module/store/askBuoyStore.js +1 -361
- package/lib/module/store/blockPlacement.js +1 -0
- package/lib/module/store/blockPlacement.test.js +1 -0
- package/lib/module/store/queue.test.js +1 -0
- package/lib/module/store/resetConversation.js +1 -0
- package/lib/module/store/restorePending.test.js +1 -0
- package/lib/module/store/restoredIds.test.js +1 -46
- package/lib/module/store/suggestions.js +1 -0
- package/lib/module/store/suggestions.test.js +1 -0
- package/lib/module/store/systemLine.test.js +1 -0
- package/lib/module/store/ticketPrompt.js +1 -0
- package/lib/module/store/traceText.js +1 -75
- package/lib/module/store/traceText.test.js +1 -111
- package/lib/module/store/transcriptPersistence.js +1 -198
- package/lib/module/store/transcriptPersistence.test.js +1 -184
- package/lib/module/store/turnRendering.test.js +1 -0
- package/lib/module/store/useAskBuoy.js +1 -394
- package/lib/module/store/useAskBuoySettings.js +1 -9
- package/lib/module/sync/askBuoySyncAdapter.js +1 -114
- package/lib/module/sync/askBuoySyncAdapter.test.js +1 -140
- package/lib/module/sync/wire.js +1 -47
- package/lib/module/types.js +1 -4
- package/lib/typescript/blocks/DataBlocks.d.ts +11 -0
- package/lib/typescript/blocks/ExtraBlocks.d.ts +18 -2
- package/lib/typescript/blocks/blockText.d.ts +14 -0
- package/lib/typescript/blocks/blockText.test.d.ts +2 -0
- package/lib/typescript/blocks/primitives.d.ts +16 -1
- package/lib/typescript/components/ApprovalCard.d.ts +9 -1
- package/lib/typescript/components/AskBuoyHeader.d.ts +31 -1
- package/lib/typescript/components/Composer.d.ts +16 -1
- package/lib/typescript/components/EmptyState.d.ts +5 -1
- package/lib/typescript/components/KeyboardPad.d.ts +33 -0
- package/lib/typescript/components/Markdown.d.ts +0 -10
- package/lib/typescript/components/MessageList.d.ts +7 -2
- package/lib/typescript/components/QueuedMessages.d.ts +10 -0
- package/lib/typescript/components/markdownParse.d.ts +49 -0
- package/lib/typescript/components/markdownParse.test.d.ts +2 -0
- package/lib/typescript/preset.d.ts +4 -2
- package/lib/typescript/store/activityGroups.d.ts +29 -0
- package/lib/typescript/store/activityGroups.test.d.ts +2 -0
- package/lib/typescript/store/askBuoySettings.d.ts +22 -0
- package/lib/typescript/store/askBuoyStore.d.ts +174 -9
- package/lib/typescript/store/blockPlacement.d.ts +30 -0
- package/lib/typescript/store/blockPlacement.test.d.ts +2 -0
- package/lib/typescript/store/queue.test.d.ts +2 -0
- package/lib/typescript/store/resetConversation.d.ts +38 -0
- package/lib/typescript/store/restorePending.test.d.ts +2 -0
- package/lib/typescript/store/suggestions.d.ts +32 -0
- package/lib/typescript/store/suggestions.test.d.ts +2 -0
- package/lib/typescript/store/systemLine.test.d.ts +2 -0
- package/lib/typescript/store/ticketPrompt.d.ts +11 -0
- package/lib/typescript/store/transcriptPersistence.d.ts +41 -2
- package/lib/typescript/store/turnRendering.test.d.ts +2 -0
- package/lib/typescript/store/useAskBuoy.d.ts +26 -3
- package/lib/typescript/sync/askBuoySyncAdapter.d.ts +29 -6
- package/package.json +10 -10
- package/lib/commonjs/blocks/AskBlocks.js.map +0 -1
- package/lib/commonjs/blocks/BlockRenderer.js.map +0 -1
- package/lib/commonjs/blocks/ChromePrototypes.js.map +0 -1
- package/lib/commonjs/blocks/DataBlocks.js.map +0 -1
- package/lib/commonjs/blocks/ExtraBlocks.js.map +0 -1
- package/lib/commonjs/blocks/MediaBlocks.js.map +0 -1
- package/lib/commonjs/blocks/ReceiptBlocks.js.map +0 -1
- package/lib/commonjs/blocks/context.js.map +0 -1
- package/lib/commonjs/blocks/icons.js.map +0 -1
- package/lib/commonjs/blocks/index.js.map +0 -1
- package/lib/commonjs/blocks/listRow.test.js.map +0 -1
- package/lib/commonjs/blocks/pageState.js.map +0 -1
- package/lib/commonjs/blocks/pageState.test.js.map +0 -1
- package/lib/commonjs/blocks/primitives.js.map +0 -1
- package/lib/commonjs/blocks/primitivesIndex.js.map +0 -1
- package/lib/commonjs/components/ApprovalCard.js.map +0 -1
- package/lib/commonjs/components/AskBuoyHeader.js.map +0 -1
- package/lib/commonjs/components/AskBuoyIcon.js.map +0 -1
- package/lib/commonjs/components/AskBuoyModal.js.map +0 -1
- package/lib/commonjs/components/AskBuoySettings.js.map +0 -1
- package/lib/commonjs/components/ChangesBar.js.map +0 -1
- package/lib/commonjs/components/Composer.js.map +0 -1
- package/lib/commonjs/components/EmptyState.js.map +0 -1
- package/lib/commonjs/components/Markdown.js.map +0 -1
- package/lib/commonjs/components/MessageList.js.map +0 -1
- package/lib/commonjs/components/ThinkingStrip.js.map +0 -1
- package/lib/commonjs/components/TypingDots.js.map +0 -1
- package/lib/commonjs/components/useCopyAction.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/preset.js.map +0 -1
- package/lib/commonjs/store/askBuoySettings.js.map +0 -1
- package/lib/commonjs/store/askBuoyStore.js.map +0 -1
- package/lib/commonjs/store/restoredIds.test.js.map +0 -1
- package/lib/commonjs/store/traceText.js.map +0 -1
- package/lib/commonjs/store/traceText.test.js.map +0 -1
- package/lib/commonjs/store/transcriptPersistence.js.map +0 -1
- package/lib/commonjs/store/transcriptPersistence.test.js.map +0 -1
- package/lib/commonjs/store/useAskBuoy.js.map +0 -1
- package/lib/commonjs/store/useAskBuoySettings.js.map +0 -1
- package/lib/commonjs/sync/askBuoySyncAdapter.js.map +0 -1
- package/lib/commonjs/sync/askBuoySyncAdapter.test.js.map +0 -1
- package/lib/commonjs/sync/wire.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/module/blocks/AskBlocks.js.map +0 -1
- package/lib/module/blocks/BlockRenderer.js.map +0 -1
- package/lib/module/blocks/ChromePrototypes.js.map +0 -1
- package/lib/module/blocks/DataBlocks.js.map +0 -1
- package/lib/module/blocks/ExtraBlocks.js.map +0 -1
- package/lib/module/blocks/MediaBlocks.js.map +0 -1
- package/lib/module/blocks/ReceiptBlocks.js.map +0 -1
- package/lib/module/blocks/context.js.map +0 -1
- package/lib/module/blocks/icons.js.map +0 -1
- package/lib/module/blocks/index.js.map +0 -1
- package/lib/module/blocks/listRow.test.js.map +0 -1
- package/lib/module/blocks/pageState.js.map +0 -1
- package/lib/module/blocks/pageState.test.js.map +0 -1
- package/lib/module/blocks/primitives.js.map +0 -1
- package/lib/module/blocks/primitivesIndex.js.map +0 -1
- package/lib/module/components/ApprovalCard.js.map +0 -1
- package/lib/module/components/AskBuoyHeader.js.map +0 -1
- package/lib/module/components/AskBuoyIcon.js.map +0 -1
- package/lib/module/components/AskBuoyModal.js.map +0 -1
- package/lib/module/components/AskBuoySettings.js.map +0 -1
- package/lib/module/components/ChangesBar.js.map +0 -1
- package/lib/module/components/Composer.js.map +0 -1
- package/lib/module/components/EmptyState.js.map +0 -1
- package/lib/module/components/Markdown.js.map +0 -1
- package/lib/module/components/MessageList.js.map +0 -1
- package/lib/module/components/ThinkingStrip.js.map +0 -1
- package/lib/module/components/TypingDots.js.map +0 -1
- package/lib/module/components/useCopyAction.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/preset.js.map +0 -1
- package/lib/module/store/askBuoySettings.js.map +0 -1
- package/lib/module/store/askBuoyStore.js.map +0 -1
- package/lib/module/store/restoredIds.test.js.map +0 -1
- package/lib/module/store/traceText.js.map +0 -1
- package/lib/module/store/traceText.test.js.map +0 -1
- package/lib/module/store/transcriptPersistence.js.map +0 -1
- package/lib/module/store/transcriptPersistence.test.js.map +0 -1
- package/lib/module/store/useAskBuoy.js.map +0 -1
- package/lib/module/store/useAskBuoySettings.js.map +0 -1
- package/lib/module/sync/askBuoySyncAdapter.js.map +0 -1
- package/lib/module/sync/askBuoySyncAdapter.test.js.map +0 -1
- package/lib/module/sync/wire.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/typescript/blocks/AskBlocks.d.ts.map +0 -1
- package/lib/typescript/blocks/BlockRenderer.d.ts.map +0 -1
- package/lib/typescript/blocks/ChromePrototypes.d.ts.map +0 -1
- package/lib/typescript/blocks/DataBlocks.d.ts.map +0 -1
- package/lib/typescript/blocks/ExtraBlocks.d.ts.map +0 -1
- package/lib/typescript/blocks/MediaBlocks.d.ts.map +0 -1
- package/lib/typescript/blocks/ReceiptBlocks.d.ts.map +0 -1
- package/lib/typescript/blocks/context.d.ts.map +0 -1
- package/lib/typescript/blocks/icons.d.ts.map +0 -1
- package/lib/typescript/blocks/index.d.ts.map +0 -1
- package/lib/typescript/blocks/listRow.test.d.ts.map +0 -1
- package/lib/typescript/blocks/pageState.d.ts.map +0 -1
- package/lib/typescript/blocks/pageState.test.d.ts.map +0 -1
- package/lib/typescript/blocks/primitives.d.ts.map +0 -1
- package/lib/typescript/blocks/primitivesIndex.d.ts.map +0 -1
- package/lib/typescript/components/ApprovalCard.d.ts.map +0 -1
- package/lib/typescript/components/AskBuoyHeader.d.ts.map +0 -1
- package/lib/typescript/components/AskBuoyIcon.d.ts.map +0 -1
- package/lib/typescript/components/AskBuoyModal.d.ts.map +0 -1
- package/lib/typescript/components/AskBuoySettings.d.ts.map +0 -1
- package/lib/typescript/components/ChangesBar.d.ts.map +0 -1
- package/lib/typescript/components/Composer.d.ts.map +0 -1
- package/lib/typescript/components/EmptyState.d.ts.map +0 -1
- package/lib/typescript/components/Markdown.d.ts.map +0 -1
- package/lib/typescript/components/MessageList.d.ts.map +0 -1
- package/lib/typescript/components/ThinkingStrip.d.ts.map +0 -1
- package/lib/typescript/components/TypingDots.d.ts.map +0 -1
- package/lib/typescript/components/useCopyAction.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/preset.d.ts.map +0 -1
- package/lib/typescript/store/askBuoySettings.d.ts.map +0 -1
- package/lib/typescript/store/askBuoyStore.d.ts.map +0 -1
- package/lib/typescript/store/restoredIds.test.d.ts.map +0 -1
- package/lib/typescript/store/traceText.d.ts.map +0 -1
- package/lib/typescript/store/traceText.test.d.ts.map +0 -1
- package/lib/typescript/store/transcriptPersistence.d.ts.map +0 -1
- package/lib/typescript/store/transcriptPersistence.test.d.ts.map +0 -1
- package/lib/typescript/store/useAskBuoy.d.ts.map +0 -1
- package/lib/typescript/store/useAskBuoySettings.d.ts.map +0 -1
- package/lib/typescript/sync/askBuoySyncAdapter.d.ts.map +0 -1
- package/lib/typescript/sync/askBuoySyncAdapter.test.d.ts.map +0 -1
- package/lib/typescript/sync/wire.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
|
@@ -1,81 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.hasTrace = hasTrace;
|
|
7
|
-
exports.traceEntries = traceEntries;
|
|
8
|
-
exports.traceToText = traceToText;
|
|
9
|
-
/**
|
|
10
|
-
* The agent's work for one answer, laid out in the order it happened.
|
|
11
|
-
*
|
|
12
|
-
* `traceEntries` is the ordering, shared by the collapsible strip in the chat
|
|
13
|
-
* and by the JSON export — the two must agree, because the point of the export
|
|
14
|
-
* is reading elsewhere the run you were just looking at. `traceToText` is the
|
|
15
|
-
* plain-text rendering, kept for a quick paste into Slack; the debugging
|
|
16
|
-
* artefact is `sessionExport.ts`.
|
|
17
|
-
*
|
|
18
|
-
* Reasoning and steps INTERLEAVE. One assistant bubble spans every turn of a
|
|
19
|
-
* multi-step run, so the real shape is "think, act, think, act"; lumping the
|
|
20
|
-
* thoughts at the top would misreport when the model knew what.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
function traceEntries(message) {
|
|
24
|
-
const thoughts = [...(message.reasoning ?? [])].sort((a, b) => a.afterCalls - b.afterCalls);
|
|
25
|
-
const out = [];
|
|
26
|
-
let t = 0;
|
|
27
|
-
const drainTo = calls => {
|
|
28
|
-
while (t < thoughts.length && thoughts[t].afterCalls <= calls) {
|
|
29
|
-
out.push({
|
|
30
|
-
kind: "thought",
|
|
31
|
-
text: thoughts[t].text
|
|
32
|
-
});
|
|
33
|
-
t += 1;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
message.toolCalls.forEach((call, i) => {
|
|
37
|
-
drainTo(i);
|
|
38
|
-
out.push({
|
|
39
|
-
kind: "step",
|
|
40
|
-
n: i + 1,
|
|
41
|
-
call
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
// Anything the model thought after its last tool call — usually the reasoning
|
|
45
|
-
// behind the answer itself, which is the most useful line in the trace.
|
|
46
|
-
drainTo(message.toolCalls.length);
|
|
47
|
-
return out;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Has this message anything to show? Drives whether the strip renders at all. */
|
|
51
|
-
function hasTrace(message) {
|
|
52
|
-
return (message.reasoning?.length ?? 0) > 0 || message.toolCalls.length > 0;
|
|
53
|
-
}
|
|
54
|
-
const statusWord = call => call.status === "ok" ? call.summary || "done" : call.status === "running" ? "running" : `${call.status}${call.summary ? ` — ${call.summary}` : ""}`;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* The trace as plain text, for the clipboard.
|
|
58
|
-
*
|
|
59
|
-
* Indented under the answer it belongs to so a pasted session still reads as a
|
|
60
|
-
* conversation rather than a log dump.
|
|
61
|
-
*/
|
|
62
|
-
function traceToText(message, indent = " ") {
|
|
63
|
-
const lines = [];
|
|
64
|
-
for (const entry of traceEntries(message)) {
|
|
65
|
-
if (entry.kind === "thought") {
|
|
66
|
-
lines.push(`${indent}[thinking]`);
|
|
67
|
-
for (const line of entry.text.trim().split("\n")) {
|
|
68
|
-
lines.push(`${indent} ${line}`);
|
|
69
|
-
}
|
|
70
|
-
} else {
|
|
71
|
-
const {
|
|
72
|
-
n,
|
|
73
|
-
call
|
|
74
|
-
} = entry;
|
|
75
|
-
lines.push(`${indent}[${n}] ${call.toolId}.${call.action} — ${statusWord(call)}`);
|
|
76
|
-
if (call.params) lines.push(`${indent} ${call.params}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return lines;
|
|
80
|
-
}
|
|
81
|
-
//# sourceMappingURL=traceText.js.map
|
|
1
|
+
"use strict";function traceEntries(t){const s=[...t.reasoning??[]].sort((t,s)=>t.afterCalls-s.afterCalls),e=[];let r=0;const n=t=>{for(;r<s.length&&s[r].afterCalls<=t;)e.push({kind:"thought",text:s[r].text}),r+=1};return t.toolCalls.forEach((t,s)=>{n(s),e.push({kind:"step",n:s+1,call:t})}),n(t.toolCalls.length),e}function hasTrace(t){return(t.reasoning?.length??0)>0||t.toolCalls.length>0}Object.defineProperty(exports,"__esModule",{value:!0}),exports.hasTrace=hasTrace,exports.traceEntries=traceEntries,exports.traceToText=traceToText;const statusWord=t=>"ok"===t.status?t.summary||"done":"running"===t.status?"running":`${t.status}${t.summary?` — ${t.summary}`:""}`;function traceToText(t,s=" "){const e=[];for(const r of traceEntries(t))if("thought"===r.kind){e.push(`${s}[thinking]`);for(const t of r.text.trim().split("\n"))e.push(`${s} ${t}`)}else{const{n:t,call:n}=r;e.push(`${s}[${t}] ${n.toolId}.${n.action} — ${statusWord(n)}`),n.params&&e.push(`${s} ${n.params}`)}return e}
|
|
@@ -1,112 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _vitest = require("vitest");
|
|
4
|
-
var _traceText = require("./traceText");
|
|
5
|
-
/**
|
|
6
|
-
* The agent's working, as text.
|
|
7
|
-
*
|
|
8
|
-
* This exists so a tester can paste a run somewhere and find the step that
|
|
9
|
-
* broke, which puts two properties on it: reasoning and steps come out in the
|
|
10
|
-
* order they HAPPENED (a multi-step turn is think, act, think, act — not all
|
|
11
|
-
* the thoughts and then all the steps), and a failed step says so with its
|
|
12
|
-
* parameters attached, since "storage.set failed" without the value it wrote
|
|
13
|
-
* is not a bug report.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const call = (over = {}) => ({
|
|
17
|
-
id: "c1",
|
|
18
|
-
toolId: "zustand",
|
|
19
|
-
action: "getStoreState",
|
|
20
|
-
label: "Read the bag",
|
|
21
|
-
status: "ok",
|
|
22
|
-
summary: "Done",
|
|
23
|
-
...over
|
|
24
|
-
});
|
|
25
|
-
const msg = (over = {}) => ({
|
|
26
|
-
id: "m1",
|
|
27
|
-
role: "assistant",
|
|
28
|
-
text: "Five items.",
|
|
29
|
-
toolCalls: [],
|
|
30
|
-
blocks: [],
|
|
31
|
-
...over
|
|
32
|
-
});
|
|
33
|
-
(0, _vitest.describe)("the order of a trace", () => {
|
|
34
|
-
(0, _vitest.it)("interleaves thinking with the steps it led to", () => {
|
|
35
|
-
const m = msg({
|
|
36
|
-
toolCalls: [call({
|
|
37
|
-
id: "c1"
|
|
38
|
-
}), call({
|
|
39
|
-
id: "c2",
|
|
40
|
-
action: "setState"
|
|
41
|
-
})],
|
|
42
|
-
reasoning: [{
|
|
43
|
-
text: "I should read the bag first.",
|
|
44
|
-
afterCalls: 0
|
|
45
|
-
}, {
|
|
46
|
-
text: "Now I can write the new quantity.",
|
|
47
|
-
afterCalls: 1
|
|
48
|
-
}]
|
|
49
|
-
});
|
|
50
|
-
(0, _vitest.expect)((0, _traceText.traceEntries)(m).map(e => e.kind === "thought" ? "T" : `S${e.n}`)).toEqual(["T", "S1", "T", "S2"]);
|
|
51
|
-
});
|
|
52
|
-
(0, _vitest.it)("keeps a closing thought after the last step", () => {
|
|
53
|
-
// Usually the reasoning behind the ANSWER, which is the most useful line
|
|
54
|
-
// in the whole trace.
|
|
55
|
-
const m = msg({
|
|
56
|
-
toolCalls: [call()],
|
|
57
|
-
reasoning: [{
|
|
58
|
-
text: "That is everything the user asked for.",
|
|
59
|
-
afterCalls: 1
|
|
60
|
-
}]
|
|
61
|
-
});
|
|
62
|
-
const entries = (0, _traceText.traceEntries)(m);
|
|
63
|
-
(0, _vitest.expect)(entries[entries.length - 1]).toEqual({
|
|
64
|
-
kind: "thought",
|
|
65
|
-
text: "That is everything the user asked for."
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
(0, _vitest.it)("handles a turn with steps and no reasoning at all", () => {
|
|
69
|
-
// The common case: gpt-4.1 and friends report none.
|
|
70
|
-
const m = msg({
|
|
71
|
-
toolCalls: [call()]
|
|
72
|
-
});
|
|
73
|
-
(0, _vitest.expect)((0, _traceText.traceEntries)(m)).toHaveLength(1);
|
|
74
|
-
(0, _vitest.expect)((0, _traceText.hasTrace)(m)).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
(0, _vitest.it)("has nothing to show for a plain answer", () => {
|
|
77
|
-
(0, _vitest.expect)((0, _traceText.hasTrace)(msg())).toBe(false);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
(0, _vitest.describe)("the text it produces", () => {
|
|
81
|
-
(0, _vitest.it)("names the tool, the action, the outcome and the parameters", () => {
|
|
82
|
-
const m = msg({
|
|
83
|
-
toolCalls: [call({
|
|
84
|
-
params: '{"storeName":"Poké Mart bag"}'
|
|
85
|
-
})]
|
|
86
|
-
});
|
|
87
|
-
(0, _vitest.expect)((0, _traceText.traceToText)(m)).toEqual([" [1] zustand.getStoreState — Done", ' {"storeName":"Poké Mart bag"}']);
|
|
88
|
-
});
|
|
89
|
-
(0, _vitest.it)("makes a failure and its parameters findable", () => {
|
|
90
|
-
const m = msg({
|
|
91
|
-
toolCalls: [call({
|
|
92
|
-
toolId: "storage",
|
|
93
|
-
action: "async.setItem",
|
|
94
|
-
status: "failed",
|
|
95
|
-
summary: "key not found",
|
|
96
|
-
params: '{"key":"@pokemart/bag"}'
|
|
97
|
-
})]
|
|
98
|
-
});
|
|
99
|
-
(0, _vitest.expect)((0, _traceText.traceToText)(m).join("\n")).toContain("failed — key not found");
|
|
100
|
-
(0, _vitest.expect)((0, _traceText.traceToText)(m).join("\n")).toContain("@pokemart/bag");
|
|
101
|
-
});
|
|
102
|
-
(0, _vitest.it)("indents a multi-line thought so a pasted session still reads as one", () => {
|
|
103
|
-
const m = msg({
|
|
104
|
-
reasoning: [{
|
|
105
|
-
text: "First line.\nSecond line.",
|
|
106
|
-
afterCalls: 0
|
|
107
|
-
}]
|
|
108
|
-
});
|
|
109
|
-
(0, _vitest.expect)((0, _traceText.traceToText)(m)).toEqual([" [thinking]", " First line.", " Second line."]);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
//# sourceMappingURL=traceText.test.js.map
|
|
1
|
+
"use strict";var _vitest=require("vitest"),_traceText=require("./traceText");const call=(t={})=>({id:"c1",toolId:"zustand",action:"getStoreState",label:"Read the bag",status:"ok",summary:"Done",...t}),msg=(t={})=>({id:"m1",role:"assistant",text:"Five items.",toolCalls:[],blocks:[],...t});(0,_vitest.describe)("the order of a trace",()=>{(0,_vitest.it)("interleaves thinking with the steps it led to",()=>{const t=msg({toolCalls:[call({id:"c1"}),call({id:"c2",action:"setState"})],reasoning:[{text:"I should read the bag first.",afterCalls:0},{text:"Now I can write the new quantity.",afterCalls:1}]});(0,_vitest.expect)((0,_traceText.traceEntries)(t).map(t=>"thought"===t.kind?"T":`S${t.n}`)).toEqual(["T","S1","T","S2"])}),(0,_vitest.it)("keeps a closing thought after the last step",()=>{const t=msg({toolCalls:[call()],reasoning:[{text:"That is everything the user asked for.",afterCalls:1}]}),e=(0,_traceText.traceEntries)(t);(0,_vitest.expect)(e[e.length-1]).toEqual({kind:"thought",text:"That is everything the user asked for."})}),(0,_vitest.it)("handles a turn with steps and no reasoning at all",()=>{const t=msg({toolCalls:[call()]});(0,_vitest.expect)((0,_traceText.traceEntries)(t)).toHaveLength(1),(0,_vitest.expect)((0,_traceText.hasTrace)(t)).toBe(!0)}),(0,_vitest.it)("has nothing to show for a plain answer",()=>{(0,_vitest.expect)((0,_traceText.hasTrace)(msg())).toBe(!1)})}),(0,_vitest.describe)("the text it produces",()=>{(0,_vitest.it)("names the tool, the action, the outcome and the parameters",()=>{const t=msg({toolCalls:[call({params:'{"storeName":"Poké Mart bag"}'})]});(0,_vitest.expect)((0,_traceText.traceToText)(t)).toEqual([" [1] zustand.getStoreState — Done",' {"storeName":"Poké Mart bag"}'])}),(0,_vitest.it)("makes a failure and its parameters findable",()=>{const t=msg({toolCalls:[call({toolId:"storage",action:"async.setItem",status:"failed",summary:"key not found",params:'{"key":"@pokemart/bag"}'})]});(0,_vitest.expect)((0,_traceText.traceToText)(t).join("\n")).toContain("failed — key not found"),(0,_vitest.expect)((0,_traceText.traceToText)(t).join("\n")).toContain("@pokemart/bag")}),(0,_vitest.it)("indents a multi-line thought so a pasted session still reads as one",()=>{const t=msg({reasoning:[{text:"First line.\nSecond line.",afterCalls:0}]});(0,_vitest.expect)((0,_traceText.traceToText)(t)).toEqual([" [thinking]"," First line."," Second line."])})});
|
|
@@ -1,208 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.TRANSCRIPT_KEY = void 0;
|
|
7
|
-
exports.buildPayload = buildPayload;
|
|
8
|
-
exports.clearTranscript = clearTranscript;
|
|
9
|
-
exports.fitToCaps = fitToCaps;
|
|
10
|
-
exports.parsePayload = parsePayload;
|
|
11
|
-
exports.readTranscript = readTranscript;
|
|
12
|
-
exports.writeTranscript = writeTranscript;
|
|
13
|
-
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
14
|
-
var _wire = require("../sync/wire");
|
|
15
|
-
/**
|
|
16
|
-
* The conversation, across a reload or a crash.
|
|
17
|
-
*
|
|
18
|
-
* This reverses a deliberate earlier decision, so the reasoning is worth
|
|
19
|
-
* stating. The transcript used to live only in memory because it can quote
|
|
20
|
-
* anything the agent read — a storage value, a response body, a customer
|
|
21
|
-
* record — and a Buoy key on disk is readable by the Storage tool, which the
|
|
22
|
-
* unauthenticated broker mirrors to every connected dashboard. That risk did
|
|
23
|
-
* not go away; it is now a trade the host app makes knowingly, and can decline
|
|
24
|
-
* with `askBuoy={{ persistTranscript: false }}`.
|
|
25
|
-
*
|
|
26
|
-
* What lowers it:
|
|
27
|
-
* - Tool RESULTS are never written. The model's saved history keeps the shape
|
|
28
|
-
* of each round (which tool ran, what it was asked) and replaces every result
|
|
29
|
-
* payload with a placeholder — so the agent resumes knowing what it did, not
|
|
30
|
-
* what it saw. Raw payloads are the sensitive part and they stay in memory.
|
|
31
|
-
* - Everything written passes `redactForWire`, the same credential scrubber the
|
|
32
|
-
* broker wire uses.
|
|
33
|
-
* - It is capped, oldest-first, so a long QA session cannot grow a file without
|
|
34
|
-
* bound.
|
|
35
|
-
* - "New conversation" deletes it, rather than leaving the last chat on disk.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/** `@react_buoy` prefix, so the Storage tool treats it as one of its own. */
|
|
39
|
-
const TRANSCRIPT_KEY = exports.TRANSCRIPT_KEY = `${_sharedUi.devToolsStorageKeys.base}_ask_buoy_transcript`;
|
|
40
|
-
|
|
41
|
-
/** Bumped when the saved shape changes; an older payload is dropped, not parsed. */
|
|
42
|
-
const VERSION = 1;
|
|
43
|
-
|
|
44
|
-
/** Newest N bubbles. Beyond this a reader is scrolling history, not testing. */
|
|
45
|
-
const MAX_MESSAGES = 60;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Hard ceiling on the file. Blocks carry image URLs and table rows, so a
|
|
49
|
-
* message is not a small object; this is checked AFTER the message cap and
|
|
50
|
-
* drops from the oldest end until it fits.
|
|
51
|
-
*/
|
|
52
|
-
const MAX_BYTES = 256_000;
|
|
53
|
-
|
|
54
|
-
/** What replaces a tool result. Addressed to the model, which will read it. */
|
|
55
|
-
const DROPPED_RESULT = "[not kept across a restart — read it again if you need it]";
|
|
56
|
-
function scrub(text) {
|
|
57
|
-
return text ? (0, _wire.redactForWire)(text) : text;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* A bubble as it should look after a restart.
|
|
62
|
-
*
|
|
63
|
-
* `streaming` is cleared because nothing is streaming into a bubble the app
|
|
64
|
-
* has just rebuilt, and a message caught mid-turn is marked as interrupted
|
|
65
|
-
* rather than left looking like a silent, empty answer — which is exactly the
|
|
66
|
-
* crash case this feature exists for. `actionState` is not saved at all: a
|
|
67
|
-
* button stuck on "running" would be a lie about work that died with the
|
|
68
|
-
* process.
|
|
69
|
-
*/
|
|
70
|
-
function settle(m) {
|
|
71
|
-
const cutOff = m.streaming === true;
|
|
72
|
-
return {
|
|
73
|
-
...m,
|
|
74
|
-
text: scrub(m.text),
|
|
75
|
-
streaming: false,
|
|
76
|
-
// The thinking view's payloads go through the same scrubber as the answer:
|
|
77
|
-
// step parameters carry whatever the model passed a tool, which is exactly
|
|
78
|
-
// where a token ends up when the agent has been asked to set one.
|
|
79
|
-
reasoning: m.reasoning?.map(r => ({
|
|
80
|
-
...r,
|
|
81
|
-
text: scrub(r.text)
|
|
82
|
-
})),
|
|
83
|
-
toolCalls: m.toolCalls.map(c => {
|
|
84
|
-
const params = c.params ? scrub(c.params) : undefined;
|
|
85
|
-
// `result` is dropped, never scrubbed. It is the tool payload itself —
|
|
86
|
-
// a store's whole state, a response body — and "tool results are never
|
|
87
|
-
// written to disk" is the promise this file is built on. It stays in
|
|
88
|
-
// memory for the thinking view and the JSON export.
|
|
89
|
-
const {
|
|
90
|
-
result: _dropped,
|
|
91
|
-
...rest
|
|
92
|
-
} = c;
|
|
93
|
-
return c.status === "running" ? {
|
|
94
|
-
...rest,
|
|
95
|
-
params,
|
|
96
|
-
status: "failed",
|
|
97
|
-
summary: "Interrupted"
|
|
98
|
-
} : {
|
|
99
|
-
...rest,
|
|
100
|
-
params
|
|
101
|
-
};
|
|
102
|
-
}),
|
|
103
|
-
error: m.error ?? (cutOff && !m.text ? "This answer was interrupted — the app restarted mid-turn." : undefined)
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Model history with every tool result replaced by a placeholder. */
|
|
108
|
-
function withoutResults(history) {
|
|
109
|
-
return history.map(m => {
|
|
110
|
-
if (m.role === "tool-results") {
|
|
111
|
-
return {
|
|
112
|
-
...m,
|
|
113
|
-
results: m.results.map(r => ({
|
|
114
|
-
...r,
|
|
115
|
-
content: DROPPED_RESULT
|
|
116
|
-
}))
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
if (m.role === "assistant") return {
|
|
120
|
-
...m,
|
|
121
|
-
text: scrub(m.text)
|
|
122
|
-
};
|
|
123
|
-
return {
|
|
124
|
-
...m,
|
|
125
|
-
text: scrub(m.text)
|
|
126
|
-
};
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Trim to the caps. Messages and history are trimmed together and from the
|
|
132
|
-
* same end, so what the user sees and what the model remembers stay the same
|
|
133
|
-
* conversation rather than drifting apart across restarts.
|
|
134
|
-
*/
|
|
135
|
-
function fitToCaps(payload) {
|
|
136
|
-
let out = {
|
|
137
|
-
...payload,
|
|
138
|
-
messages: payload.messages.slice(-MAX_MESSAGES),
|
|
139
|
-
history: payload.history.slice(-MAX_MESSAGES * 2)
|
|
140
|
-
};
|
|
141
|
-
let json = JSON.stringify(out);
|
|
142
|
-
while (json.length > MAX_BYTES && out.messages.length > 1) {
|
|
143
|
-
out = {
|
|
144
|
-
...out,
|
|
145
|
-
messages: out.messages.slice(1),
|
|
146
|
-
history: out.history.slice(Math.min(2, out.history.length))
|
|
147
|
-
};
|
|
148
|
-
json = JSON.stringify(out);
|
|
149
|
-
}
|
|
150
|
-
return out;
|
|
151
|
-
}
|
|
152
|
-
function buildPayload(input) {
|
|
153
|
-
return fitToCaps({
|
|
154
|
-
v: VERSION,
|
|
155
|
-
savedAt: Date.now(),
|
|
156
|
-
messages: input.messages.map(settle),
|
|
157
|
-
history: withoutResults(input.history),
|
|
158
|
-
answers: input.answers,
|
|
159
|
-
usage: input.usage
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Anything unparseable, or from an older shape, is treated as "no transcript". */
|
|
164
|
-
function parsePayload(raw) {
|
|
165
|
-
if (!raw) return null;
|
|
166
|
-
try {
|
|
167
|
-
const parsed = JSON.parse(raw);
|
|
168
|
-
if (parsed?.v !== VERSION || !Array.isArray(parsed.messages)) return null;
|
|
169
|
-
if (parsed.messages.length === 0) return null;
|
|
170
|
-
return {
|
|
171
|
-
v: VERSION,
|
|
172
|
-
savedAt: typeof parsed.savedAt === "number" ? parsed.savedAt : 0,
|
|
173
|
-
messages: parsed.messages,
|
|
174
|
-
history: Array.isArray(parsed.history) ? parsed.history : [],
|
|
175
|
-
answers: parsed.answers ?? {},
|
|
176
|
-
usage: parsed.usage ?? {
|
|
177
|
-
input: 0,
|
|
178
|
-
output: 0,
|
|
179
|
-
requests: 0
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
} catch {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
async function readTranscript() {
|
|
187
|
-
try {
|
|
188
|
-
return parsePayload(await _sharedUi.persistentStorage.getItem(TRANSCRIPT_KEY));
|
|
189
|
-
} catch {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async function writeTranscript(payload) {
|
|
194
|
-
try {
|
|
195
|
-
await _sharedUi.persistentStorage.setItem(TRANSCRIPT_KEY, JSON.stringify(payload));
|
|
196
|
-
} catch {
|
|
197
|
-
// A transcript that cannot be saved must never break the chat that
|
|
198
|
-
// produced it.
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
async function clearTranscript() {
|
|
202
|
-
try {
|
|
203
|
-
await _sharedUi.persistentStorage.removeItem(TRANSCRIPT_KEY);
|
|
204
|
-
} catch {
|
|
205
|
-
/* nothing to do — see writeTranscript */
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
//# sourceMappingURL=transcriptPersistence.js.map
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.TRANSCRIPT_KEY=void 0,exports.buildPayload=buildPayload,exports.clearTranscript=clearTranscript,exports.fitToCaps=fitToCaps,exports.parsePayload=parsePayload,exports.readTranscript=readTranscript,exports.writeTranscript=writeTranscript;var _sharedUi=require("@buoy-gg/shared-ui"),_wire=require("../sync/wire");const TRANSCRIPT_KEY=exports.TRANSCRIPT_KEY=`${_sharedUi.devToolsStorageKeys.base}_ask_buoy_transcript`,VERSION=1,MAX_MESSAGES=60,MAX_BYTES=256e3,DROPPED_RESULT="[not kept across a restart — read it again if you need it]";function scrub(e){return e?(0,_wire.redactForWire)(e):e}function settle(e){const r=!0===e.streaming;return{...e,text:scrub(e.text),streaming:!1,reasoning:e.reasoning?.map(e=>({...e,text:scrub(e.text)})),toolCalls:e.toolCalls.map(e=>{const r=e.params?scrub(e.params):void 0,{result:t,...s}=e;return"running"===e.status?{...s,params:r,status:"failed",summary:"Interrupted"}:{...s,params:r}}),error:e.error??(r&&!e.text?"This answer was interrupted — the app restarted mid-turn.":void 0)}}function withoutResults(e){return e.map(e=>"tool-results"===e.role?{...e,results:e.results.map(e=>({...e,content:DROPPED_RESULT}))}:(e.role,{...e,text:scrub(e.text)}))}function fitToCaps(e){let r={...e,messages:e.messages.slice(-MAX_MESSAGES),history:e.history.slice(2*-MAX_MESSAGES)},t=JSON.stringify(r);for(;t.length>MAX_BYTES&&r.messages.length>1;)r={...r,messages:r.messages.slice(1),history:r.history.slice(Math.min(2,r.history.length))},t=JSON.stringify(r);return r}function storableApproval(e){if(!e)return null;const r=e.params;if(!r)return null;let t;try{t=JSON.stringify(r)}catch{return null}return(0,_wire.redactForWire)(t)!==t?null:{id:e.id,toolId:e.toolId,action:e.action,label:scrub(e.label),description:e.description?scrub(e.description):void 0,reason:scrub(e.reason),params:r,targetDigest:e.targetDigest}}function isStoredApproval(e){if("object"!=typeof e||null===e)return!1;const r=e;return"string"==typeof r.id&&"string"==typeof r.toolId&&"string"==typeof r.action&&"string"==typeof r.label&&"string"==typeof r.reason&&"object"==typeof r.params&&null!==r.params}function storableLedger(e){if(!e?.length)return;const r=e.filter(e=>{try{const r=JSON.stringify(e);return(0,_wire.redactForWire)(r)===r}catch{return!1}});return r.length?r:void 0}function buildPayload(e){return fitToCaps({v:VERSION,savedAt:Date.now(),messages:e.messages.map(settle),history:withoutResults(e.history),answers:e.answers,usage:e.usage,pending:storableApproval(e.pending),ledger:storableLedger(e.ledger),queued:e.queued?.length?e.queued.map(e=>({id:e.id,text:scrub(e.text)})):void 0})}function parsePayload(e){if(!e)return null;try{const r=JSON.parse(e);return r?.v===VERSION&&Array.isArray(r.messages)?0===r.messages.length?null:{v:VERSION,savedAt:"number"==typeof r.savedAt?r.savedAt:0,messages:r.messages,history:Array.isArray(r.history)?r.history:[],answers:r.answers??{},usage:r.usage??{input:0,output:0,requests:0},pending:isStoredApproval(r.pending)?r.pending:null,ledger:Array.isArray(r.ledger)?r.ledger.filter(e=>"object"==typeof e&&null!==e&&"string"==typeof e.id):void 0,queued:Array.isArray(r.queued)?r.queued.filter(e=>"object"==typeof e&&null!==e&&"string"==typeof e.id&&"string"==typeof e.text):void 0}:null}catch{return null}}async function readTranscript(){try{return parsePayload(await _sharedUi.persistentStorage.getItem(TRANSCRIPT_KEY))}catch{return null}}async function writeTranscript(e){try{await _sharedUi.persistentStorage.setItem(TRANSCRIPT_KEY,JSON.stringify(e))}catch{}}async function clearTranscript(){try{await _sharedUi.persistentStorage.removeItem(TRANSCRIPT_KEY)}catch{}}
|
|
@@ -1,185 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _vitest = require("vitest");
|
|
4
|
-
var _transcriptPersistence = require("./transcriptPersistence");
|
|
5
|
-
/**
|
|
6
|
-
* What a saved conversation is allowed to contain, and what it looks like on
|
|
7
|
-
* the way back in.
|
|
8
|
-
*
|
|
9
|
-
* The transcript is on disk under a `@react_buoy` key now, which the Storage
|
|
10
|
-
* tool can read and the unauthenticated broker mirrors. Everything below is a
|
|
11
|
-
* property that keeps that trade bounded: no tool-result payloads, no
|
|
12
|
-
* credentials, no unbounded growth, and nothing that comes back claiming to
|
|
13
|
-
* still be running.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const msg = (over = {}) => ({
|
|
17
|
-
id: "m1",
|
|
18
|
-
role: "assistant",
|
|
19
|
-
text: "ok",
|
|
20
|
-
toolCalls: [],
|
|
21
|
-
blocks: [],
|
|
22
|
-
...over
|
|
23
|
-
});
|
|
24
|
-
const build = (messages, history = []) => (0, _transcriptPersistence.buildPayload)({
|
|
25
|
-
messages,
|
|
26
|
-
history,
|
|
27
|
-
answers: {},
|
|
28
|
-
usage: {
|
|
29
|
-
input: 0,
|
|
30
|
-
output: 0,
|
|
31
|
-
requests: 0
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
(0, _vitest.describe)("what reaches the disk", () => {
|
|
35
|
-
(0, _vitest.it)("never writes a tool result", () => {
|
|
36
|
-
// The payloads are the sensitive half: storage values, response bodies, a
|
|
37
|
-
// customer's record. The ROUND survives so the model can resume; what it
|
|
38
|
-
// saw does not.
|
|
39
|
-
const history = [{
|
|
40
|
-
role: "user",
|
|
41
|
-
text: "what's in the bag?"
|
|
42
|
-
}, {
|
|
43
|
-
role: "assistant",
|
|
44
|
-
text: "",
|
|
45
|
-
toolCalls: [{
|
|
46
|
-
id: "c1",
|
|
47
|
-
name: "buoy_zustand",
|
|
48
|
-
input: {}
|
|
49
|
-
}]
|
|
50
|
-
}, {
|
|
51
|
-
role: "tool-results",
|
|
52
|
-
results: [{
|
|
53
|
-
toolCallId: "c1",
|
|
54
|
-
content: '{"lines":[{"card":"4111111111111111"}]}'
|
|
55
|
-
}]
|
|
56
|
-
}, {
|
|
57
|
-
role: "assistant",
|
|
58
|
-
text: "Five items."
|
|
59
|
-
}];
|
|
60
|
-
const saved = build([msg()], history);
|
|
61
|
-
const json = JSON.stringify(saved);
|
|
62
|
-
(0, _vitest.expect)(json).not.toContain("4111111111111111");
|
|
63
|
-
// The round itself is intact, so the pairing every provider requires holds.
|
|
64
|
-
(0, _vitest.expect)(saved.history.map(m => m.role)).toEqual(["user", "assistant", "tool-results", "assistant"]);
|
|
65
|
-
});
|
|
66
|
-
(0, _vitest.it)("never writes a step's result, even when it writes the step", () => {
|
|
67
|
-
// The step survives so the trace still reads; the payload is the thing the
|
|
68
|
-
// no-tool-results-on-disk promise is about.
|
|
69
|
-
const saved = build([msg({
|
|
70
|
-
toolCalls: [{
|
|
71
|
-
id: "c1",
|
|
72
|
-
toolId: "zustand",
|
|
73
|
-
action: "getStoreState",
|
|
74
|
-
label: "Read the bag",
|
|
75
|
-
status: "ok",
|
|
76
|
-
summary: "Done",
|
|
77
|
-
params: '{"storeName":"bag"}',
|
|
78
|
-
result: '{"card":"4111111111111111"}'
|
|
79
|
-
}]
|
|
80
|
-
})]);
|
|
81
|
-
(0, _vitest.expect)(JSON.stringify(saved)).not.toContain("4111111111111111");
|
|
82
|
-
(0, _vitest.expect)(saved.messages[0].toolCalls[0].result).toBeUndefined();
|
|
83
|
-
(0, _vitest.expect)(saved.messages[0].toolCalls[0].action).toBe("getStoreState");
|
|
84
|
-
});
|
|
85
|
-
(0, _vitest.it)("scrubs credential shapes out of what the agent said", () => {
|
|
86
|
-
const saved = build([msg({
|
|
87
|
-
text: "The header was Bearer abcdefghijklmnop123456"
|
|
88
|
-
})]);
|
|
89
|
-
(0, _vitest.expect)(saved.messages[0].text).not.toContain("abcdefghijklmnop123456");
|
|
90
|
-
});
|
|
91
|
-
(0, _vitest.it)("caps a long session instead of growing a file forever", () => {
|
|
92
|
-
const many = Array.from({
|
|
93
|
-
length: 400
|
|
94
|
-
}, (_, i) => msg({
|
|
95
|
-
id: `m${i}`,
|
|
96
|
-
text: `answer ${i}`
|
|
97
|
-
}));
|
|
98
|
-
const saved = build(many);
|
|
99
|
-
(0, _vitest.expect)(saved.messages.length).toBeLessThanOrEqual(60);
|
|
100
|
-
// Newest kept, oldest dropped — the end of a session is the useful end.
|
|
101
|
-
(0, _vitest.expect)(saved.messages[saved.messages.length - 1].text).toBe("answer 399");
|
|
102
|
-
});
|
|
103
|
-
(0, _vitest.it)("drops from the oldest end until a heavy transcript fits", () => {
|
|
104
|
-
const heavy = Array.from({
|
|
105
|
-
length: 20
|
|
106
|
-
}, (_, i) => msg({
|
|
107
|
-
id: `m${i}`,
|
|
108
|
-
text: "x".repeat(40_000)
|
|
109
|
-
}));
|
|
110
|
-
const saved = (0, _transcriptPersistence.fitToCaps)({
|
|
111
|
-
v: 1,
|
|
112
|
-
savedAt: 0,
|
|
113
|
-
messages: heavy,
|
|
114
|
-
history: [],
|
|
115
|
-
answers: {},
|
|
116
|
-
usage: {
|
|
117
|
-
input: 0,
|
|
118
|
-
output: 0,
|
|
119
|
-
requests: 0
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
(0, _vitest.expect)(JSON.stringify(saved).length).toBeLessThanOrEqual(256_000);
|
|
123
|
-
(0, _vitest.expect)(saved.messages.length).toBeGreaterThan(0);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
(0, _vitest.describe)("what comes back", () => {
|
|
127
|
-
(0, _vitest.it)("marks a turn the app died in as interrupted rather than blank", () => {
|
|
128
|
-
// The crash case this feature exists for. An empty bubble with no
|
|
129
|
-
// explanation reads as the model having nothing to say.
|
|
130
|
-
const saved = build([msg({
|
|
131
|
-
text: "",
|
|
132
|
-
streaming: true
|
|
133
|
-
})]);
|
|
134
|
-
(0, _vitest.expect)(saved.messages[0].streaming).toBe(false);
|
|
135
|
-
(0, _vitest.expect)(saved.messages[0].error).toMatch(/interrupted/i);
|
|
136
|
-
});
|
|
137
|
-
(0, _vitest.it)("never restores a tool call still claiming to run", () => {
|
|
138
|
-
const saved = build([msg({
|
|
139
|
-
streaming: true,
|
|
140
|
-
toolCalls: [{
|
|
141
|
-
id: "c1",
|
|
142
|
-
toolId: "storage",
|
|
143
|
-
action: "set",
|
|
144
|
-
label: "Write a key",
|
|
145
|
-
status: "running"
|
|
146
|
-
}]
|
|
147
|
-
})]);
|
|
148
|
-
(0, _vitest.expect)(saved.messages[0].toolCalls[0].status).toBe("failed");
|
|
149
|
-
(0, _vitest.expect)(saved.messages[0].toolCalls[0].summary).toBe("Interrupted");
|
|
150
|
-
});
|
|
151
|
-
(0, _vitest.it)("keeps an answer that already finished exactly as it was", () => {
|
|
152
|
-
const saved = build([msg({
|
|
153
|
-
text: "Five items.",
|
|
154
|
-
streaming: false
|
|
155
|
-
})]);
|
|
156
|
-
(0, _vitest.expect)(saved.messages[0].error).toBeUndefined();
|
|
157
|
-
(0, _vitest.expect)(saved.messages[0].text).toBe("Five items.");
|
|
158
|
-
});
|
|
159
|
-
(0, _vitest.it)("treats anything it cannot trust as no transcript at all", () => {
|
|
160
|
-
(0, _vitest.expect)((0, _transcriptPersistence.parsePayload)(null)).toBeNull();
|
|
161
|
-
(0, _vitest.expect)((0, _transcriptPersistence.parsePayload)("not json")).toBeNull();
|
|
162
|
-
// An older shape is dropped, not coerced — a half-read transcript is worse
|
|
163
|
-
// than a missing one.
|
|
164
|
-
(0, _vitest.expect)((0, _transcriptPersistence.parsePayload)(JSON.stringify({
|
|
165
|
-
v: 0,
|
|
166
|
-
messages: [msg()]
|
|
167
|
-
}))).toBeNull();
|
|
168
|
-
(0, _vitest.expect)((0, _transcriptPersistence.parsePayload)(JSON.stringify({
|
|
169
|
-
v: 1,
|
|
170
|
-
messages: []
|
|
171
|
-
}))).toBeNull();
|
|
172
|
-
});
|
|
173
|
-
(0, _vitest.it)("round-trips a real conversation", () => {
|
|
174
|
-
const saved = build([msg({
|
|
175
|
-
role: "user",
|
|
176
|
-
text: "hi"
|
|
177
|
-
}), msg({
|
|
178
|
-
id: "m2",
|
|
179
|
-
text: "hello"
|
|
180
|
-
})]);
|
|
181
|
-
const back = (0, _transcriptPersistence.parsePayload)(JSON.stringify(saved));
|
|
182
|
-
(0, _vitest.expect)(back?.messages.map(m => m.text)).toEqual(["hi", "hello"]);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
//# sourceMappingURL=transcriptPersistence.test.js.map
|
|
1
|
+
"use strict";var _vitest=require("vitest"),_transcriptPersistence=require("./transcriptPersistence");const msg=(t={})=>({id:"m1",role:"assistant",text:"ok",toolCalls:[],blocks:[],...t}),build=(t,e=[])=>(0,_transcriptPersistence.buildPayload)({messages:t,history:e,answers:{},usage:{input:0,output:0,requests:0}});(0,_vitest.describe)("what reaches the disk",()=>{(0,_vitest.it)("never writes a tool result",()=>{const t=build([msg()],[{role:"user",text:"what's in the bag?"},{role:"assistant",text:"",toolCalls:[{id:"c1",name:"buoy_zustand",input:{}}]},{role:"tool-results",results:[{toolCallId:"c1",content:'{"lines":[{"card":"4111111111111111"}]}'}]},{role:"assistant",text:"Five items."}]),e=JSON.stringify(t);(0,_vitest.expect)(e).not.toContain("4111111111111111"),(0,_vitest.expect)(t.history.map(t=>t.role)).toEqual(["user","assistant","tool-results","assistant"])}),(0,_vitest.it)("never writes a step's result, even when it writes the step",()=>{const t=build([msg({toolCalls:[{id:"c1",toolId:"zustand",action:"getStoreState",label:"Read the bag",status:"ok",summary:"Done",params:'{"storeName":"bag"}',result:'{"card":"4111111111111111"}'}]})]);(0,_vitest.expect)(JSON.stringify(t)).not.toContain("4111111111111111"),(0,_vitest.expect)(t.messages[0].toolCalls[0].result).toBeUndefined(),(0,_vitest.expect)(t.messages[0].toolCalls[0].action).toBe("getStoreState")}),(0,_vitest.it)("scrubs credential shapes out of what the agent said",()=>{const t=build([msg({text:"The header was Bearer abcdefghijklmnop123456"})]);(0,_vitest.expect)(t.messages[0].text).not.toContain("abcdefghijklmnop123456")}),(0,_vitest.it)("caps a long session instead of growing a file forever",()=>{const t=Array.from({length:400},(t,e)=>msg({id:`m${e}`,text:`answer ${e}`})),e=build(t);(0,_vitest.expect)(e.messages.length).toBeLessThanOrEqual(60),(0,_vitest.expect)(e.messages[e.messages.length-1].text).toBe("answer 399")}),(0,_vitest.it)("drops from the oldest end until a heavy transcript fits",()=>{const t=Array.from({length:20},(t,e)=>msg({id:`m${e}`,text:"x".repeat(4e4)})),e=(0,_transcriptPersistence.fitToCaps)({v:1,savedAt:0,messages:t,history:[],answers:{},usage:{input:0,output:0,requests:0}});(0,_vitest.expect)(JSON.stringify(e).length).toBeLessThanOrEqual(256e3),(0,_vitest.expect)(e.messages.length).toBeGreaterThan(0)})}),(0,_vitest.describe)("what comes back",()=>{(0,_vitest.it)("marks a turn the app died in as interrupted rather than blank",()=>{const t=build([msg({text:"",streaming:!0})]);(0,_vitest.expect)(t.messages[0].streaming).toBe(!1),(0,_vitest.expect)(t.messages[0].error).toMatch(/interrupted/i)}),(0,_vitest.it)("never restores a tool call still claiming to run",()=>{const t=build([msg({streaming:!0,toolCalls:[{id:"c1",toolId:"storage",action:"set",label:"Write a key",status:"running"}]})]);(0,_vitest.expect)(t.messages[0].toolCalls[0].status).toBe("failed"),(0,_vitest.expect)(t.messages[0].toolCalls[0].summary).toBe("Interrupted")}),(0,_vitest.it)("keeps an answer that already finished exactly as it was",()=>{const t=build([msg({text:"Five items.",streaming:!1})]);(0,_vitest.expect)(t.messages[0].error).toBeUndefined(),(0,_vitest.expect)(t.messages[0].text).toBe("Five items.")}),(0,_vitest.it)("treats anything it cannot trust as no transcript at all",()=>{(0,_vitest.expect)((0,_transcriptPersistence.parsePayload)(null)).toBeNull(),(0,_vitest.expect)((0,_transcriptPersistence.parsePayload)("not json")).toBeNull(),(0,_vitest.expect)((0,_transcriptPersistence.parsePayload)(JSON.stringify({v:0,messages:[msg()]}))).toBeNull(),(0,_vitest.expect)((0,_transcriptPersistence.parsePayload)(JSON.stringify({v:1,messages:[]}))).toBeNull()}),(0,_vitest.it)("round-trips a real conversation",()=>{const t=build([msg({role:"user",text:"hi"}),msg({id:"m2",text:"hello"})]),e=(0,_transcriptPersistence.parsePayload)(JSON.stringify(t));(0,_vitest.expect)(e?.messages.map(t=>t.text)).toEqual(["hi","hello"])})}),(0,_vitest.describe)("an unanswered approval",()=>{const t={id:"c1",toolId:"zustand",action:"setState",label:"Update the Poké Mart bag store",description:"Change a live zustand store.",reason:"This will change the app's state.",params:{storeName:"bag",data:{lines:[]}},resolve:()=>{}},e=(e={})=>(0,_transcriptPersistence.buildPayload)({messages:[msg()],history:[],answers:{},usage:{input:0,output:0,requests:0},pending:{...t,...e}});(0,_vitest.it)("survives the round trip with the exact call it asked about",()=>{const t=(0,_transcriptPersistence.parsePayload)(JSON.stringify(e()));(0,_vitest.expect)(t?.pending).toMatchObject({toolId:"zustand",action:"setState",label:"Update the Poké Mart bag store",params:{storeName:"bag",data:{lines:[]}}})}),(0,_vitest.it)("is saved again after it has already been restored once",()=>{(0,_vitest.expect)(e({restored:!0}).pending).toBeTruthy()}),(0,_vitest.it)("is dropped rather than scrubbed when the call carries a credential",()=>{const t=e({params:{key:"auth",value:"Bearer abcdefghijklmnop123456"}});(0,_vitest.expect)(t.pending).toBeNull(),(0,_vitest.expect)(JSON.stringify(t)).not.toContain("abcdefghijklmnop123456")}),(0,_vitest.it)("reads back as absent from a transcript written before it existed",()=>{const t=build([msg()]);(0,_vitest.expect)((0,_transcriptPersistence.parsePayload)(JSON.stringify(t))?.pending).toBeNull()}),(0,_vitest.it)("refuses a malformed one rather than restoring half a question",()=>{const t=JSON.stringify({...build([msg()]),pending:{toolId:"zustand"}});(0,_vitest.expect)((0,_transcriptPersistence.parsePayload)(t)?.pending).toBeNull()})}),(0,_vitest.describe)("the effect ledger",()=>{const t={id:"fx-1-1",at:1,toolId:"storage",action:"async.setItem",effect:{kind:"storage-write",toolId:"storage",action:"async.setItem",label:"Save @qa/key",key:"@qa/key",had:!0,previous:"old value"}},e=t=>(0,_transcriptPersistence.buildPayload)({messages:[msg()],history:[],answers:{},usage:{input:0,output:0,requests:0},ledger:t});(0,_vitest.it)("survives the round trip with the prior value an undo needs",()=>{const s=(0,_transcriptPersistence.parsePayload)(JSON.stringify(e([t])));(0,_vitest.expect)(s?.ledger).toHaveLength(1),(0,_vitest.expect)(s?.ledger?.[0]).toMatchObject({toolId:"storage",action:"async.setItem"})}),(0,_vitest.it)("drops an entry holding a credential rather than rewriting it",()=>{const s={...t,effect:{...t.effect,previous:"Bearer abcdefghijklmnop123456"}},a=e([s]);(0,_vitest.expect)(a.ledger).toBeUndefined(),(0,_vitest.expect)(JSON.stringify(a)).not.toContain("abcdefghijklmnop123456")}),(0,_vitest.it)("reads back as absent from a transcript written before it existed",()=>{(0,_vitest.expect)((0,_transcriptPersistence.parsePayload)(JSON.stringify(build([msg()])))?.ledger).toBeUndefined()})});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var _vitest=require("vitest"),_askBuoyStore=require("./askBuoyStore");(0,_vitest.beforeEach)(()=>_askBuoyStore.askBuoyStore.reset());const lastText=()=>_askBuoyStore.askBuoyStore.getState().messages.at(-1)?.text??"";(0,_vitest.describe)("round boundaries",()=>{(0,_vitest.it)("opens a paragraph when the engine says a new round began",()=>{const t=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(t,{type:"text",delta:"Wiping storage now."}),_askBuoyStore.askBuoyStore.applyEvent(t,{type:"text",delta:"No problem — I left it alone.",breakBefore:!0}),(0,_vitest.expect)(lastText()).toBe("Wiping storage now.\n\nNo problem — I left it alone.")}),(0,_vitest.it)("still streams deltas within one round straight together",()=>{const t=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(t,{type:"text",delta:"Hello"}),_askBuoyStore.askBuoyStore.applyEvent(t,{type:"text",delta:" there."}),(0,_vitest.expect)(lastText()).toBe("Hello there.")}),(0,_vitest.it)("never opens with a blank line",()=>{const t=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(t,{type:"text",delta:"First words.",breakBefore:!0}),(0,_vitest.expect)(lastText()).toBe("First words.")})}),(0,_vitest.describe)("a declined call",()=>{(0,_vitest.it)("keeps its row and reads as declined, not failed",()=>{const t=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(t,{type:"tool-start",id:"c1",toolId:"storage",action:"clearAll",label:"Clear all saved app data",description:"Wipe every stored key",params:{},effect:"destructive"}),_askBuoyStore.askBuoyStore.applyEvent(t,{type:"tool-end",id:"c1",ok:!1,summary:"declined",durationMs:0}),(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().messages.at(-1)?.toolCalls[0]).toMatchObject({label:"Clear all saved app data",status:"declined"})})}),(0,_vitest.describe)("stop",()=>{(0,_vitest.it)("marks the turn stopped and leaves no error on it",()=>{const t=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.applyEvent(t,{type:"text",delta:"Looking…"}),_askBuoyStore.askBuoyStore.markStopped(t);const e=_askBuoyStore.askBuoyStore.getState().messages.at(-1);(0,_vitest.expect)(e?.stopped).toBe(!0),(0,_vitest.expect)(e?.error).toBeUndefined()})}),(0,_vitest.describe)("stop and the queue",()=>{(0,_vitest.it)("keeps parked messages parked, and says why",()=>{_askBuoyStore.askBuoyStore.addUser("first");const t=_askBuoyStore.askBuoyStore.startAssistant();_askBuoyStore.askBuoyStore.enqueue("do this next"),_askBuoyStore.askBuoyStore.requestStop(),_askBuoyStore.askBuoyStore.markStopped(t),_askBuoyStore.askBuoyStore.finishAssistant(t);const e=_askBuoyStore.askBuoyStore.getState();(0,_vitest.expect)(e.queued.map(t=>t.text)).toEqual(["do this next"]),(0,_vitest.expect)((0,_askBuoyStore.queuePauseReason)(e)).toMatch(/stopped/)}),(0,_vitest.it)("hands one parked message back to the composer to edit",()=>{const t=_askBuoyStore.askBuoyStore.enqueue("words");_askBuoyStore.askBuoyStore.takeQueuedToDraft(t.id),(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().queued).toEqual([]),(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().draft).toBe("words"),_askBuoyStore.askBuoyStore.clearDraft(),(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().draft).toBeNull()})}),(0,_vitest.describe)("hydrate",()=>{(0,_vitest.it)("marks what it puts back",()=>{_askBuoyStore.askBuoyStore.hydrate({messages:[{id:"m1",role:"assistant",text:"hi",toolCalls:[],blocks:[]}],answers:{},usage:{input:0,output:0,requests:0}}),(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().messages[0]?.restored).toBe(!0)}),(0,_vitest.it)("leaves a live message unmarked",()=>{_askBuoyStore.askBuoyStore.addUser("live one"),(0,_vitest.expect)(_askBuoyStore.askBuoyStore.getState().messages[0]?.restored).toBeUndefined()})});
|