@runtypelabs/persona 4.21.1 → 4.22.1
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/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-B_JdVK9o.d.cts → types-9Vxm-PPz.d.cts} +136 -6
- package/dist/animations/{types-B_JdVK9o.d.ts → types-9Vxm-PPz.d.ts} +136 -6
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +136 -6
- package/dist/animations-extra.d.ts +136 -6
- package/dist/{approval-ui-RNNBR5R3.js → approval-ui-K7R5Q5OC.js} +1 -1
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +221 -16
- package/dist/approval-ui.d.ts +221 -16
- package/dist/approval-ui.js +2 -2
- package/dist/artifacts-ui-F4N6VMKF.js +7 -0
- package/dist/artifacts-ui.cjs +1 -1
- package/dist/artifacts-ui.d.cts +221 -16
- package/dist/artifacts-ui.d.ts +221 -16
- package/dist/artifacts-ui.js +1 -1
- package/dist/chunk-NQLDCJAP.js +4 -0
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +221 -16
- package/dist/context-mentions.d.ts +221 -16
- package/dist/event-stream-view-GL3ADRY2.js +1 -0
- package/dist/event-stream-view.cjs +1 -1
- package/dist/event-stream-view.d.cts +221 -16
- package/dist/event-stream-view.d.ts +221 -16
- package/dist/event-stream-view.js +1 -1
- package/dist/forms-ui.d.cts +234 -25
- package/dist/forms-ui.d.ts +234 -25
- package/dist/history-view.d.cts +136 -6
- package/dist/history-view.d.ts +136 -6
- package/dist/index.cjs +70 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +302 -32
- package/dist/index.d.ts +302 -32
- package/dist/index.global.js +54 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +69 -69
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +221 -16
- package/dist/session-reconnect.d.ts +221 -16
- package/dist/smart-dom-reader.d.cts +221 -16
- package/dist/smart-dom-reader.d.ts +221 -16
- package/dist/testing.cjs +3 -3
- package/dist/testing.d.cts +3 -4
- package/dist/testing.d.ts +3 -4
- package/dist/testing.js +3 -3
- package/dist/theme-editor-preview.cjs +73 -73
- package/dist/theme-editor-preview.d.cts +221 -16
- package/dist/theme-editor-preview.d.ts +221 -16
- package/dist/theme-editor-preview.js +51 -51
- package/dist/theme-editor.cjs +4 -4
- package/dist/theme-editor.d.cts +221 -16
- package/dist/theme-editor.d.ts +221 -16
- package/dist/theme-editor.js +2 -2
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +65 -1
- package/dist/theme-reference.d.ts +65 -1
- package/dist/theme-reference.js +1 -1
- package/dist/voice-runtime.d.cts +6 -2
- package/dist/voice-runtime.d.ts +6 -2
- package/dist/webmcp-polyfill.js +1 -4
- package/dist/webmcp-runtime-JI7T4QYF.js +6 -0
- package/dist/webmcp-runtime.cjs +2 -2
- package/dist/webmcp-runtime.d.cts +34 -2
- package/dist/webmcp-runtime.d.ts +34 -2
- package/dist/webmcp-runtime.js +2 -2
- package/package.json +2 -2
- package/src/ask-user-question-tool.ts +1 -1
- package/src/client-history-rest.test.ts +2 -1
- package/src/client-visitor-history.test.ts +20 -4
- package/src/client.composer-options.test.ts +1 -1
- package/src/client.interrupt.test.ts +2 -2
- package/src/client.test.ts +477 -915
- package/src/client.ts +134 -494
- package/src/client.unified-tools.test.ts +76 -0
- package/src/components/artifact-preview.ts +5 -0
- package/src/components/event-stream-badge-theming.test.ts +162 -0
- package/src/components/event-stream-view.test.ts +76 -76
- package/src/components/event-stream-view.ts +55 -20
- package/src/components/tool-bubble.test.ts +12 -0
- package/src/components/tool-bubble.ts +13 -2
- package/src/event-stream-view-bundle.test.ts +2 -2
- package/src/generated/runtype-openapi-contract.ts +137 -1
- package/src/index-core.ts +2 -0
- package/src/internal/runtype-history-provider.test.ts +2 -2
- package/src/runtime/init.registry.test.ts +56 -0
- package/src/runtime/init.ts +17 -0
- package/src/session-history.test.ts +1 -1
- package/src/session.quote.test.ts +1 -1
- package/src/session.resubmit-from.test.ts +1 -1
- package/src/session.test.ts +5 -5
- package/src/session.ts +95 -36
- package/src/suggest-replies-tool.ts +1 -1
- package/src/testing/mock-stream.test.ts +37 -16
- package/src/testing/mock-stream.ts +14 -9
- package/src/types/theme.ts +41 -1
- package/src/types.ts +57 -15
- package/src/ui.ask-user-question-plugin.test.ts +7 -7
- package/src/ui.composer-defer-one.test.ts +6 -34
- package/src/ui.composer-draft-persistence.test.ts +1 -1
- package/src/ui.composer-quote.test.ts +1 -1
- package/src/ui.message-history-actions.test.ts +1 -1
- package/src/ui.reasoning-display.test.ts +136 -2
- package/src/ui.streaming-coalescing.test.ts +10 -13
- package/src/ui.ts +101 -35
- package/src/ui.typing-indicator.test.ts +216 -0
- package/src/ui.voice-custom-provider.test.ts +211 -0
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1740 -0
- package/src/utils/event-stream-buffer.test.ts +13 -13
- package/src/utils/event-stream-capture.test.ts +20 -20
- package/src/utils/formatting.ts +22 -0
- package/src/utils/theme.ts +16 -0
- package/src/utils/throughput-tracker.test.ts +100 -142
- package/src/utils/throughput-tracker.ts +18 -132
- package/src/utils/tokens.ts +59 -0
- package/src/utils/voice-support.ts +18 -1
- package/src/voice/voice-turn-tracker.ts +18 -0
- package/src/webmcp-bridge.test.ts +132 -23
- package/src/webmcp-bridge.ts +27 -3
- package/src/webmcp-runtime-entry.ts +71 -11
- package/dist/artifacts-ui-J6AKUGTS.js +0 -7
- package/dist/chunk-HBE7LFNT.js +0 -4
- package/dist/event-stream-view-73NWKWVQ.js +0 -1
- package/dist/webmcp-runtime-P3CHZRHF.js +0 -6
- package/src/utils/__fixtures__/unified-translator.oracle.ts +0 -717
package/src/types.ts
CHANGED
|
@@ -826,6 +826,14 @@ export type ClientToolDefinition = {
|
|
|
826
826
|
readOnlyHint?: boolean;
|
|
827
827
|
untrustedContentHint?: boolean;
|
|
828
828
|
};
|
|
829
|
+
/**
|
|
830
|
+
* Hoisted copy of `annotations.untrustedContentHint`. This is the field the
|
|
831
|
+
* server actually acts on (`InlineClientToolSchema`): it spotlights the
|
|
832
|
+
* tool's output before the model reads it, so instructions embedded in
|
|
833
|
+
* page-provided content (reviews, comments, emails) are not treated as
|
|
834
|
+
* instructions. `annotations` itself is trace/dashboard metadata only.
|
|
835
|
+
*/
|
|
836
|
+
untrustedContentHint?: boolean;
|
|
829
837
|
};
|
|
830
838
|
|
|
831
839
|
/**
|
|
@@ -983,15 +991,15 @@ export type AgentMessageMetadata = {
|
|
|
983
991
|
*/
|
|
984
992
|
parentStepId?: string;
|
|
985
993
|
/**
|
|
986
|
-
* Set to `true` on a tool-variant message produced from
|
|
994
|
+
* Set to `true` on a tool-variant message produced from an `await`
|
|
987
995
|
* event (`awaitReason: "local_tool_required"`). Signals to UI code that
|
|
988
996
|
* the tool call is a LOCAL tool and the server is paused waiting for a
|
|
989
997
|
* `POST /v1/dispatch/resume` with the user's answer keyed by tool name.
|
|
990
998
|
*/
|
|
991
999
|
awaitingLocalTool?: boolean;
|
|
992
1000
|
/**
|
|
993
|
-
* The provider per-call id (`toolu_…`) carried on the `
|
|
994
|
-
*
|
|
1001
|
+
* The provider per-call id (`toolu_…`) carried on the `await`
|
|
1002
|
+
* event for a LOCAL tool (core#3878). Present only when the
|
|
995
1003
|
* server emits it. Two PARALLEL calls to the same tool in one turn share a
|
|
996
1004
|
* `toolName` (and a collapsed `toolId`) but get DISTINCT `webMcpToolCallId`s,
|
|
997
1005
|
* so this is the key the widget batches a single `/resume` on: preferred
|
|
@@ -2237,6 +2245,16 @@ export type AgentWidgetToolCallDisplayFeature = {
|
|
|
2237
2245
|
* @default "stack"
|
|
2238
2246
|
*/
|
|
2239
2247
|
groupedMode?: "stack" | "summary";
|
|
2248
|
+
/**
|
|
2249
|
+
* What happens to a tool call row once it completes.
|
|
2250
|
+
*
|
|
2251
|
+
* - `"kept"` (default): the finished row stays in the transcript.
|
|
2252
|
+
* - `"removed"`: no row is rendered at all, so the transcript gap closes with
|
|
2253
|
+
* it. The tool call stays on the message and in `getMessages()`.
|
|
2254
|
+
*
|
|
2255
|
+
* @default "kept"
|
|
2256
|
+
*/
|
|
2257
|
+
completedVisibility?: AgentWidgetToolCallCompletedVisibility;
|
|
2240
2258
|
/**
|
|
2241
2259
|
* When false, tool call bubbles show only the collapsed summary with no
|
|
2242
2260
|
* expand/collapse toggle. Users see tool awareness without full details.
|
|
@@ -2255,6 +2273,9 @@ export type AgentWidgetToolCallDisplayFeature = {
|
|
|
2255
2273
|
loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
|
|
2256
2274
|
};
|
|
2257
2275
|
|
|
2276
|
+
/** See `features.toolCallDisplay.completedVisibility`. */
|
|
2277
|
+
export type AgentWidgetToolCallCompletedVisibility = "kept" | "removed";
|
|
2278
|
+
|
|
2258
2279
|
export type AgentWidgetReasoningDisplayFeature = {
|
|
2259
2280
|
/**
|
|
2260
2281
|
* When true, active collapsed reasoning rows can render a lightweight preview block.
|
|
@@ -2300,14 +2321,30 @@ export type AgentWidgetReasoningDisplayFeature = {
|
|
|
2300
2321
|
* - `"kept"` (default): the finished row stays in the transcript.
|
|
2301
2322
|
* - `"removed"`: no row is rendered at all, so the transcript gap closes with
|
|
2302
2323
|
* it. The reasoning text stays on the message and in `getMessages()`.
|
|
2324
|
+
* - `"removed-when-short"`: behaves as `"removed"` when the completed trace
|
|
2325
|
+
* is short (see `shortThinkThreshold`), and as `"kept"` otherwise.
|
|
2303
2326
|
*
|
|
2304
2327
|
* @default "kept"
|
|
2305
2328
|
*/
|
|
2306
2329
|
completedVisibility?: AgentWidgetReasoningCompletedVisibility;
|
|
2330
|
+
/**
|
|
2331
|
+
* Thresholds used by `completedVisibility: "removed-when-short"` to decide
|
|
2332
|
+
* whether a completed trace counts as short. Duration is checked first when
|
|
2333
|
+
* the reasoning reports one; the character count is a fallback for traces
|
|
2334
|
+
* with no reported duration.
|
|
2335
|
+
* @default { durationMs: 15000, chars: 1200 }
|
|
2336
|
+
*/
|
|
2337
|
+
shortThinkThreshold?: {
|
|
2338
|
+
durationMs?: number;
|
|
2339
|
+
chars?: number;
|
|
2340
|
+
};
|
|
2307
2341
|
};
|
|
2308
2342
|
|
|
2309
2343
|
/** See `features.reasoningDisplay.completedVisibility`. */
|
|
2310
|
-
export type AgentWidgetReasoningCompletedVisibility =
|
|
2344
|
+
export type AgentWidgetReasoningCompletedVisibility =
|
|
2345
|
+
| "kept"
|
|
2346
|
+
| "removed"
|
|
2347
|
+
| "removed-when-short";
|
|
2311
2348
|
|
|
2312
2349
|
/**
|
|
2313
2350
|
* Reveal animation applied to assistant message text while it is streaming.
|
|
@@ -3381,7 +3418,7 @@ export type EventStreamConfig = {
|
|
|
3381
3418
|
/**
|
|
3382
3419
|
* Custom badge color mappings by event type prefix or exact type.
|
|
3383
3420
|
* Keys are matched as exact match first, then prefix match (keys ending with "_").
|
|
3384
|
-
* @example { "
|
|
3421
|
+
* @example { "execution_": { bg: "#dcfce7", text: "#166534" }, "error": { bg: "#fecaca", text: "#991b1b" } }
|
|
3385
3422
|
*/
|
|
3386
3423
|
badgeColors?: Record<string, EventStreamBadgeColor>;
|
|
3387
3424
|
/**
|
|
@@ -4946,10 +4983,12 @@ export interface VoiceProvider {
|
|
|
4946
4983
|
* `isFinal=false` is a live interim update (user partials, or assistant deltas
|
|
4947
4984
|
* on providers that stream them); `isFinal=true` finalizes that role's text.
|
|
4948
4985
|
* On the realtime `runtype` path, interim updates fire for the `user` only and
|
|
4949
|
-
* the `assistant` arrives as a single final.
|
|
4986
|
+
* the `assistant` arrives as a single final. Providers with overlapping turns
|
|
4987
|
+
* must include the same turnId on user and assistant finals; without IDs,
|
|
4988
|
+
* providers must discard cancelled output before emitting the next user final.
|
|
4950
4989
|
*/
|
|
4951
4990
|
onTranscript?(
|
|
4952
|
-
callback: (role: 'user' | 'assistant', text: string, isFinal: boolean) => void,
|
|
4991
|
+
callback: (role: 'user' | 'assistant', text: string, isFinal: boolean, metadata?: { turnId?: string }) => void,
|
|
4953
4992
|
): void;
|
|
4954
4993
|
|
|
4955
4994
|
/** Register a callback for per-turn latency metrics (realtime path). */
|
|
@@ -7524,16 +7563,16 @@ export type AgentWidgetConfig = {
|
|
|
7524
7563
|
*
|
|
7525
7564
|
* @example
|
|
7526
7565
|
* ```typescript
|
|
7527
|
-
* // For Runtype
|
|
7566
|
+
* // For a custom backend (Runtype unified events are handled natively)
|
|
7528
7567
|
* config: {
|
|
7529
7568
|
* parseSSEEvent: (data) => {
|
|
7530
|
-
* if (
|
|
7531
|
-
* return { text: data.
|
|
7569
|
+
* if (data.type === 'token' && typeof data.value === 'string') {
|
|
7570
|
+
* return { text: data.value };
|
|
7532
7571
|
* }
|
|
7533
|
-
* if (data.type === '
|
|
7572
|
+
* if (data.type === 'done') {
|
|
7534
7573
|
* return { done: true };
|
|
7535
7574
|
* }
|
|
7536
|
-
* if (data.type === '
|
|
7575
|
+
* if (data.type === 'failure') {
|
|
7537
7576
|
* return { error: data.error };
|
|
7538
7577
|
* }
|
|
7539
7578
|
* return null; // Ignore other events
|
|
@@ -7750,7 +7789,7 @@ export type AgentWidgetReasoning = {
|
|
|
7750
7789
|
/**
|
|
7751
7790
|
* Reasoning channel scope (wire spec). `"turn"` is ordinary per-turn
|
|
7752
7791
|
* thinking; `"loop"` is a cross-iteration agent reflection (the fold that
|
|
7753
|
-
* replaced the legacy `agent_reflection` event).
|
|
7792
|
+
* replaced the legacy `agent_reflection` event). Optional when the stream does not specify a scope.
|
|
7754
7793
|
*/
|
|
7755
7794
|
scope?: "turn" | "loop";
|
|
7756
7795
|
startedAt?: number;
|
|
@@ -7765,6 +7804,9 @@ export type AgentWidgetToolCall = {
|
|
|
7765
7804
|
args?: unknown;
|
|
7766
7805
|
chunks?: string[];
|
|
7767
7806
|
result?: unknown;
|
|
7807
|
+
/** Whether the tool succeeded; failure does not necessarily end the execution. */
|
|
7808
|
+
success?: boolean;
|
|
7809
|
+
error?: string;
|
|
7768
7810
|
duration?: number;
|
|
7769
7811
|
startedAt?: number;
|
|
7770
7812
|
completedAt?: number;
|
|
@@ -7798,7 +7840,7 @@ export type AgentWidgetMessageVariant = "assistant" | "reasoning" | "tool" | "ap
|
|
|
7798
7840
|
|
|
7799
7841
|
/**
|
|
7800
7842
|
* Per-turn / per-step stop reason emitted by the runtime on
|
|
7801
|
-
* `
|
|
7843
|
+
* `turn_complete` and `step_complete` SSE events. The vocabulary is
|
|
7802
7844
|
* owned by the upstream Runtype API: do not extend without coordination.
|
|
7803
7845
|
*
|
|
7804
7846
|
* - `end_turn`: natural completion (no affordance needed)
|
|
@@ -7930,7 +7972,7 @@ export type AgentWidgetMessage = {
|
|
|
7930
7972
|
*/
|
|
7931
7973
|
agentMetadata?: AgentMessageMetadata;
|
|
7932
7974
|
/**
|
|
7933
|
-
* Per-turn stop reason reported by the runtime on `
|
|
7975
|
+
* Per-turn stop reason reported by the runtime on `turn_complete`
|
|
7934
7976
|
* (agent-loop path) or the last `step_complete` for a prompt step
|
|
7935
7977
|
* (dispatch / flow path). Absent when the API did not report a value.
|
|
7936
7978
|
*
|
|
@@ -174,7 +174,7 @@ describe("renderAskUserQuestion plugin hook", () => {
|
|
|
174
174
|
ok: true,
|
|
175
175
|
body: new ReadableStream({
|
|
176
176
|
start(c) {
|
|
177
|
-
c.enqueue(new TextEncoder().encode('data: {"type":"
|
|
177
|
+
c.enqueue(new TextEncoder().encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
178
178
|
c.close();
|
|
179
179
|
},
|
|
180
180
|
}),
|
|
@@ -217,7 +217,7 @@ describe("renderAskUserQuestion plugin hook", () => {
|
|
|
217
217
|
ok: true,
|
|
218
218
|
body: new ReadableStream({
|
|
219
219
|
start(c) {
|
|
220
|
-
c.enqueue(new TextEncoder().encode('data: {"type":"
|
|
220
|
+
c.enqueue(new TextEncoder().encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
221
221
|
c.close();
|
|
222
222
|
},
|
|
223
223
|
}),
|
|
@@ -284,7 +284,7 @@ describe("renderAskUserQuestion plugin hook", () => {
|
|
|
284
284
|
ok: true,
|
|
285
285
|
body: new ReadableStream({
|
|
286
286
|
start(c) {
|
|
287
|
-
c.enqueue(new TextEncoder().encode('data: {"type":"
|
|
287
|
+
c.enqueue(new TextEncoder().encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
288
288
|
c.close();
|
|
289
289
|
},
|
|
290
290
|
}),
|
|
@@ -366,7 +366,7 @@ describe("renderAskUserQuestion plugin hook", () => {
|
|
|
366
366
|
ok: true,
|
|
367
367
|
body: new ReadableStream({
|
|
368
368
|
start(c) {
|
|
369
|
-
c.enqueue(new TextEncoder().encode('data: {"type":"
|
|
369
|
+
c.enqueue(new TextEncoder().encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
370
370
|
c.close();
|
|
371
371
|
},
|
|
372
372
|
}),
|
|
@@ -434,7 +434,7 @@ describe("renderAskUserQuestion plugin hook", () => {
|
|
|
434
434
|
ok: true,
|
|
435
435
|
body: new ReadableStream({
|
|
436
436
|
start(c) {
|
|
437
|
-
c.enqueue(new TextEncoder().encode('data: {"type":"
|
|
437
|
+
c.enqueue(new TextEncoder().encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
438
438
|
c.close();
|
|
439
439
|
},
|
|
440
440
|
}),
|
|
@@ -631,7 +631,7 @@ describe("renderAskUserQuestion plugin hook", () => {
|
|
|
631
631
|
ok: true,
|
|
632
632
|
body: new ReadableStream({
|
|
633
633
|
start(c) {
|
|
634
|
-
c.enqueue(new TextEncoder().encode('data: {"type":"
|
|
634
|
+
c.enqueue(new TextEncoder().encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
635
635
|
c.close();
|
|
636
636
|
},
|
|
637
637
|
}),
|
|
@@ -645,7 +645,7 @@ describe("renderAskUserQuestion plugin hook", () => {
|
|
|
645
645
|
ok: true,
|
|
646
646
|
body: new ReadableStream({
|
|
647
647
|
start(c) {
|
|
648
|
-
c.enqueue(new TextEncoder().encode('data: {"type":"
|
|
648
|
+
c.enqueue(new TextEncoder().encode('data: {"type":"execution_complete","success":true}\n\n'));
|
|
649
649
|
c.close();
|
|
650
650
|
},
|
|
651
651
|
}),
|
|
@@ -3,42 +3,14 @@
|
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
|
|
5
5
|
import { createAgentExperience } from "./ui";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const legacyEvent = (type: string, data: Record<string, unknown>) =>
|
|
9
|
-
`event: ${type}\ndata: ${JSON.stringify({ type, ...data })}\n\n`;
|
|
6
|
+
import { buildAssistantTurnFrames } from "./testing/mock-stream";
|
|
10
7
|
|
|
11
8
|
/** One complete assistant turn, in the same wire shape the widget expects. */
|
|
12
|
-
const unifiedFrames = (): string =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
write(legacyEvent("flow_start", { flowId: "flow_1", flowName: "T", totalSteps: 1 }));
|
|
18
|
-
write(
|
|
19
|
-
legacyEvent("step_start", {
|
|
20
|
-
id: "step_1",
|
|
21
|
-
name: "Prompt",
|
|
22
|
-
stepType: "prompt",
|
|
23
|
-
index: 0,
|
|
24
|
-
totalSteps: 1,
|
|
25
|
-
})
|
|
26
|
-
);
|
|
27
|
-
write(legacyEvent("text_start", { messageId: "message_1" }));
|
|
28
|
-
write(legacyEvent("step_delta", { id: "step_1", text: "answer" }));
|
|
29
|
-
write(legacyEvent("text_end", { messageId: "message_1" }));
|
|
30
|
-
write(
|
|
31
|
-
legacyEvent("step_complete", {
|
|
32
|
-
id: "step_1",
|
|
33
|
-
name: "Prompt",
|
|
34
|
-
stepType: "prompt",
|
|
35
|
-
success: true,
|
|
36
|
-
result: { response: "answer" },
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
write(legacyEvent("flow_complete", { flowId: "flow_1", success: true }));
|
|
40
|
-
return out;
|
|
41
|
-
};
|
|
9
|
+
const unifiedFrames = (): string => [
|
|
10
|
+
{ type: "execution_start", executionId: "exec-1", kind: "agent" },
|
|
11
|
+
...buildAssistantTurnFrames({ executionId: "exec-1", text: "answer" }),
|
|
12
|
+
{ type: "execution_complete", executionId: "exec-1", kind: "agent", success: true },
|
|
13
|
+
].map((frame) => `data: ${JSON.stringify(frame)}\n\n`).join("");
|
|
42
14
|
|
|
43
15
|
const mounts: HTMLElement[] = [];
|
|
44
16
|
const controllers: ReturnType<typeof createAgentExperience>[] = [];
|
|
@@ -17,7 +17,7 @@ const streamResponse = () => {
|
|
|
17
17
|
body: new ReadableStream({
|
|
18
18
|
start(controller) {
|
|
19
19
|
controller.enqueue(
|
|
20
|
-
encoder.encode('data: {"type":"
|
|
20
|
+
encoder.encode('data: {"type":"execution_complete","success":true}\n\n')
|
|
21
21
|
);
|
|
22
22
|
controller.close();
|
|
23
23
|
},
|
|
@@ -17,7 +17,7 @@ const streamResponse = () => {
|
|
|
17
17
|
body: new ReadableStream({
|
|
18
18
|
start(controller) {
|
|
19
19
|
controller.enqueue(
|
|
20
|
-
encoder.encode('data: {"type":"
|
|
20
|
+
encoder.encode('data: {"type":"execution_complete","success":true}\n\n')
|
|
21
21
|
);
|
|
22
22
|
controller.close();
|
|
23
23
|
},
|
|
@@ -17,7 +17,7 @@ const streamResponse = () => {
|
|
|
17
17
|
body: new ReadableStream({
|
|
18
18
|
start(controller) {
|
|
19
19
|
controller.enqueue(
|
|
20
|
-
encoder.encode('data: {"type":"
|
|
20
|
+
encoder.encode('data: {"type":"execution_complete","success":true}\n\n')
|
|
21
21
|
);
|
|
22
22
|
controller.close();
|
|
23
23
|
},
|
|
@@ -25,7 +25,8 @@ const injectReasoning = (
|
|
|
25
25
|
controller: ReturnType<typeof createAgentExperience>,
|
|
26
26
|
id: string,
|
|
27
27
|
status: "streaming" | "complete",
|
|
28
|
-
chunks: string[] = ["Weighing the options"]
|
|
28
|
+
chunks: string[] = ["Weighing the options"],
|
|
29
|
+
durationMs?: number
|
|
29
30
|
) => {
|
|
30
31
|
controller.injectTestMessage({
|
|
31
32
|
type: "message",
|
|
@@ -36,7 +37,26 @@ const injectReasoning = (
|
|
|
36
37
|
createdAt: new Date().toISOString(),
|
|
37
38
|
streaming: status !== "complete",
|
|
38
39
|
variant: "reasoning",
|
|
39
|
-
reasoning: { id, status, chunks },
|
|
40
|
+
reasoning: { id, status, chunks, durationMs },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const injectToolCall = (
|
|
46
|
+
controller: ReturnType<typeof createAgentExperience>,
|
|
47
|
+
id: string,
|
|
48
|
+
status: "pending" | "running" | "complete"
|
|
49
|
+
) => {
|
|
50
|
+
controller.injectTestMessage({
|
|
51
|
+
type: "message",
|
|
52
|
+
message: {
|
|
53
|
+
id,
|
|
54
|
+
role: "assistant",
|
|
55
|
+
content: "",
|
|
56
|
+
createdAt: new Date().toISOString(),
|
|
57
|
+
streaming: status !== "complete",
|
|
58
|
+
variant: "tool",
|
|
59
|
+
toolCall: { id, name: "search", status, chunks: [] },
|
|
40
60
|
},
|
|
41
61
|
});
|
|
42
62
|
};
|
|
@@ -161,4 +181,118 @@ describe("features.reasoningDisplay.completedVisibility", () => {
|
|
|
161
181
|
?.chunks
|
|
162
182
|
).toEqual(["Weighing the options"]);
|
|
163
183
|
});
|
|
184
|
+
|
|
185
|
+
it('"removed-when-short" drops a completed trace with a short reported duration', () => {
|
|
186
|
+
const { mount, controller } = makeController({
|
|
187
|
+
features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
|
|
188
|
+
});
|
|
189
|
+
injectReasoning(controller, "r1", "complete", ["Weighing the options"], 5_000);
|
|
190
|
+
expect(rowOf(mount, "r1")).toBeNull();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('"removed-when-short" keeps a completed trace with a long reported duration', () => {
|
|
194
|
+
const { mount, controller } = makeController({
|
|
195
|
+
features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
|
|
196
|
+
});
|
|
197
|
+
injectReasoning(controller, "r1", "complete", ["Weighing the options"], 20_000);
|
|
198
|
+
expect(rowOf(mount, "r1")).not.toBeNull();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('"removed-when-short" drops a completed trace with no duration and few chars', () => {
|
|
202
|
+
const { mount, controller } = makeController({
|
|
203
|
+
features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
|
|
204
|
+
});
|
|
205
|
+
injectReasoning(controller, "r1", "complete", ["Short thought"]);
|
|
206
|
+
expect(rowOf(mount, "r1")).toBeNull();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('"removed-when-short" keeps a completed trace with no duration and many chars', () => {
|
|
210
|
+
const { mount, controller } = makeController({
|
|
211
|
+
features: { reasoningDisplay: { completedVisibility: "removed-when-short" } },
|
|
212
|
+
});
|
|
213
|
+
injectReasoning(controller, "r1", "complete", ["x".repeat(1_500)]);
|
|
214
|
+
expect(rowOf(mount, "r1")).not.toBeNull();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('"removed-when-short" honors custom duration and char thresholds', () => {
|
|
218
|
+
const { mount, controller } = makeController({
|
|
219
|
+
features: {
|
|
220
|
+
reasoningDisplay: {
|
|
221
|
+
completedVisibility: "removed-when-short",
|
|
222
|
+
shortThinkThreshold: { durationMs: 1_000, chars: 50 },
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
// 5s exceeds the custom 1s threshold, so this long-by-default trace is kept.
|
|
227
|
+
injectReasoning(controller, "r1", "complete", ["Weighing the options"], 5_000);
|
|
228
|
+
expect(rowOf(mount, "r1")).not.toBeNull();
|
|
229
|
+
|
|
230
|
+
// 60 chars exceeds the custom 50-char threshold, so this trace is kept too.
|
|
231
|
+
injectReasoning(controller, "r2", "complete", ["y".repeat(60)]);
|
|
232
|
+
expect(rowOf(mount, "r2")).not.toBeNull();
|
|
233
|
+
|
|
234
|
+
// Under both custom thresholds, so this trace is dropped.
|
|
235
|
+
injectReasoning(controller, "r3", "complete", ["z".repeat(10)], 500);
|
|
236
|
+
expect(rowOf(mount, "r3")).toBeNull();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("features.toolCallDisplay.completedVisibility", () => {
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
window.localStorage.clear();
|
|
243
|
+
vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
|
|
244
|
+
cb(0);
|
|
245
|
+
return 1;
|
|
246
|
+
});
|
|
247
|
+
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
248
|
+
window.scrollTo = vi.fn();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
afterEach(() => {
|
|
252
|
+
controllers.splice(0).forEach((controller) => controller.destroy());
|
|
253
|
+
mounts.splice(0).forEach((mount) => mount.remove());
|
|
254
|
+
document.body.innerHTML = "";
|
|
255
|
+
vi.restoreAllMocks();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("keeps a finished tool call row by default", () => {
|
|
259
|
+
const { mount, controller } = makeController();
|
|
260
|
+
injectToolCall(controller, "t1", "complete");
|
|
261
|
+
expect(rowOf(mount, "t1")).not.toBeNull();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('removes the whole row under "removed", leaving no transcript gap', () => {
|
|
265
|
+
const { mount, controller } = makeController({
|
|
266
|
+
features: { toolCallDisplay: { completedVisibility: "removed" } },
|
|
267
|
+
});
|
|
268
|
+
injectToolCall(controller, "t1", "complete");
|
|
269
|
+
injectText(controller, "a1", "Here is the answer");
|
|
270
|
+
|
|
271
|
+
expect(rowOf(mount, "t1")).toBeNull();
|
|
272
|
+
const rows = mount.querySelectorAll("[data-wrapper-id]");
|
|
273
|
+
expect(Array.from(rows).map((row) => row.getAttribute("data-wrapper-id")))
|
|
274
|
+
.toEqual(["a1"]);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('shows a running tool call under "removed" and drops it once it completes', () => {
|
|
278
|
+
const { mount, controller } = makeController({
|
|
279
|
+
features: { toolCallDisplay: { completedVisibility: "removed" } },
|
|
280
|
+
});
|
|
281
|
+
injectToolCall(controller, "t1", "running");
|
|
282
|
+
expect(rowOf(mount, "t1")).not.toBeNull();
|
|
283
|
+
|
|
284
|
+
injectToolCall(controller, "t1", "complete");
|
|
285
|
+
expect(rowOf(mount, "t1")).toBeNull();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('keeps the tool call message in the session under "removed"', () => {
|
|
289
|
+
const { controller } = makeController({
|
|
290
|
+
features: { toolCallDisplay: { completedVisibility: "removed" } },
|
|
291
|
+
});
|
|
292
|
+
injectToolCall(controller, "t1", "complete");
|
|
293
|
+
expect(
|
|
294
|
+
controller.getMessages().find((message) => message.id === "t1")?.toolCall
|
|
295
|
+
?.status
|
|
296
|
+
).toEqual("complete");
|
|
297
|
+
});
|
|
164
298
|
});
|
|
@@ -4,7 +4,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
4
4
|
|
|
5
5
|
import { createAgentExperience } from "./ui";
|
|
6
6
|
import type { AgentWidgetStoredState } from "./types";
|
|
7
|
-
import { createUnifiedEventWrite } from "./utils/__fixtures__/unified-translator.oracle";
|
|
8
7
|
|
|
9
8
|
// The approval UI ships in a lazy chunk; provide it eagerly so this file's
|
|
10
9
|
// synchronous render assertions hold. Per-file module isolation keeps the
|
|
@@ -48,7 +47,7 @@ const flushMicrotasks = async (times = 20) => {
|
|
|
48
47
|
}
|
|
49
48
|
};
|
|
50
49
|
|
|
51
|
-
const
|
|
50
|
+
const sseEvent = (type: string, data: Record<string, unknown>) =>
|
|
52
51
|
`event: ${type}\ndata: ${JSON.stringify({ type, ...data })}\n\n`;
|
|
53
52
|
|
|
54
53
|
const createStreamHarness = () => {
|
|
@@ -59,9 +58,7 @@ const createStreamHarness = () => {
|
|
|
59
58
|
new Response(new ReadableStream<Uint8Array>({
|
|
60
59
|
start(streamController) {
|
|
61
60
|
controller = streamController;
|
|
62
|
-
write =
|
|
63
|
-
streamController.enqueue(encoder.encode(chunk));
|
|
64
|
-
});
|
|
61
|
+
write = (chunk) => streamController.enqueue(encoder.encode(chunk));
|
|
65
62
|
},
|
|
66
63
|
}))
|
|
67
64
|
);
|
|
@@ -69,7 +66,7 @@ const createStreamHarness = () => {
|
|
|
69
66
|
fetchMock,
|
|
70
67
|
send(type: string, data: Record<string, unknown>) {
|
|
71
68
|
if (!write) throw new Error("stream not started");
|
|
72
|
-
write(
|
|
69
|
+
write(sseEvent(type, data));
|
|
73
70
|
},
|
|
74
71
|
close() {
|
|
75
72
|
controller?.close();
|
|
@@ -85,7 +82,7 @@ const startTurn = async (mount: HTMLElement) => {
|
|
|
85
82
|
};
|
|
86
83
|
|
|
87
84
|
const startText = (stream: ReturnType<typeof createStreamHarness>, deltas: string[]) => {
|
|
88
|
-
stream.send("
|
|
85
|
+
stream.send("execution_start", { kind: "flow", executionId: "exec-1", flowId: "flow_1", flowName: "Test", totalSteps: 1 });
|
|
89
86
|
stream.send("step_start", {
|
|
90
87
|
id: "step_1",
|
|
91
88
|
name: "Prompt",
|
|
@@ -93,12 +90,12 @@ const startText = (stream: ReturnType<typeof createStreamHarness>, deltas: strin
|
|
|
93
90
|
index: 0,
|
|
94
91
|
totalSteps: 1,
|
|
95
92
|
});
|
|
96
|
-
stream.send("text_start", {
|
|
97
|
-
deltas.forEach((text) => stream.send("
|
|
93
|
+
stream.send("text_start", { id: "message_1" });
|
|
94
|
+
deltas.forEach((text) => stream.send("text_delta", { id: "message_1", delta: text }));
|
|
98
95
|
};
|
|
99
96
|
|
|
100
97
|
const finishText = (stream: ReturnType<typeof createStreamHarness>) => {
|
|
101
|
-
stream.send("
|
|
98
|
+
stream.send("text_complete", { id: "message_1" });
|
|
102
99
|
stream.send("step_complete", {
|
|
103
100
|
id: "step_1",
|
|
104
101
|
name: "Prompt",
|
|
@@ -106,7 +103,7 @@ const finishText = (stream: ReturnType<typeof createStreamHarness>) => {
|
|
|
106
103
|
success: true,
|
|
107
104
|
result: { response: "Hello world" },
|
|
108
105
|
});
|
|
109
|
-
stream.send("
|
|
106
|
+
stream.send("execution_complete", { kind: "flow", executionId: "exec-1", flowId: "flow_1", success: true });
|
|
110
107
|
stream.close();
|
|
111
108
|
};
|
|
112
109
|
|
|
@@ -145,8 +142,8 @@ describe("pure streaming text update coalescing", () => {
|
|
|
145
142
|
startText(stream, ["Hel"]);
|
|
146
143
|
await flushMicrotasks();
|
|
147
144
|
const afterFirstDelta = saves.length;
|
|
148
|
-
stream.send("
|
|
149
|
-
stream.send("
|
|
145
|
+
stream.send("text_delta", { id: "message_1", delta: "lo" });
|
|
146
|
+
stream.send("text_delta", { id: "message_1", delta: " world" });
|
|
150
147
|
await flushMicrotasks();
|
|
151
148
|
|
|
152
149
|
expect(saves).toHaveLength(afterFirstDelta);
|