@guuey/chat 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -0
  3. package/dist/history-inputs.d.ts +24 -0
  4. package/dist/history-inputs.d.ts.map +1 -0
  5. package/dist/history-inputs.js +34 -0
  6. package/dist/index.d.ts +19 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +17 -0
  9. package/dist/plan.d.ts +5 -0
  10. package/dist/plan.d.ts.map +1 -0
  11. package/dist/plan.js +629 -0
  12. package/dist/policy.d.ts +110 -0
  13. package/dist/policy.d.ts.map +1 -0
  14. package/dist/policy.js +75 -0
  15. package/dist/react/components.d.ts +87 -0
  16. package/dist/react/components.d.ts.map +1 -0
  17. package/dist/react/components.js +270 -0
  18. package/dist/react/guuey-chat.d.ts +84 -0
  19. package/dist/react/guuey-chat.d.ts.map +1 -0
  20. package/dist/react/guuey-chat.js +103 -0
  21. package/dist/react/markdown.d.ts +32 -0
  22. package/dist/react/markdown.d.ts.map +1 -0
  23. package/dist/react/markdown.js +40 -0
  24. package/dist/react/theme-css.d.ts +16 -0
  25. package/dist/react/theme-css.d.ts.map +1 -0
  26. package/dist/react/theme-css.js +37 -0
  27. package/dist/react/transcript.d.ts +42 -0
  28. package/dist/react/transcript.d.ts.map +1 -0
  29. package/dist/react/transcript.js +88 -0
  30. package/dist/react/use-transcript.d.ts +39 -0
  31. package/dist/react/use-transcript.d.ts.map +1 -0
  32. package/dist/react/use-transcript.js +201 -0
  33. package/dist/react.d.ts +21 -0
  34. package/dist/react.d.ts.map +1 -0
  35. package/dist/react.js +20 -0
  36. package/dist/strings.d.ts +74 -0
  37. package/dist/strings.d.ts.map +1 -0
  38. package/dist/strings.js +45 -0
  39. package/dist/theme.d.ts +99 -0
  40. package/dist/theme.d.ts.map +1 -0
  41. package/dist/theme.js +182 -0
  42. package/dist/types.d.ts +283 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +1 -0
  45. package/package.json +87 -0
  46. package/src/corpus/README.md +40 -0
  47. package/src/corpus/__snapshots__/corpus.test.ts.snap +1590 -0
  48. package/src/corpus/capture.ts +67 -0
  49. package/src/corpus/captures/issue2627-render-capture.coalesced.sse.txt +173 -0
  50. package/src/corpus/drive.ts +184 -0
  51. package/src/corpus/fixtures.ts +338 -0
  52. package/src/history-inputs.ts +48 -0
  53. package/src/index.ts +58 -0
  54. package/src/plan.ts +740 -0
  55. package/src/policy.ts +146 -0
  56. package/src/react/components.tsx +655 -0
  57. package/src/react/guuey-chat.tsx +227 -0
  58. package/src/react/markdown.tsx +114 -0
  59. package/src/react/theme-css.ts +50 -0
  60. package/src/react/transcript.tsx +187 -0
  61. package/src/react/use-transcript.ts +274 -0
  62. package/src/react.tsx +51 -0
  63. package/src/strings.ts +144 -0
  64. package/src/theme.ts +195 -0
  65. package/src/types.ts +320 -0
  66. package/styles.css +514 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Capture-derived corpus inputs (guuey#135 wave 3c — the lesson from 3b's
3
+ * dogfood finding 1: the hand-driven corpus passed while the PRODUCTION
4
+ * fold shape failed, because real pods fold tool results into separate
5
+ * `role: "tool"` messages the hand-built sequences never produce).
6
+ *
7
+ * The rule this module enforces: at least one corpus family is derived
8
+ * from a REAL redacted production capture, replayed through the REAL
9
+ * `invokeTurn` (parse → status → text fold → AgEvent ingest) — so the
10
+ * corpus can never again be green while production shapes are broken.
11
+ *
12
+ * Provenance: `captures/issue2627-render-capture.coalesced.sse.txt` is a byte copy
13
+ * of `apps/widget/src/fixtures/issue2627-render-capture.coalesced.sse.txt` — the
14
+ * redacted production SSE capture of a ggui-render turn (every id
15
+ * remapped to a stable synthetic one of the same shape, credentials
16
+ * dummied; see the widget's `scripts/derive-coalesced-capture.mjs`).
17
+ * COALESCED is the valid production shape; the RAW variant of the same
18
+ * capture deliberately parks the Reducer and stays in the widget as that
19
+ * regression's pin. Captures are immutable records, so the copy cannot
20
+ * drift.
21
+ */
22
+ import { readFileSync } from "node:fs";
23
+ import { join } from "node:path";
24
+ import { invokeTurn, type InvokeTurnEvent } from "@guuey/agent-client";
25
+ import { driveTurn, type DriveOptions } from "./drive.js";
26
+ import type { TranscriptInputs } from "../types.js";
27
+
28
+ const CAPTURES = join(import.meta.dirname, "captures");
29
+
30
+ /** The capture's (redacted) render locator — what R6's actionScope must be. */
31
+ export const CAPTURE_RENDER_URI =
32
+ "ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c10a20553df2349b";
33
+
34
+ /**
35
+ * Replay a capture through the REAL `invokeTurn`, chunked adversarially
36
+ * (fixed-size slices that split SSE frames mid-line) so the parse path is
37
+ * exercised the way a network delivers it, not a fixture's convenience.
38
+ */
39
+ export async function captureTurnEvents(
40
+ file: string,
41
+ chunkSize = 512,
42
+ ): Promise<InvokeTurnEvent[]> {
43
+ const text = readFileSync(join(CAPTURES, file), "utf8");
44
+ const transport = async function* (): AsyncGenerator<string> {
45
+ for (let at = 0; at < text.length; at += chunkSize) {
46
+ yield text.slice(at, at + chunkSize);
47
+ }
48
+ };
49
+ const events: InvokeTurnEvent[] = [];
50
+ const req = {
51
+ url: "https://capture.replay.invalid/agent/invoke",
52
+ body: { input: "capture replay" },
53
+ signal: new AbortController().signal,
54
+ };
55
+ for await (const event of invokeTurn(req, transport)) events.push(event);
56
+ return events;
57
+ }
58
+
59
+ /** The ggui-render production capture as `TranscriptInputs`. */
60
+ export async function productionGguiRenderCapture(
61
+ opts: DriveOptions = {},
62
+ ): Promise<TranscriptInputs> {
63
+ const events = await captureTurnEvents("issue2627-render-capture.coalesced.sse.txt");
64
+ // The capture ends without a `done` frame (capture-time truncation);
65
+ // the corpus family asserts the SETTLED plan, so the drive settles it.
66
+ return driveTurn(events, { userText: "capture replay", finalStatus: "ready", ...opts });
67
+ }
@@ -0,0 +1,173 @@
1
+ event: session
2
+ data: {"sessionId":"00000000-0000-4000-8000-100000000001","userId":"g_000000000000000000000000000001","authMode":"anonymous","threadId":"00000000-0000-4000-8000-100000000002"}
3
+
4
+ event: message
5
+ data: {"type":"turn.start","seq":0,"turnId":"turn_00000000-0000-4000-8000-200000000001","threadId":"00000000-0000-4000-8000-100000000003"}
6
+
7
+ event: message
8
+ data: {"type":"message.start","seq":1,"id":"msg_000000000000000000000001","turnId":"turn_00000000-0000-4000-8000-200000000001","threadId":"00000000-0000-4000-8000-100000000003","role":"assistant","model":"claude-sonnet-5"}
9
+
10
+ event: message
11
+ data: {"type":"reasoning.start","seq":2,"id":"msg_000000000000000000000001:reasoning:0","messageId":"msg_000000000000000000000001","turnId":"turn_00000000-0000-4000-8000-200000000001"}
12
+
13
+ event: message
14
+ data: {"type":"reasoning.delta","seq":3,"id":"msg_000000000000000000000001:reasoning:0","messageId":"msg_000000000000000000000001","delta":"","turnId":"turn_00000000-0000-4000-8000-200000000001"}
15
+
16
+ event: message
17
+ data: {"type":"reasoning.end","seq":4,"id":"msg_000000000000000000000001:reasoning:0","messageId":"msg_000000000000000000000001","turnId":"turn_00000000-0000-4000-8000-200000000001"}
18
+
19
+ event: message
20
+ data: {"type":"reasoning.opaque","seq":5,"id":"msg_000000000000000000000001:reasoning:0","messageId":"msg_000000000000000000000001","kind":"signature","value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","turnId":"turn_00000000-0000-4000-8000-200000000001","provider":"anthropic"}
21
+
22
+ event: message
23
+ data: {"type":"tool.start","seq":6,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000001","name":"mcp__todo__todo_create","index":0,"messageId":"msg_000000000000000000000001","providerExecuted":false}
24
+
25
+ event: message
26
+ data: {"type":"tool.args.delta","seq":7,"toolCallId":"toolu_000000000000000000000001","delta":"{\"title\":\"Buy milk\"}"}
27
+
28
+ event: message
29
+ data: {"type":"tool.args.assembled","seq":8,"toolCallId":"toolu_000000000000000000000001","input":{"title":"Buy milk"}}
30
+
31
+ event: message
32
+ data: {"type":"message.end","seq":9,"id":"msg_000000000000000000000001","usage":{"inputTokens":2,"outputTokens":2,"cacheReadTokens":11664,"cacheWriteTokens":206,"cumulative":true}}
33
+
34
+ event: message
35
+ data: {"type":"tool.done","seq":10,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000001","content":[{"type":"text","text":"{\"id\":\"00000000-0000-4000-8000-100000000004\",\"title\":\"Buy milk\",\"done\":false}"}],"outcome":"ok","isError":false,"messageId":"toolu_000000000000000000000001:result","structuredContent":{"id":"00000000-0000-4000-8000-100000000004","title":"Buy milk","done":false}}
36
+
37
+ event: message
38
+ data: {"type":"message.start","seq":11,"id":"msg_000000000000000000000002","turnId":"turn_00000000-0000-4000-8000-200000000001","threadId":"00000000-0000-4000-8000-100000000003","role":"assistant","model":"claude-sonnet-5"}
39
+
40
+ event: message
41
+ data: {"type":"reasoning.start","seq":12,"id":"msg_000000000000000000000002:reasoning:0","messageId":"msg_000000000000000000000002","turnId":"turn_00000000-0000-4000-8000-200000000001"}
42
+
43
+ event: message
44
+ data: {"type":"reasoning.delta","seq":13,"id":"msg_000000000000000000000002:reasoning:0","messageId":"msg_000000000000000000000002","delta":"","turnId":"turn_00000000-0000-4000-8000-200000000001"}
45
+
46
+ event: message
47
+ data: {"type":"reasoning.end","seq":14,"id":"msg_000000000000000000000002:reasoning:0","messageId":"msg_000000000000000000000002","turnId":"turn_00000000-0000-4000-8000-200000000001"}
48
+
49
+ event: message
50
+ data: {"type":"reasoning.opaque","seq":15,"id":"msg_000000000000000000000002:reasoning:0","messageId":"msg_000000000000000000000002","kind":"signature","value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","turnId":"turn_00000000-0000-4000-8000-200000000001","provider":"anthropic"}
51
+
52
+ event: message
53
+ data: {"type":"tool.start","seq":16,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000002","name":"mcp__todo__todo_list","index":0,"messageId":"msg_000000000000000000000002","providerExecuted":false}
54
+
55
+ event: message
56
+ data: {"type":"tool.args.delta","seq":17,"toolCallId":"toolu_000000000000000000000002","delta":"{}"}
57
+
58
+ event: message
59
+ data: {"type":"tool.args.assembled","seq":18,"toolCallId":"toolu_000000000000000000000002","input":{}}
60
+
61
+ event: message
62
+ data: {"type":"tool.start","seq":19,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000003","name":"mcp__ggui__ggui_discover","index":0,"messageId":"msg_000000000000000000000002","providerExecuted":false}
63
+
64
+ event: message
65
+ data: {"type":"tool.args.delta","seq":20,"toolCallId":"toolu_000000000000000000000003","delta":"{}"}
66
+
67
+ event: message
68
+ data: {"type":"tool.args.assembled","seq":21,"toolCallId":"toolu_000000000000000000000003","input":{}}
69
+
70
+ event: message
71
+ data: {"type":"message.end","seq":22,"id":"msg_000000000000000000000002","usage":{"inputTokens":2,"outputTokens":2,"cacheReadTokens":11870,"cacheWriteTokens":128,"cumulative":true}}
72
+
73
+ event: message
74
+ data: {"type":"tool.done","seq":23,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000002","content":[{"type":"text","text":"{\"todos\":[{\"id\":\"00000000-0000-4000-8000-100000000004\",\"title\":\"Buy milk\",\"done\":false}]}"}],"outcome":"ok","isError":false,"messageId":"toolu_000000000000000000000002:result","structuredContent":{"todos":[{"id":"00000000-0000-4000-8000-100000000004","title":"Buy milk","done":false}]}}
75
+
76
+ event: message
77
+ data: {"type":"tool.done","seq":24,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000003","content":[{"type":"text","text":"{\"protocolVersion\":\"draft-2026-06-12\",\"contentTypes\":[\"application/javascript+react\"],\"shellTypes\":[\"chat\",\"fullscreen\",\"spatial\"],\"adapterTypes\":[\"voice\",\"camera\",\"location\",\"bluetooth\"],\"componentCapabilities\":[\"network\",\"storage\",\"dom\",\"eval\",\"navigation\",\"device\"]}"}],"outcome":"ok","isError":false,"messageId":"toolu_000000000000000000000003:result","structuredContent":{"protocolVersion":"draft-2026-06-12","contentTypes":["application/javascript+react"],"shellTypes":["chat","fullscreen","spatial"],"adapterTypes":["voice","camera","location","bluetooth"],"componentCapabilities":["network","storage","dom","eval","navigation","device"]}}
78
+
79
+ event: message
80
+ data: {"type":"message.start","seq":25,"id":"msg_000000000000000000000003","turnId":"turn_00000000-0000-4000-8000-200000000001","threadId":"00000000-0000-4000-8000-100000000003","role":"assistant","model":"claude-sonnet-5"}
81
+
82
+ event: message
83
+ data: {"type":"reasoning.start","seq":26,"id":"msg_000000000000000000000003:reasoning:0","messageId":"msg_000000000000000000000003","turnId":"turn_00000000-0000-4000-8000-200000000001"}
84
+
85
+ event: message
86
+ data: {"type":"reasoning.delta","seq":27,"id":"msg_000000000000000000000003:reasoning:0","messageId":"msg_000000000000000000000003","delta":"","turnId":"turn_00000000-0000-4000-8000-200000000001"}
87
+
88
+ event: message
89
+ data: {"type":"reasoning.end","seq":28,"id":"msg_000000000000000000000003:reasoning:0","messageId":"msg_000000000000000000000003","turnId":"turn_00000000-0000-4000-8000-200000000001"}
90
+
91
+ event: message
92
+ data: {"type":"reasoning.opaque","seq":29,"id":"msg_000000000000000000000003:reasoning:0","messageId":"msg_000000000000000000000003","kind":"signature","value":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","turnId":"turn_00000000-0000-4000-8000-200000000001","provider":"anthropic"}
93
+
94
+ event: message
95
+ data: {"type":"tool.start","seq":30,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000004","name":"mcp__ggui__ggui_handshake","index":0,"messageId":"msg_000000000000000000000003","providerExecuted":false}
96
+
97
+ event: message
98
+ data: {"type":"tool.args.delta","seq":31,"toolCallId":"toolu_000000000000000000000004","delta":"{\"intent\":\"Show a rich todo list card with items that can be toggled done/undone\",\"blueprintDraft\":{\"contract\":{\"propsSpec\":{\"properties\":{\"todos\":{\"schema\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"},\"title\":{\"type\":\"string\"},\"done\":{\"type\":\"boolean\"}},\"required\":[\"id\",\"title\",\"done\"]}},\"description\":\"List of todo items\"}}},\"actionSpec\":{\"toggle\":{\"label\":\"Toggle done\",\"description\":\"Toggle a todo's done state\",\"schema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\"}},\"required\":[\"id\"]},\"nextStep\":\"todo_toggle\"}},\"agentCapabilities\":{\"tools\":{\"todo_toggle\":{\"toolInfo\":{\"description\":\"Flip a todo's done state.\",\"inputSchema\":{\"type\":\"object\",\"properties\":{\"id\":{\"type\":\"string\",\"description\":\"Todo id.\"}},\"required\":[\"id\"]}},\"serverInfo\":{\"name\":\"todo\"}}}}}}}"}
99
+
100
+ event: message
101
+ data: {"type":"tool.args.assembled","seq":32,"toolCallId":"toolu_000000000000000000000004","input":{"intent":"Show a rich todo list card with items that can be toggled done/undone","blueprintDraft":{"contract":{"propsSpec":{"properties":{"todos":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"done":{"type":"boolean"}},"required":["id","title","done"]}},"description":"List of todo items"}}},"actionSpec":{"toggle":{"label":"Toggle done","description":"Toggle a todo's done state","schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"nextStep":"todo_toggle"}},"agentCapabilities":{"tools":{"todo_toggle":{"toolInfo":{"description":"Flip a todo's done state.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Todo id."}},"required":["id"]}},"serverInfo":{"name":"todo"}}}}}}}}
102
+
103
+ event: message
104
+ data: {"type":"message.end","seq":33,"id":"msg_000000000000000000000003","usage":{"inputTokens":2,"outputTokens":4,"cacheReadTokens":11998,"cacheWriteTokens":320,"cumulative":true}}
105
+
106
+ event: message
107
+ data: {"type":"tool.done","seq":34,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000004","content":[{"type":"text","text":"{\"handshakeId\":\"00000000-0000-4000-8000-100000000005\",\"action\":\"create\",\"suggestion\":{\"origin\":\"agent\",\"rationale\":\"agent draft passed validateContract; accepted verbatim (origin: agent)\",\"blueprintMeta\":{\"contractHash\":\"c10a20553df2349b\",\"variance\":{}},\"proposedContractSummary\":\"slots=∅; actions=toggle(id); streams=∅; props=todos:array\",\"validationFindings\":[{\"code\":\"LINT_MISSING_USAGE\",\"severity\":\"warn\",\"path\":\"agentCapabilities.tools.todo_toggle.usage\",\"message\":\"agentCapabilities.tools.todo_toggle has no 'usage' prose. The agent's reasoning loop reads usage as context-of-use; tools without it tend to get invoked at the wrong time.\"},{\"code\":\"LINT_MISSING_EXAMPLE\",\"severity\":\"warn\",\"path\":\"agentCapabilities.tools.todo_toggle.example\",\"message\":\"agentCapabilities.tools.todo_toggle has no 'example'. Examples ground the agent's invocation patterns; tools without one are harder to use correctly on the first call.\"}]},\"nextStep\":{\"tool\":\"ggui_render\",\"example\":\"ggui_render({\\\"handshakeId\\\":\\\"00000000-0000-4000-8000-100000000005\\\",\\\"props\\\":{}})\"}}"}],"outcome":"ok","isError":false,"messageId":"toolu_000000000000000000000004:result","structuredContent":{"handshakeId":"00000000-0000-4000-8000-100000000005","action":"create","suggestion":{"origin":"agent","rationale":"agent draft passed validateContract; accepted verbatim (origin: agent)","blueprintMeta":{"contractHash":"c10a20553df2349b","variance":{}},"proposedContractSummary":"slots=∅; actions=toggle(id); streams=∅; props=todos:array","validationFindings":[{"code":"LINT_MISSING_USAGE","severity":"warn","path":"agentCapabilities.tools.todo_toggle.usage","message":"agentCapabilities.tools.todo_toggle has no 'usage' prose. The agent's reasoning loop reads usage as context-of-use; tools without it tend to get invoked at the wrong time."},{"code":"LINT_MISSING_EXAMPLE","severity":"warn","path":"agentCapabilities.tools.todo_toggle.example","message":"agentCapabilities.tools.todo_toggle has no 'example'. Examples ground the agent's invocation patterns; tools without one are harder to use correctly on the first call."}]},"nextStep":{"tool":"ggui_render","example":"ggui_render({\"handshakeId\":\"00000000-0000-4000-8000-100000000005\",\"props\":{}})"}}}
108
+
109
+ event: message
110
+ data: {"type":"message.start","seq":35,"id":"msg_000000000000000000000004","turnId":"turn_00000000-0000-4000-8000-200000000001","threadId":"00000000-0000-4000-8000-100000000003","role":"assistant","model":"claude-sonnet-5"}
111
+
112
+ event: message
113
+ data: {"type":"tool.start","seq":36,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000005","name":"mcp__ggui__ggui_render","index":0,"messageId":"msg_000000000000000000000004","providerExecuted":false}
114
+
115
+ event: message
116
+ data: {"type":"tool.args.delta","seq":37,"toolCallId":"toolu_000000000000000000000005","delta":"{\"handshakeId\":\"00000000-0000-4000-8000-100000000005\",\"props\":{\"todos\":[{\"id\":\"00000000-0000-4000-8000-100000000004\",\"title\":\"Buy milk\",\"done\":false}]}}"}
117
+
118
+ event: message
119
+ data: {"type":"tool.args.assembled","seq":38,"toolCallId":"toolu_000000000000000000000005","input":{"handshakeId":"00000000-0000-4000-8000-100000000005","props":{"todos":[{"id":"00000000-0000-4000-8000-100000000004","title":"Buy milk","done":false}]}}}
120
+
121
+ event: message
122
+ data: {"type":"message.end","seq":39,"id":"msg_000000000000000000000004","usage":{"inputTokens":2,"outputTokens":66,"cacheReadTokens":12318,"cacheWriteTokens":956,"cumulative":true}}
123
+
124
+ event: message
125
+ data: {"type":"tool.done","seq":40,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000005","content":[{"type":"text","text":"{\"sessionId\":\"render_00000000-0000-4000-8000-300000000001\",\"resourceUri\":\"ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c10a20553df2349b\",\"action\":\"create\",\"contractHash\":\"c10a20553df2349b\",\"blueprintId\":\"bp_00000000-0000-4000-8000-400000000001\",\"variantKey\":\"44136fa355b3678a\",\"cache\":{\"hit\":false,\"llmCallsAvoided\":0,\"kind\":\"cold\",\"reason\":\"cold: generated fresh — no stored component was reused for this render\"},\"nextStep\":{\"tool\":\"ggui_consume\",\"description\":\"Drain the action pipe for this render — long-polls until a user gesture arrives or 15s timeout.\",\"example\":\"ggui_consume({ sessionId: \\\"render_00000000-0000-4000-8000-300000000001\\\" })\",\"args\":{\"sessionId\":\"render_00000000-0000-4000-8000-300000000001\"}}}"}],"outcome":"ok","isError":false,"messageId":"toolu_000000000000000000000005:result","uiData":{"sessionId":"render_00000000-0000-4000-8000-300000000001","resourceUri":"ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c10a20553df2349b","action":"create","contractHash":"c10a20553df2349b","blueprintId":"bp_00000000-0000-4000-8000-400000000001","variantKey":"44136fa355b3678a","cache":{"hit":false,"llmCallsAvoided":0,"kind":"cold","reason":"cold: generated fresh — no stored component was reused for this render"},"nextStep":{"tool":"ggui_consume","description":"Drain the action pipe for this render — long-polls until a user gesture arrives or 15s timeout.","example":"ggui_consume({ sessionId: \"render_00000000-0000-4000-8000-300000000001\" })","args":{"sessionId":"render_00000000-0000-4000-8000-300000000001"}}},"_meta":{"ai.ggui/render":{"sessionId":"render_00000000-0000-4000-8000-300000000001","appId":"APP00000","runtimeUrl":"https://dev.mcp.sandbox.ggui.ai/_ggui/iframe-runtime.js","wsUrl":"wss://dev.mcp.sandbox.ggui.ai/ws","wsToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJraW5kIjoid3MiLCJpYXQiOjAsImV4cCI6MH0.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA","expiresAt":"2026-07-29T11:07:58.000Z","lastSequence":0,"propsJson":"{\"todos\":[{\"id\":\"00000000-0000-4000-8000-100000000004\",\"title\":\"Buy milk\",\"done\":false}]}"},"ui":{"resourceUri":"ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c10a20553df2349b"},"ui/resourceUri":"ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c10a20553df2349b"}}
126
+
127
+ event: message
128
+ data: {"type":"message.start","seq":41,"id":"msg_000000000000000000000005","turnId":"turn_00000000-0000-4000-8000-200000000001","threadId":"00000000-0000-4000-8000-100000000003","role":"assistant","model":"claude-sonnet-5"}
129
+
130
+ event: message
131
+ data: {"type":"text.start","seq":42,"id":"msg_000000000000000000000005:text:0","messageId":"msg_000000000000000000000005","turnId":"turn_00000000-0000-4000-8000-200000000001"}
132
+
133
+ event: message
134
+ data: {"type":"text.delta","seq":43,"id":"msg_000000000000000000000005:text:0","messageId":"msg_000000000000000000000005","delta":"I created \"Buy milk\" and rendered your todo list as a rich UI card — it currently shows that one item, unchecked. I'll keep listening for any toggle actions you make in the card.","turnId":"turn_00000000-0000-4000-8000-200000000001"}
135
+
136
+ event: message
137
+ data: {"type":"text.end","seq":44,"id":"msg_000000000000000000000005:text:0","messageId":"msg_000000000000000000000005","turnId":"turn_00000000-0000-4000-8000-200000000001"}
138
+
139
+ event: message
140
+ data: {"type":"tool.start","seq":45,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000006","name":"mcp__ggui__ggui_consume","index":0,"messageId":"msg_000000000000000000000005","providerExecuted":false}
141
+
142
+ event: message
143
+ data: {"type":"tool.args.delta","seq":46,"toolCallId":"toolu_000000000000000000000006","delta":"{\"sessionId\":\"render_00000000-0000-4000-8000-300000000001\",\"timeout\":20}"}
144
+
145
+ event: message
146
+ data: {"type":"tool.args.assembled","seq":47,"toolCallId":"toolu_000000000000000000000006","input":{"sessionId":"render_00000000-0000-4000-8000-300000000001","timeout":20}}
147
+
148
+ event: message
149
+ data: {"type":"message.end","seq":48,"id":"msg_000000000000000000000005","usage":{"inputTokens":2,"outputTokens":1,"cacheReadTokens":13274,"cacheWriteTokens":543,"cumulative":true}}
150
+
151
+ event: message
152
+ data: {"type":"tool.done","seq":49,"turnId":"turn_00000000-0000-4000-8000-200000000001","toolCallId":"toolu_000000000000000000000006","content":[{"type":"text","text":"{\"events\":[],\"status\":\"active\"}"}],"outcome":"ok","isError":false,"messageId":"toolu_000000000000000000000006:result","structuredContent":{"events":[],"status":"active"}}
153
+
154
+ event: message
155
+ data: {"type":"message.start","seq":50,"id":"msg_000000000000000000000006","turnId":"turn_00000000-0000-4000-8000-200000000001","threadId":"00000000-0000-4000-8000-100000000003","role":"assistant","model":"claude-sonnet-5"}
156
+
157
+ event: message
158
+ data: {"type":"text.start","seq":51,"id":"msg_000000000000000000000006:text:0","messageId":"msg_000000000000000000000006","turnId":"turn_00000000-0000-4000-8000-200000000001"}
159
+
160
+ event: message
161
+ data: {"type":"text.delta","seq":52,"id":"msg_000000000000000000000006:text:0","messageId":"msg_000000000000000000000006","delta":"No actions yet — the card is live and waiting for you. Let me know if you'd like to check it off, add more todos, or anything else, and I'll update the UI accordingly.","turnId":"turn_00000000-0000-4000-8000-200000000001"}
162
+
163
+ event: message
164
+ data: {"type":"text.end","seq":53,"id":"msg_000000000000000000000006:text:0","messageId":"msg_000000000000000000000006","turnId":"turn_00000000-0000-4000-8000-200000000001"}
165
+
166
+ event: message
167
+ data: {"type":"message.end","seq":54,"id":"msg_000000000000000000000006","usage":{"inputTokens":2,"outputTokens":1,"cacheReadTokens":13817,"cacheWriteTokens":189,"cumulative":true}}
168
+
169
+ event: message
170
+ data: {"type":"turn.done","seq":55,"turnId":"turn_00000000-0000-4000-8000-200000000001","outcome":{"type":"success","result":"No actions yet — the card is live and waiting for you. Let me know if you'd like to check it off, add more todos, or anything else, and I'll update the UI accordingly."},"finishReason":"stop","usage":{"inputTokens":12,"outputTokens":1112,"cacheReadTokens":74941,"cacheWriteTokens":2342,"serverToolRequests":0,"costUsd":0.0479808,"cumulative":true,"byModel":{"claude-sonnet-5":{"inputTokens":12,"outputTokens":1112,"cacheReadTokens":74941,"cacheWriteTokens":2342,"costUsd":0.0479808,"serverToolRequests":0,"cumulative":true}}}}
171
+
172
+ event: done
173
+ data: {"stopReason":"end_turn","threadId":"00000000-0000-4000-8000-100000000002","userSeq":1,"agentSeq":2}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The corpus's turn driver: recorded `InvokeTurnEvent[]` sequences →
3
+ * `TranscriptInputs`, applying the SAME accumulation rules the live hook
4
+ * documents (status transitions, cumulative text, agEvents → the REAL
5
+ * `@silverprotocol/core` Reducer). Test-only plumbing — excluded from the
6
+ * build; the 3b live/history assemblers are the production twins.
7
+ */
8
+ import { Reducer, type AgEvent } from "@silverprotocol/core";
9
+ import type { InvokeTurnEvent } from "@guuey/agent-client";
10
+ import type { PromptItemInput, TranscriptInputs, TranscriptMessage } from "../types.js";
11
+
12
+ export interface DriveOptions {
13
+ userText?: string;
14
+ clientMessageId?: string;
15
+ /** Override the derived status (mid-stream plan points). */
16
+ finalStatus?: TranscriptInputs["status"];
17
+ statusElapsedMs?: number;
18
+ aborted?: boolean;
19
+ adopted?: boolean;
20
+ sendStates?: TranscriptInputs["sendStates"];
21
+ viewPhases?: TranscriptInputs["viewPhases"];
22
+ historyCards?: TranscriptInputs["historyCards"];
23
+ historyState?: TranscriptInputs["historyState"];
24
+ /** Resolve accumulated prompts to this state (fixture 4's answered leg). */
25
+ promptState?: PromptItemInput["state"];
26
+ }
27
+
28
+ export function driveTurn(events: InvokeTurnEvent[], opts: DriveOptions = {}): TranscriptInputs {
29
+ const reducer = new Reducer();
30
+ let sawAgEvents = false;
31
+ let status: TranscriptInputs["status"] = "connecting";
32
+ let assistantText = "";
33
+ let activeTool: string | null = null;
34
+ let error: TranscriptInputs["error"] = null;
35
+ const prompts: PromptItemInput[] = [];
36
+ let done = false;
37
+
38
+ for (const ev of events) {
39
+ switch (ev.kind) {
40
+ case "session":
41
+ if (status === "connecting") status = "thinking";
42
+ break;
43
+ case "message":
44
+ if (ev.status !== undefined) status = ev.status;
45
+ if (ev.activeTool !== undefined) activeTool = ev.activeTool;
46
+ assistantText = ev.assistantText;
47
+ for (const ag of ev.agEvents) {
48
+ sawAgEvents = true;
49
+ reducer.push(ag);
50
+ }
51
+ break;
52
+ case "error":
53
+ error = { message: ev.message, code: ev.code };
54
+ status = "ready";
55
+ break;
56
+ case "profile-consent":
57
+ prompts.push({
58
+ id: `consent-${prompts.length}`,
59
+ kind: "consent",
60
+ appId: ev.request.appId,
61
+ requested: ev.request.requested,
62
+ state: opts.promptState ?? "pending",
63
+ });
64
+ break;
65
+ case "profile-link":
66
+ prompts.push({
67
+ id: `link-${prompts.length}`,
68
+ kind: "link",
69
+ appId: ev.request.appId,
70
+ requested: ev.request.requested,
71
+ state: opts.promptState ?? "pending",
72
+ });
73
+ break;
74
+ case "done":
75
+ done = true;
76
+ status = "ready";
77
+ activeTool = null;
78
+ break;
79
+ }
80
+ }
81
+ if (opts.aborted === true) status = "ready";
82
+
83
+ const messages: TranscriptMessage[] = [];
84
+ if (opts.userText !== undefined) {
85
+ messages.push({
86
+ role: "user",
87
+ text: opts.userText,
88
+ ...(opts.clientMessageId !== undefined ? { clientMessageId: opts.clientMessageId } : {}),
89
+ });
90
+ }
91
+ // A settled BYPASS turn's text moves into the flat transcript (the hook's
92
+ // behaviour); silver turns stay owned by the fold.
93
+ if (done && !sawAgEvents && assistantText !== "" && opts.aborted !== true) {
94
+ messages.push({ role: "assistant", text: assistantText });
95
+ }
96
+
97
+ return {
98
+ result: sawAgEvents ? reducer.result() : null,
99
+ assistantText,
100
+ status: opts.finalStatus ?? status,
101
+ statusElapsedMs: opts.statusElapsedMs ?? 0,
102
+ activeTool,
103
+ error,
104
+ prompts,
105
+ messages,
106
+ ...(opts.sendStates !== undefined ? { sendStates: opts.sendStates } : {}),
107
+ ...(opts.viewPhases !== undefined ? { viewPhases: opts.viewPhases } : {}),
108
+ ...(opts.historyCards !== undefined ? { historyCards: opts.historyCards } : {}),
109
+ ...(opts.historyState !== undefined ? { historyState: opts.historyState } : {}),
110
+ ...(opts.aborted === true ? { aborted: true } : {}),
111
+ ...(opts.adopted === true ? { adopted: true } : {}),
112
+ };
113
+ }
114
+
115
+ // ─── Event builders (shared fixture vocabulary) ───────────────────────────
116
+
117
+ export const THREAD = "thread-corpus";
118
+ export const TURN = "turn-corpus";
119
+ export const MSG = "msg-corpus";
120
+
121
+ /** A per-fixture monotonically increasing seq source. */
122
+ export function seqSource(): () => number {
123
+ let n = 0;
124
+ return () => n++;
125
+ }
126
+
127
+ /** The silver turn's lifecycle boot: turn.start + an open assistant message. */
128
+ export function boot(next: () => number): AgEvent[] {
129
+ return [
130
+ { type: "turn.start", threadId: THREAD, turnId: TURN, seq: next() },
131
+ { type: "message.start", id: MSG, role: "assistant", turnId: TURN, threadId: THREAD, seq: next() },
132
+ ];
133
+ }
134
+
135
+ /** A complete streamed text part. */
136
+ export function textPart(next: () => number, id: string, text: string): AgEvent[] {
137
+ return [
138
+ { type: "text.start", id, seq: next() },
139
+ { type: "text.delta", id, delta: text, seq: next() },
140
+ { type: "text.end", id, seq: next() },
141
+ ];
142
+ }
143
+
144
+ /** A complete tool call: start + assembled args + done. */
145
+ export function toolPart(
146
+ next: () => number,
147
+ toolCallId: string,
148
+ name: string,
149
+ result: Partial<Extract<AgEvent, { type: "tool.done" }>> = {},
150
+ ): AgEvent[] {
151
+ return [
152
+ { type: "tool.start", toolCallId, name, seq: next() },
153
+ { type: "tool.args.assembled", toolCallId, input: { q: name }, seq: next() },
154
+ {
155
+ type: "tool.done",
156
+ toolCallId,
157
+ content: [{ type: "text", text: "ok" }],
158
+ outcome: "ok",
159
+ ...result,
160
+ seq: next(),
161
+ },
162
+ ];
163
+ }
164
+
165
+ /** Wrap agEvents into the `message` frame the pod emits. */
166
+ export function frame(
167
+ agEvents: AgEvent[],
168
+ extra: {
169
+ status?: "thinking" | "using-tool" | "responding";
170
+ activeTool?: string | null;
171
+ text?: string;
172
+ } = {},
173
+ ): InvokeTurnEvent {
174
+ return {
175
+ kind: "message",
176
+ ...(extra.status !== undefined ? { status: extra.status } : {}),
177
+ ...(extra.activeTool !== undefined ? { activeTool: extra.activeTool } : {}),
178
+ assistantText: extra.text ?? "",
179
+ agEvents,
180
+ };
181
+ }
182
+
183
+ export const session: InvokeTurnEvent = { kind: "session", threadId: THREAD };
184
+ export const doneEvent: InvokeTurnEvent = { kind: "done", stopReason: "end_turn" };