@runtypelabs/persona 4.25.0 → 4.26.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-Db61aI9J.d.cts → types-BwKTZhg9.d.cts} +14 -0
- package/dist/animations/{types-Db61aI9J.d.ts → types-BwKTZhg9.d.ts} +14 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +14 -0
- package/dist/animations-extra.d.ts +14 -0
- package/dist/approval-ui-QAKWNFNM.js +1 -0
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +186 -8
- package/dist/approval-ui.d.ts +186 -8
- package/dist/approval-ui.js +2 -2
- package/dist/{artifacts-ui-IGB5YKLO.js → artifacts-ui-NJGMM27I.js} +1 -1
- package/dist/artifacts-ui.cjs +4 -4
- package/dist/artifacts-ui.d.cts +186 -8
- package/dist/artifacts-ui.d.ts +186 -8
- package/dist/artifacts-ui.js +5 -5
- package/dist/chunk-2ANTRKTV.js +1 -0
- package/dist/chunk-4HPTPUC6.js +1 -0
- package/dist/chunk-6V3MNEKB.js +1 -0
- package/dist/chunk-GKPCIQPB.js +1 -0
- package/dist/chunk-HGUCGM3K.js +9 -0
- package/dist/chunk-IO5VVUKP.js +3 -0
- package/dist/chunk-J4J3Y2P2.js +1 -0
- package/dist/chunk-K6HKIP5K.js +1 -0
- package/dist/chunk-K7NA7G7J.js +1 -0
- package/dist/chunk-MXUTS3LB.js +1 -0
- package/dist/chunk-PDPVORUF.js +1 -0
- package/dist/chunk-QRKGQPU7.js +1 -0
- package/dist/chunk-S6PBWE75.js +1 -0
- package/dist/chunk-T7QM7YMF.js +1 -0
- package/dist/chunk-TR2H475E.js +1 -0
- package/dist/chunk-UPO4GUFC.js +1 -0
- package/dist/chunk-ZC66NAIR.js +1 -0
- package/dist/client-history-GOZWBDA5.js +1 -0
- package/dist/client-history.js +1 -0
- package/dist/client-stream-42FOI3EC.js +4 -0
- package/dist/client-stream.js +5 -0
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions-E7HJYWO6.js +161 -0
- package/dist/{context-mentions-inline-I2RDEBAK.js → context-mentions-inline-QVESLW55.js} +1 -1
- package/dist/context-mentions.cjs +7 -7
- package/dist/context-mentions.d.cts +186 -8
- package/dist/context-mentions.d.ts +186 -8
- package/dist/context-mentions.js +8 -8
- package/dist/event-stream-view-FOIJHFQO.js +1 -0
- package/dist/event-stream-view.cjs +1 -1
- package/dist/event-stream-view.d.cts +189 -31
- package/dist/event-stream-view.d.ts +189 -31
- package/dist/event-stream-view.js +1 -1
- package/dist/forms-ui.d.cts +227 -667
- package/dist/forms-ui.d.ts +227 -667
- package/dist/history-shell-QDQ44PSE.js +3 -0
- package/dist/history-shell.js +3 -0
- package/dist/{history-view-BD6OXLNA.js → history-view-NCFBI5GR.js} +2 -2
- package/dist/history-view.cjs +2 -2
- package/dist/history-view.d.cts +35 -1
- package/dist/history-view.d.ts +35 -1
- package/dist/history-view.js +2 -2
- package/dist/index.cjs +80 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -738
- package/dist/index.d.ts +233 -738
- package/dist/index.global.js +3 -215
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +81 -75
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +3 -67
- package/dist/launcher.global.js.map +1 -1
- package/dist/markdown-parsers.js +22 -22
- package/dist/runtype-tts-entry-NFBVWR7N.js +1 -0
- package/dist/runtype-tts.js +1 -1
- package/dist/session-actions-ISSKSI66.js +1 -0
- package/dist/session-actions.js +1 -0
- package/dist/session-reconnect.d.cts +186 -8
- package/dist/session-reconnect.d.ts +186 -8
- package/dist/smart-dom-reader.cjs +13 -13
- package/dist/smart-dom-reader.d.cts +186 -8
- package/dist/smart-dom-reader.d.ts +186 -8
- package/dist/smart-dom-reader.js +11 -11
- package/dist/theme-editor-preview.cjs +116 -98
- package/dist/theme-editor-preview.d.cts +188 -8
- package/dist/theme-editor-preview.d.ts +188 -8
- package/dist/theme-editor-preview.js +49 -54
- package/dist/theme-editor.cjs +2 -2
- package/dist/theme-editor.d.cts +187 -23
- package/dist/theme-editor.d.ts +187 -23
- package/dist/theme-editor.js +2 -2
- package/dist/theme-reference.d.cts +14 -0
- package/dist/theme-reference.d.ts +14 -0
- package/dist/ui-extras-entry-WAYFKVNW.js +3 -0
- package/dist/ui-extras.js +72 -0
- package/dist/voice-runtime-XLU6YK2N.js +17 -0
- package/dist/voice-runtime.cjs +17 -1
- package/dist/voice-runtime.d.cts +11656 -232
- package/dist/voice-runtime.d.ts +11656 -232
- package/dist/voice-runtime.js +17 -1
- package/dist/voice-worklet-player.cjs +19 -4
- package/dist/voice-worklet-player.d.cts +9 -10
- package/dist/voice-worklet-player.d.ts +9 -10
- package/dist/voice-worklet-player.js +19 -4
- package/dist/webmcp-polyfill.js +1 -1
- package/dist/webmcp-runtime-V6ZWW3PI.js +6 -0
- package/dist/webmcp-runtime.cjs +2 -2
- package/dist/webmcp-runtime.d.cts +1 -33
- package/dist/webmcp-runtime.d.ts +1 -33
- package/dist/webmcp-runtime.js +2 -2
- package/dist/widget.css +1 -1
- package/package.json +11 -5
- package/src/approval-paused-tool-settle.test.ts +325 -0
- package/src/client-approve.test.ts +311 -0
- package/src/client-history-eager.ts +9 -0
- package/src/client-history-loader.ts +23 -0
- package/src/client-history.ts +579 -0
- package/src/client-resume.ts +177 -0
- package/src/client-stream-eager.ts +12 -0
- package/src/client-stream-loader.ts +25 -0
- package/src/client-stream.ts +1880 -0
- package/src/client.test.ts +31 -1
- package/src/client.ts +293 -2607
- package/src/components/approval-actions.test.ts +27 -0
- package/src/components/approval-actions.ts +27 -3
- package/src/components/message-bubble.test.ts +19 -0
- package/src/components/message-bubble.ts +2 -0
- package/src/components/registry.ts +6 -6
- package/src/components/tool-bubble.test.ts +9 -0
- package/src/components/tool-bubble.ts +1 -0
- package/src/history-shell-bundle.test.ts +54 -0
- package/src/history-shell-eager.ts +12 -0
- package/src/history-shell-loader.ts +27 -0
- package/src/history-shell.ts +2362 -0
- package/src/history-view-loader.ts +2 -0
- package/src/history-view.ts +3 -0
- package/src/index-core.ts +6 -0
- package/src/index-global.ts +90 -248
- package/src/index.ts +13 -0
- package/src/install.ts +6 -4
- package/src/internal/runtype-history-provider.test.ts +1 -0
- package/src/internal/runtype-history-provider.ts +28 -13
- package/src/plugins/registry.ts +8 -8
- package/src/session-actions-chunk.test.ts +55 -0
- package/src/session-actions-eager.ts +11 -0
- package/src/session-actions-loader.ts +37 -0
- package/src/session-actions.ts +909 -0
- package/src/session-history.test.ts +3 -1
- package/src/session.ts +895 -1538
- package/src/session.voice.test.ts +1171 -5
- package/src/styles/widget.css +58 -0
- package/src/theme-editor/state.ts +91 -91
- package/src/types.ts +204 -5
- package/src/ui-ask-user-sheet.ts +382 -0
- package/src/ui-extras-eager.ts +10 -0
- package/src/ui-extras-entry.ts +9 -0
- package/src/ui-extras-loader.ts +26 -0
- package/src/ui.artifact-rail-resize.test.ts +2 -1
- package/src/ui.ask-user-chunk.test.ts +93 -0
- package/src/ui.history-chunk.test.ts +2 -1
- package/src/ui.history-config-provider.test.ts +2 -1
- package/src/ui.history-confirm-fallback.test.ts +142 -0
- package/src/ui.history-controller.test.ts +2 -1
- package/src/ui.history-lifecycle.test.ts +2 -1
- package/src/ui.history-render-hooks.test.ts +2 -1
- package/src/ui.history-shell.test.ts +2 -1
- package/src/ui.rail-overlay.test.ts +2 -1
- package/src/ui.rail-resize.test.ts +2 -1
- package/src/ui.rail-trigger-pending.test.ts +2 -1
- package/src/ui.ts +340 -2770
- package/src/utils/attachment-manager.ts +67 -67
- package/src/utils/constants.ts +3 -0
- package/src/utils/context-mention-controller.ts +323 -323
- package/src/utils/context-mention-manager.ts +40 -40
- package/src/utils/context-mention-orchestrator.ts +28 -0
- package/src/utils/event-stream-buffer.ts +50 -50
- package/src/utils/event-stream-store.ts +43 -43
- package/src/utils/throughput-tracker.ts +34 -34
- package/src/utils/virtual-scroller.ts +67 -67
- package/src/utils/visitor-store.test.ts +50 -0
- package/src/utils/visitor-store.ts +23 -5
- package/src/voice/__fixtures__/voice-wire/README.md +1 -0
- package/src/voice/__fixtures__/voice-wire/client/cancel.json +3 -0
- package/src/voice/__fixtures__/voice-wire/client/context.json +4 -0
- package/src/voice/__fixtures__/voice-wire/client/delegation_result.json +6 -0
- package/src/voice/__fixtures__/voice-wire/client/delegation_update.json +6 -0
- package/src/voice/__fixtures__/voice-wire/client/ping.json +3 -0
- package/src/voice/__fixtures__/voice-wire/client/playback_progress.json +5 -0
- package/src/voice/__fixtures__/voice-wire/client/start.json +3 -0
- package/src/voice/__fixtures__/voice-wire/server/artifact.json +11 -0
- package/src/voice/__fixtures__/voice-wire/server/attached.json +4 -0
- package/src/voice/__fixtures__/voice-wire/server/audio_clear.json +4 -0
- package/src/voice/__fixtures__/voice-wire/server/audio_end.json +3 -0
- package/src/voice/__fixtures__/voice-wire/server/delegation_cancelled.json +5 -0
- package/src/voice/__fixtures__/voice-wire/server/delegation_completed.json +8 -0
- package/src/voice/__fixtures__/voice-wire/server/delegation_started.json +15 -0
- package/src/voice/__fixtures__/voice-wire/server/error.json +6 -0
- package/src/voice/__fixtures__/voice-wire/server/metrics.json +10 -0
- package/src/voice/__fixtures__/voice-wire/server/pong.json +3 -0
- package/src/voice/__fixtures__/voice-wire/server/session_config.json +16 -0
- package/src/voice/__fixtures__/voice-wire/server/transcript_final.json +7 -0
- package/src/voice/__fixtures__/voice-wire/server/transcript_interim.json +4 -0
- package/src/voice/__fixtures__/voice-wire/server/transcript_partial.json +6 -0
- package/src/voice/__fixtures__/voice-wire/server/transcript_update.json +10 -0
- package/src/voice/__fixtures__/voice-wire/server/warning.json +5 -0
- package/src/voice/audio-playback-manager.test.ts +57 -0
- package/src/voice/audio-playback-manager.ts +120 -98
- package/src/voice/browser-voice-provider.ts +37 -37
- package/src/voice/fallback-speech-engine.ts +22 -16
- package/src/voice/keyed-voice-transcript.ts +314 -0
- package/src/voice/mic-state-styles.ts +182 -0
- package/src/voice/read-aloud-controller.ts +46 -44
- package/src/voice/runtype-speech-engine.ts +35 -33
- package/src/voice/runtype-voice-provider.ts +680 -189
- package/src/voice/session-voice-wiring.ts +266 -0
- package/src/voice/voice-delegation.test.ts +71 -0
- package/src/voice/voice-delegation.ts +364 -0
- package/src/voice/voice-turn-tracker.ts +6 -6
- package/src/voice/voice.test.ts +1060 -8
- package/src/voice/worklet-playback-engine.test.ts +81 -0
- package/src/voice/worklet-playback-engine.ts +21 -3
- package/src/voice-runtime-loader.ts +4 -1
- package/src/voice-runtime.ts +7 -0
- package/src/webmcp-runtime-entry.ts +41 -39
- package/dist/approval-ui-3NMYNXBD.js +0 -1
- package/dist/chunk-5EIIHQLQ.js +0 -1
- package/dist/chunk-7CO4T5KB.js +0 -1
- package/dist/chunk-EFJBUH5F.js +0 -1
- package/dist/chunk-R2TMIMDI.js +0 -3
- package/dist/chunk-UTVLOTWB.js +0 -9
- package/dist/context-mentions-45MODWOY.js +0 -161
- package/dist/event-stream-view-GL3ADRY2.js +0 -1
- package/dist/runtype-tts-entry-UJAEF7NZ.js +0 -1
- package/dist/voice-runtime-7DA7MJH4.js +0 -1
- package/dist/webmcp-runtime-JI7T4QYF.js +0 -6
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { AgentWidgetClient } from "./client";
|
|
3
|
+
import { AgentWidgetSession } from "./session";
|
|
4
|
+
import type { AgentWidgetMessage } from "./types";
|
|
5
|
+
|
|
6
|
+
// runtypelabs/core#9518: client-token embeds answer `approver: 'end-user'`
|
|
7
|
+
// gates through the session-authenticated `/v1/client/approve`.
|
|
8
|
+
|
|
9
|
+
const originalFetch = global.fetch;
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
global.fetch = originalFetch;
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const approval = { agentId: "agent_abc", executionId: "exec_abc", approvalId: "appr_1" };
|
|
16
|
+
|
|
17
|
+
const liveSession = (client: AgentWidgetClient) => {
|
|
18
|
+
// A live session so initSession() short-circuits instead of fetching /init.
|
|
19
|
+
(client as unknown as { clientSession: { sessionId: string; expiresAt: Date } }).clientSession = {
|
|
20
|
+
sessionId: "cs_123",
|
|
21
|
+
expiresAt: new Date(Date.now() + 60_000),
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const sse = (events: Array<Record<string, unknown>>) =>
|
|
26
|
+
new Response(events.map((e) => `data: ${JSON.stringify(e)}\n\n`).join(""), {
|
|
27
|
+
headers: { "Content-Type": "text/event-stream" },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
type Call = { url: string; body: Record<string, unknown>; headers: Record<string, string> };
|
|
31
|
+
|
|
32
|
+
const mockFetch = (...responses: Response[]) => {
|
|
33
|
+
const calls: Call[] = [];
|
|
34
|
+
global.fetch = vi.fn().mockImplementation(async (url: string, init: RequestInit) => {
|
|
35
|
+
calls.push({
|
|
36
|
+
url,
|
|
37
|
+
body: JSON.parse(init.body as string),
|
|
38
|
+
headers: init.headers as Record<string, string>,
|
|
39
|
+
});
|
|
40
|
+
return responses.shift() ?? sse([]);
|
|
41
|
+
});
|
|
42
|
+
return calls;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const tokenClient = () => {
|
|
46
|
+
const client = new AgentWidgetClient({ clientToken: "ct_live_demo", apiUrl: "https://api.runtype.com" });
|
|
47
|
+
liveSession(client);
|
|
48
|
+
return client;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("AgentWidgetClient.resolveApproval", () => {
|
|
52
|
+
it("posts a client-token decision to /v1/client/approve with the session and no Bearer key", async () => {
|
|
53
|
+
const calls = mockFetch(sse([]));
|
|
54
|
+
await tokenClient().resolveApproval(approval, "approved");
|
|
55
|
+
|
|
56
|
+
expect(calls).toHaveLength(1);
|
|
57
|
+
expect(calls[0].url).toBe("https://api.runtype.com/v1/client/approve");
|
|
58
|
+
expect(calls[0].body).toEqual({
|
|
59
|
+
sessionId: "cs_123",
|
|
60
|
+
executionId: "exec_abc",
|
|
61
|
+
approvalId: "appr_1",
|
|
62
|
+
decision: "approved",
|
|
63
|
+
streamResponse: true,
|
|
64
|
+
});
|
|
65
|
+
expect(calls[0].headers.Authorization).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("sends a denial the same way", async () => {
|
|
69
|
+
const calls = mockFetch(sse([]));
|
|
70
|
+
await tokenClient().resolveApproval(approval, "denied");
|
|
71
|
+
|
|
72
|
+
expect(calls[0].url).toBe("https://api.runtype.com/v1/client/approve");
|
|
73
|
+
expect(calls[0].body.decision).toBe("denied");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("falls back to the owner route when the core predates /v1/client/approve", async () => {
|
|
77
|
+
const calls = mockFetch(
|
|
78
|
+
Response.json({ error: "Not Found", message: "The requested resource was not found" }, { status: 404 }),
|
|
79
|
+
sse([])
|
|
80
|
+
);
|
|
81
|
+
const response = await tokenClient().resolveApproval(approval, "approved");
|
|
82
|
+
|
|
83
|
+
expect(calls.map((c) => c.url)).toEqual([
|
|
84
|
+
"https://api.runtype.com/v1/client/approve",
|
|
85
|
+
"https://api.runtype.com/v1/agents/agent_abc/approve",
|
|
86
|
+
]);
|
|
87
|
+
expect(calls[1].body.sessionId).toBeUndefined();
|
|
88
|
+
expect(response.ok).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("returns an unknown or expired pause 404 without falling back", async () => {
|
|
92
|
+
const calls = mockFetch(
|
|
93
|
+
Response.json({ error: "No paused execution found for this executionId" }, { status: 404 })
|
|
94
|
+
);
|
|
95
|
+
const response = await tokenClient().resolveApproval(approval, "approved");
|
|
96
|
+
|
|
97
|
+
expect(calls).toHaveLength(1);
|
|
98
|
+
expect(response.status).toBe(404);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("keeps proxy mode on the owner route with the host headers", async () => {
|
|
102
|
+
const calls = mockFetch(sse([]));
|
|
103
|
+
const client = new AgentWidgetClient({
|
|
104
|
+
apiUrl: "https://api.runtype.com/v1/dispatch",
|
|
105
|
+
headers: { Authorization: "Bearer host" },
|
|
106
|
+
});
|
|
107
|
+
await client.resolveApproval(approval, "approved");
|
|
108
|
+
|
|
109
|
+
expect(calls[0].url).toBe("https://api.runtype.com/v1/agents/agent_abc/approve");
|
|
110
|
+
expect(calls[0].body).toEqual({
|
|
111
|
+
executionId: "exec_abc",
|
|
112
|
+
approvalId: "appr_1",
|
|
113
|
+
decision: "approved",
|
|
114
|
+
streamResponse: true,
|
|
115
|
+
});
|
|
116
|
+
expect(calls[0].headers.Authorization).toBe("Bearer host");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("AgentWidgetSession.resolveApproval in client-token mode", () => {
|
|
121
|
+
const setup = () => {
|
|
122
|
+
let messages: AgentWidgetMessage[] = [];
|
|
123
|
+
const errors: Error[] = [];
|
|
124
|
+
const pending: AgentWidgetMessage = {
|
|
125
|
+
id: "approval-appr_1",
|
|
126
|
+
role: "assistant",
|
|
127
|
+
content: "",
|
|
128
|
+
createdAt: new Date().toISOString(),
|
|
129
|
+
variant: "approval",
|
|
130
|
+
approval: {
|
|
131
|
+
id: "appr_1",
|
|
132
|
+
status: "pending",
|
|
133
|
+
agentId: "agent_abc",
|
|
134
|
+
executionId: "exec_abc",
|
|
135
|
+
toolName: "place_pickup_order",
|
|
136
|
+
description: "Place the order",
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const session = new AgentWidgetSession(
|
|
140
|
+
{ clientToken: "ct_live_demo", apiUrl: "https://api.runtype.com", initialMessages: [pending] },
|
|
141
|
+
{
|
|
142
|
+
onMessagesChanged: (m) => { messages = m; },
|
|
143
|
+
onStatusChanged: () => {},
|
|
144
|
+
onStreamingChanged: () => {},
|
|
145
|
+
onError: (e) => errors.push(e),
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
liveSession((session as unknown as { client: AgentWidgetClient }).client);
|
|
149
|
+
return {
|
|
150
|
+
session,
|
|
151
|
+
errors,
|
|
152
|
+
approve: (decision: "approved" | "denied" = "approved") =>
|
|
153
|
+
session.resolveApproval(pending.approval!, decision),
|
|
154
|
+
bubble: () => messages.find((m) => m.id === "approval-appr_1"),
|
|
155
|
+
last: () => messages.at(-1),
|
|
156
|
+
all: () => messages,
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
it("streams the approved continuation into the conversation", async () => {
|
|
161
|
+
mockFetch(
|
|
162
|
+
sse([
|
|
163
|
+
{ type: "approval_complete", executionId: "exec_abc", approvalId: "appr_1", decision: "approved" },
|
|
164
|
+
{ type: "text_start", id: "t1" },
|
|
165
|
+
{ type: "text_delta", id: "t1", delta: "Order placed." },
|
|
166
|
+
{ type: "text_complete", id: "t1" },
|
|
167
|
+
{ type: "execution_complete", kind: "agent", success: true },
|
|
168
|
+
])
|
|
169
|
+
);
|
|
170
|
+
const { session, errors, approve, bubble, last } = setup();
|
|
171
|
+
await approve();
|
|
172
|
+
|
|
173
|
+
expect(bubble()?.approval?.status).toBe("approved");
|
|
174
|
+
expect(last()?.content).toBe("Order placed.");
|
|
175
|
+
expect(session.isStreaming()).toBe(false);
|
|
176
|
+
expect(errors).toEqual([]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it.each([
|
|
180
|
+
[
|
|
181
|
+
403,
|
|
182
|
+
{
|
|
183
|
+
error: "This approval needs the business owner, not the chat visitor",
|
|
184
|
+
code: "APPROVAL_APPROVER_NOT_END_USER",
|
|
185
|
+
},
|
|
186
|
+
"This approval needs the business owner, not the chat visitor",
|
|
187
|
+
"pending",
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
409,
|
|
191
|
+
{
|
|
192
|
+
error: "Approval already resolved",
|
|
193
|
+
message: "This approval was already denied; that decision is the one in force.",
|
|
194
|
+
code: "APPROVAL_ALREADY_RESOLVED",
|
|
195
|
+
},
|
|
196
|
+
"This approval was already denied; that decision is the one in force.",
|
|
197
|
+
"timeout",
|
|
198
|
+
],
|
|
199
|
+
[
|
|
200
|
+
404,
|
|
201
|
+
{ error: "No paused execution found for this executionId" },
|
|
202
|
+
"No paused execution found for this executionId",
|
|
203
|
+
"timeout",
|
|
204
|
+
],
|
|
205
|
+
[404, { error: "Agent not found" }, "Agent not found", "pending"],
|
|
206
|
+
[500, { error: "Failed to process approval" }, "Failed to process approval", "pending"],
|
|
207
|
+
])("surfaces a %i refusal without claiming the decision applied", async (status, body, text, badge) => {
|
|
208
|
+
mockFetch(Response.json(body, { status }));
|
|
209
|
+
const { session, errors, approve, bubble, last } = setup();
|
|
210
|
+
await approve();
|
|
211
|
+
|
|
212
|
+
expect(bubble()?.approval?.status).toBe(badge);
|
|
213
|
+
expect(last()).toMatchObject({ id: "approval-error-appr_1", content: text });
|
|
214
|
+
expect(session.isStreaming()).toBe(false);
|
|
215
|
+
expect(errors.map((e) => e.message)).toEqual([text]);
|
|
216
|
+
// A refused request settles too: its token must not outlive it.
|
|
217
|
+
expect((session as unknown as { approvalTokens: Map<string, object> }).approvalTokens.size).toBe(0);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("clears the failure notice when a retry succeeds", async () => {
|
|
221
|
+
mockFetch(
|
|
222
|
+
Response.json({ error: "Failed to process approval" }, { status: 500 }),
|
|
223
|
+
sse([{ type: "approval_complete", executionId: "exec_abc", approvalId: "appr_1", decision: "approved" }])
|
|
224
|
+
);
|
|
225
|
+
const { approve, bubble, all } = setup();
|
|
226
|
+
await approve();
|
|
227
|
+
expect(bubble()?.approval?.status).toBe("pending");
|
|
228
|
+
await approve();
|
|
229
|
+
|
|
230
|
+
expect(bubble()?.approval?.status).toBe("approved");
|
|
231
|
+
expect(all().some((m) => m.id === "approval-error-appr_1")).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("does not overwrite a card a newer resolution already settled", async () => {
|
|
235
|
+
let failFirst!: (r: Response) => void;
|
|
236
|
+
global.fetch = vi
|
|
237
|
+
.fn()
|
|
238
|
+
.mockImplementationOnce(() => new Promise<Response>((res) => { failFirst = res; }))
|
|
239
|
+
.mockImplementationOnce(async () =>
|
|
240
|
+
sse([{ type: "approval_complete", executionId: "exec_abc", approvalId: "appr_1", decision: "approved" }])
|
|
241
|
+
);
|
|
242
|
+
const { approve, bubble, all } = setup();
|
|
243
|
+
const first = approve();
|
|
244
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
245
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
246
|
+
await approve();
|
|
247
|
+
failFirst(Response.json({ error: "Failed to process approval" }, { status: 500 }));
|
|
248
|
+
await first;
|
|
249
|
+
|
|
250
|
+
expect(bubble()?.approval?.status).toBe("approved");
|
|
251
|
+
expect(all().some((m) => m.id === "approval-error-appr_1")).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("does not let an older failure reopen a card a newer decision settled in the same millisecond", async () => {
|
|
255
|
+
// Both decisions (and the newer one's approval_complete) stamp the same
|
|
256
|
+
// Date.now(), so only a per-request token tells the older failure apart.
|
|
257
|
+
vi.spyOn(Date, "now").mockReturnValue(Date.now());
|
|
258
|
+
let failFirst!: (r: Response) => void;
|
|
259
|
+
global.fetch = vi
|
|
260
|
+
.fn()
|
|
261
|
+
.mockImplementationOnce(() => new Promise<Response>((res) => { failFirst = res; }))
|
|
262
|
+
.mockImplementationOnce(async () =>
|
|
263
|
+
sse([{ type: "approval_complete", executionId: "exec_abc", approvalId: "appr_1", decision: "denied" }])
|
|
264
|
+
);
|
|
265
|
+
const { session, approve, bubble, all } = setup();
|
|
266
|
+
const first = approve("approved");
|
|
267
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
268
|
+
await approve("denied");
|
|
269
|
+
expect(bubble()?.approval?.status).toBe("denied");
|
|
270
|
+
failFirst(Response.json({ error: "Failed to process approval" }, { status: 500 }));
|
|
271
|
+
await first;
|
|
272
|
+
|
|
273
|
+
expect(bubble()?.approval?.status).toBe("denied");
|
|
274
|
+
expect(all().some((m) => m.id === "approval-error-appr_1")).toBe(false);
|
|
275
|
+
// Both requests settled: no token outlives them.
|
|
276
|
+
expect((session as unknown as { approvalTokens: Map<string, object> }).approvalTokens.size).toBe(0);
|
|
277
|
+
vi.restoreAllMocks();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("does not let an older success clear a newer request's failure notice", async () => {
|
|
281
|
+
let succeedFirst!: (r: Response) => void;
|
|
282
|
+
global.fetch = vi
|
|
283
|
+
.fn()
|
|
284
|
+
.mockImplementationOnce(() => new Promise<Response>((res) => { succeedFirst = res; }))
|
|
285
|
+
.mockResolvedValueOnce(Response.json({ error: "Failed to process approval" }, { status: 500 }));
|
|
286
|
+
const { approve, all } = setup();
|
|
287
|
+
const first = approve();
|
|
288
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
289
|
+
await new Promise((r) => setTimeout(r, 2));
|
|
290
|
+
await approve();
|
|
291
|
+
expect(all().some((m) => m.id === "approval-error-appr_1")).toBe(true);
|
|
292
|
+
succeedFirst(
|
|
293
|
+
sse([{ type: "approval_complete", executionId: "exec_abc", approvalId: "appr_1", decision: "approved" }])
|
|
294
|
+
);
|
|
295
|
+
await first;
|
|
296
|
+
|
|
297
|
+
expect(all().some((m) => m.id === "approval-error-appr_1")).toBe(true);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("keeps the failure notice when a retry rejects without a response", async () => {
|
|
301
|
+
global.fetch = vi
|
|
302
|
+
.fn()
|
|
303
|
+
.mockResolvedValueOnce(Response.json({ error: "Failed to process approval" }, { status: 500 }))
|
|
304
|
+
.mockRejectedValueOnce(new TypeError("Failed to fetch"));
|
|
305
|
+
const { approve, all } = setup();
|
|
306
|
+
await approve();
|
|
307
|
+
await approve();
|
|
308
|
+
|
|
309
|
+
expect(all().find((m) => m.id === "approval-error-appr_1")?.content).toBe("Failed to process approval");
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eager history-REST registration for the bundled (ESM / CJS) builds and
|
|
3
|
+
* tests. Like `markdown-parsers-eager.ts`, this module must NOT be reachable
|
|
4
|
+
* from `index-global.ts`: the IIFE/CDN build fetches `client-history.js`.
|
|
5
|
+
*/
|
|
6
|
+
import * as clientHistory from "./client-history";
|
|
7
|
+
import { provideClientHistory } from "./client-history-loader";
|
|
8
|
+
|
|
9
|
+
provideClientHistory(clientHistory);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createChunkLoader } from "./utils/chunk-loader";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Loader indirection for the visitor-history REST functions
|
|
5
|
+
* (`client-history.ts`). The ESM/CJS fallback imports `./client-history`
|
|
6
|
+
* directly (inlined by `--splitting false`); the IIFE/CDN build marks it
|
|
7
|
+
* external and registers a loader for the sibling `client-history.js` chunk
|
|
8
|
+
* (see `index-global.ts`).
|
|
9
|
+
*/
|
|
10
|
+
export type ClientHistoryModule = typeof import("./client-history");
|
|
11
|
+
|
|
12
|
+
const { setLoader, load, provide } = createChunkLoader<ClientHistoryModule>({
|
|
13
|
+
fallbackImport: () => import("./client-history"),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** Override how the chunk is fetched (the IIFE build registers a sibling-URL loader). */
|
|
17
|
+
export const setClientHistoryLoader = setLoader;
|
|
18
|
+
|
|
19
|
+
/** Load the history REST functions. Memoized; retries after rejection. */
|
|
20
|
+
export const loadClientHistory = load;
|
|
21
|
+
|
|
22
|
+
/** Eagerly supply the module (`client-history-eager.ts`: npm builds and tests). */
|
|
23
|
+
export const provideClientHistory = provide;
|