@swirls/looms 0.1.0 → 0.2.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 (162) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/actor/host.js +2 -1
  3. package/dist/agent/debugger/agent-chat.d.ts +2 -0
  4. package/dist/agent/debugger/agent-chat.js +176 -0
  5. package/dist/agent/debugger/index.d.ts +5 -0
  6. package/dist/agent/debugger/index.js +19 -0
  7. package/dist/agent/debugger/summarize.d.ts +2 -0
  8. package/dist/agent/debugger/summarize.js +51 -0
  9. package/dist/agent/debugger/token-usage.d.ts +3 -0
  10. package/dist/agent/debugger/token-usage.js +11 -0
  11. package/dist/agent/definitions.d.ts +3 -11
  12. package/dist/agent/definitions.js +5 -6
  13. package/dist/agent/signals.d.ts +2 -0
  14. package/dist/agent/signals.js +18 -16
  15. package/dist/agent/tool-schema.d.ts +1 -17
  16. package/dist/agent/tool-schema.js +3 -42
  17. package/dist/ai-vercel/index.d.ts +1 -1
  18. package/dist/ai-vercel/vercel-evaluator.d.ts +21 -8
  19. package/dist/ai-vercel/vercel-evaluator.js +7 -2
  20. package/dist/approval/debugger/approvals-view.d.ts +3 -0
  21. package/dist/approval/debugger/approvals-view.js +16 -0
  22. package/dist/approval/debugger/index.d.ts +4 -0
  23. package/dist/approval/debugger/index.js +11 -0
  24. package/dist/approval/debugger/summarize.d.ts +2 -0
  25. package/dist/approval/debugger/summarize.js +17 -0
  26. package/dist/client/client.d.ts +18 -0
  27. package/dist/client/client.js +3 -1
  28. package/dist/cloudflare/router.d.ts +1 -1
  29. package/dist/cloudflare/router.js +2 -2
  30. package/dist/core/compose.js +1 -0
  31. package/dist/core/debugger-host.d.ts +16 -0
  32. package/dist/core/debugger-host.js +1 -0
  33. package/dist/core/index.d.ts +2 -0
  34. package/dist/core/index.js +1 -0
  35. package/dist/core/module.d.ts +2 -0
  36. package/dist/core/published.d.ts +43 -0
  37. package/dist/core/published.js +44 -0
  38. package/dist/core/schema.d.ts +3 -1
  39. package/dist/core/schema.js +38 -0
  40. package/dist/core/snapshot-store.d.ts +4 -0
  41. package/dist/core/snapshot-store.js +7 -0
  42. package/dist/core/sqlite-store.js +39 -0
  43. package/dist/debugger/app/assets/index-B3Ah8WAE.css +2 -0
  44. package/dist/debugger/app/assets/index-Qb371t_O.js +22 -0
  45. package/dist/debugger/app/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  46. package/dist/debugger/app/assets/routes-CjtEHgPQ.js +47 -0
  47. package/dist/debugger/app/index.html +14 -0
  48. package/dist/debugger/components/composer.d.ts +17 -0
  49. package/dist/debugger/components/composer.js +25 -0
  50. package/dist/debugger/components/debugger-split.d.ts +2 -1
  51. package/dist/debugger/components/debugger-split.js +2 -2
  52. package/dist/debugger/components/event-inspector.d.ts +2 -1
  53. package/dist/debugger/components/event-inspector.js +25 -16
  54. package/dist/debugger/components/event-stream.d.ts +2 -1
  55. package/dist/debugger/components/event-stream.js +12 -71
  56. package/dist/debugger/components/follow-list.d.ts +19 -0
  57. package/dist/debugger/components/follow-list.js +96 -0
  58. package/dist/debugger/components/json-tree.d.ts +11 -0
  59. package/dist/debugger/components/json-tree.js +120 -0
  60. package/dist/debugger/components/json-view.js +2 -2
  61. package/dist/debugger/components/run-tree.js +53 -20
  62. package/dist/debugger/components/status-dot.d.ts +4 -0
  63. package/dist/debugger/components/status-dot.js +6 -2
  64. package/dist/debugger/context.d.ts +14 -0
  65. package/dist/debugger/context.js +17 -0
  66. package/dist/debugger/contracts.d.ts +7 -4
  67. package/dist/debugger/events/builtin-families.d.ts +3 -0
  68. package/dist/debugger/events/builtin-families.js +9 -0
  69. package/dist/debugger/events/catalog.d.ts +3 -4
  70. package/dist/debugger/events/catalog.js +27 -17
  71. package/dist/debugger/events/protocol-summarize.d.ts +1 -2
  72. package/dist/debugger/events/protocol-summarize.js +29 -37
  73. package/dist/debugger/index.d.ts +20 -6
  74. package/dist/debugger/index.js +19 -5
  75. package/dist/debugger/lib/cn.d.ts +1 -0
  76. package/dist/debugger/lib/cn.js +3 -0
  77. package/dist/debugger/lib/event-meta.d.ts +2 -3
  78. package/dist/debugger/lib/event-meta.js +6 -11
  79. package/dist/debugger/lib/json.d.ts +25 -0
  80. package/dist/debugger/lib/json.js +87 -0
  81. package/dist/debugger/lib/payload.d.ts +7 -0
  82. package/dist/debugger/lib/payload.js +11 -0
  83. package/dist/debugger/lib/time.d.ts +3 -0
  84. package/dist/debugger/lib/time.js +24 -0
  85. package/dist/debugger/plugin.d.ts +36 -0
  86. package/dist/debugger/plugin.js +14 -0
  87. package/dist/debugger/server/index.d.ts +14 -0
  88. package/dist/debugger/server/index.js +114 -0
  89. package/dist/debugger/shell/debugger-shell.d.ts +15 -0
  90. package/dist/debugger/shell/debugger-shell.js +43 -0
  91. package/dist/debugger/shell/definition-key.d.ts +3 -0
  92. package/dist/debugger/shell/definition-key.js +4 -0
  93. package/dist/debugger/shell/events-pane.d.ts +12 -0
  94. package/dist/debugger/shell/events-pane.js +25 -0
  95. package/dist/debugger/shell/projections-panel.d.ts +4 -0
  96. package/dist/debugger/shell/projections-panel.js +59 -0
  97. package/dist/debugger/shell/run-controls.d.ts +4 -0
  98. package/dist/debugger/shell/run-controls.js +72 -0
  99. package/dist/debugger/shell/run-search.d.ts +6 -0
  100. package/dist/debugger/shell/run-search.js +102 -0
  101. package/dist/debugger/shell/run-type-menu.d.ts +6 -0
  102. package/dist/debugger/shell/run-type-menu.js +11 -0
  103. package/dist/debugger/shell/schema-form.d.ts +31 -0
  104. package/dist/debugger/shell/schema-form.js +117 -0
  105. package/dist/debugger/shell/start-form.d.ts +3 -0
  106. package/dist/debugger/shell/start-form.js +75 -0
  107. package/dist/debugger/shell/use-start-run.d.ts +14 -0
  108. package/dist/debugger/shell/use-start-run.js +32 -0
  109. package/dist/debugger/shell/workspace.d.ts +6 -0
  110. package/dist/debugger/shell/workspace.js +15 -0
  111. package/dist/debugger/styles.css +111 -27
  112. package/dist/debugger/ui/button.d.ts +24 -0
  113. package/dist/debugger/ui/button.js +24 -0
  114. package/dist/debugger/ui/command.d.ts +9 -0
  115. package/dist/debugger/ui/command.js +24 -0
  116. package/dist/debugger/ui/dropdown-menu.d.ts +13 -0
  117. package/dist/debugger/ui/dropdown-menu.js +37 -0
  118. package/dist/debugger/ui/panel.d.ts +14 -0
  119. package/dist/debugger/ui/panel.js +16 -0
  120. package/dist/debugger/ui/popover.d.ts +7 -0
  121. package/dist/debugger/ui/popover.js +17 -0
  122. package/dist/debugger/ui/resizable.d.ts +7 -0
  123. package/dist/debugger/ui/resizable.js +14 -0
  124. package/dist/debugger/ui/scroll-area.d.ts +2 -1
  125. package/dist/debugger/ui/scroll-area.js +5 -3
  126. package/dist/debugger/ui/select.d.ts +10 -0
  127. package/dist/debugger/ui/select.js +21 -0
  128. package/dist/debugger/ui/tabs.js +2 -2
  129. package/dist/debugger/ui/textarea.d.ts +3 -0
  130. package/dist/debugger/ui/textarea.js +7 -0
  131. package/dist/react/store.d.ts +1 -1
  132. package/dist/react/store.js +1 -1
  133. package/dist/runtime/looms.d.ts +9 -1
  134. package/dist/runtime/looms.js +4 -2
  135. package/dist/runtime/repository.js +16 -1
  136. package/dist/runtime/runtime.js +16 -1
  137. package/dist/runtime/server/auth.d.ts +1 -0
  138. package/dist/runtime/server/auth.js +5 -0
  139. package/dist/runtime/server/debugger-host.d.ts +11 -0
  140. package/dist/runtime/server/debugger-host.js +32 -0
  141. package/dist/runtime/server/fetch-handler.d.ts +5 -1
  142. package/dist/runtime/server/fetch-handler.js +24 -13
  143. package/dist/runtime/server/http.d.ts +1 -0
  144. package/dist/runtime/server/http.js +4 -0
  145. package/dist/runtime/server/routes/definitions.d.ts +3 -0
  146. package/dist/runtime/server/routes/definitions.js +20 -0
  147. package/dist/runtime/server/routes/runs.js +8 -1
  148. package/dist/runtime/server.d.ts +3 -1
  149. package/dist/runtime/server.js +3 -1
  150. package/dist/runtime/types.d.ts +3 -1
  151. package/dist/s2/snapshot-store.js +43 -1
  152. package/dist/workflow/debugger/index.d.ts +4 -0
  153. package/dist/workflow/debugger/index.js +11 -0
  154. package/dist/workflow/debugger/nodes-view.d.ts +3 -0
  155. package/dist/workflow/debugger/nodes-view.js +13 -0
  156. package/dist/workflow/debugger/summarize.d.ts +2 -0
  157. package/dist/workflow/debugger/summarize.js +30 -0
  158. package/package.json +20 -1
  159. package/dist/debugger/events/domain-summarize.d.ts +0 -3
  160. package/dist/debugger/events/domain-summarize.js +0 -107
  161. package/dist/debugger/events/family.d.ts +0 -8
  162. package/dist/debugger/events/family.js +0 -44
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @swirls/looms
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6ed3a5f: `vercelEvaluator({ provider, model })` resolves model ids through an AI SDK provider such as `createTypeSafeAi({ apiKey })`, so callers can use their own TypeSafe key instead of the AI Gateway.
8
+ - 6ed3a5f: Serve a debugger UI from `createLooms` with `debuggerUi()` from `@swirls/looms/debugger/server`. Modules ship their own debugger views from `@swirls/looms/agent/debugger`, `@swirls/looms/workflow/debugger`, and `@swirls/looms/approval/debugger`. `GET /definitions` lists the definitions and projections that UI can start and inspect, and `GET /runs/summaries` returns each run's status and definition.
9
+
10
+ `@swirls/looms/debugger` also exports the building blocks the hosted UI uses, so module views can match it: `JsonTree` (collapsible JSON that expands JSON held in strings), `FollowList` (a virtualized list that follows new items), `Composer`, `Button`, `Input`, `Textarea`, `Select`, `PanelHeader`, `PanelTitle`, `EmptyState`, `Kbd`, and `StatusBadge`. To render `DebuggerShell` in your own app, import `@swirls/looms/debugger/styles.css` after Tailwind CSS v4. The stylesheet brings its theme and scans the kit and the built-in module views.
11
+
12
+ Breaking changes:
13
+
14
+ - Tool helpers and tool definitions no longer accept `inputSchema`. Pass `input` and the JSON Schema is derived from it.
15
+ - `@swirls/looms/debugger` no longer exports `EventFamily`, `DEFAULT_FAMILIES`, `familyClass`, `familyFromPrefix`, `summarizeDomainEvent`, `summarizeEvent`, `searchText`, or `createDefaultCatalog`. Build a catalog with `createEventCatalog(plugins)` or use `defaultEventCatalog`.
16
+ - `EventStreamCatalog.familyClass` is now `familyColor`, which returns a CSS color, and `searchText` is required.
17
+
3
18
  ## 0.1.0
4
19
 
5
20
  ### Minor Changes
@@ -76,7 +76,8 @@ export function createLocalActorHost(options) {
76
76
  if (url.pathname === '/health') {
77
77
  return Promise.resolve(Response.json({ ok: true }));
78
78
  }
79
- if (req.method === 'GET' && url.pathname === '/runs') {
79
+ if (req.method === 'GET' &&
80
+ (url.pathname === '/runs' || url.pathname === '/runs/summaries')) {
80
81
  return Promise.resolve(Response.json({
81
82
  error: 'Global run listing is not supported in the actor host without a global index',
82
83
  }, { status: 501 }));
@@ -0,0 +1,2 @@
1
+ import { type WorkspaceContext } from '@swirls/looms/debugger';
2
+ export declare function AgentChat(context: WorkspaceContext): import("react").JSX.Element;
@@ -0,0 +1,176 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Bot, CornerDownRight, MessageSquare, Settings2, User, Wrench } from 'lucide-react';
3
+ import { useMemo, useState } from 'react';
4
+ import { createEventId, isJsonObject, isJsonString } from '@swirls/looms/core';
5
+ import { cn, Composer, EmptyState, errorMessage, FollowList, inputForm, JsonTree, messageInput, parseStructuredJson, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, shortId, useDebugger, useStartRun, } from '@swirls/looms/debugger';
6
+ import { createFold, useEventFold, useProjection, useRunStore, useRunSummary } from '@swirls/looms/react';
7
+ import { conversation } from '../projections.js';
8
+ import { agentSessionMessageEvents, userMessage } from '../signals.js';
9
+ /** Child thread of the session turn that is still open, from the events the store already has. */
10
+ function activeSessionChild(events) {
11
+ let child;
12
+ for (const event of events) {
13
+ if (!isJsonObject(event.payload)) {
14
+ continue;
15
+ }
16
+ if (event.type === 'agent.session.turn.started' && isJsonString(event.payload.childThreadId)) {
17
+ child = event.payload.childThreadId;
18
+ continue;
19
+ }
20
+ const tag = event.payload.tag;
21
+ if (event.type === 'runtime.wait.satisfied' &&
22
+ isJsonObject(tag) &&
23
+ tag.kind === 'agent-session-turn') {
24
+ child = undefined;
25
+ }
26
+ }
27
+ return child;
28
+ }
29
+ const streamingFold = createFold({
30
+ name: 'agentStreamingText',
31
+ initialState: { lastMessageSeq: -1, turnStarted: false, parts: [] },
32
+ includeEphemeral: true,
33
+ reduce(state, event) {
34
+ if (event.type === 'agent.message') {
35
+ return { lastMessageSeq: event.seq, turnStarted: false, parts: [] };
36
+ }
37
+ if (event.seq <= state.lastMessageSeq) {
38
+ return state;
39
+ }
40
+ if (event.type === 'agent.turn.started') {
41
+ return { ...state, turnStarted: true };
42
+ }
43
+ if (event.type === 'agent.turn.text_delta' &&
44
+ isJsonObject(event.payload) &&
45
+ isJsonString(event.payload.delta)) {
46
+ return {
47
+ ...state,
48
+ turnStarted: true,
49
+ parts: state.parts.concat(event.payload.delta),
50
+ };
51
+ }
52
+ return state;
53
+ },
54
+ });
55
+ const deliveryOptions = [
56
+ { value: 'followUp', label: 'Follow up', description: 'Queue after the current turn' },
57
+ { value: 'steer', label: 'Steer', description: 'Redirect the active turn' },
58
+ { value: 'nextTurn', label: 'Next turn', description: 'Hold until the next turn starts' },
59
+ ];
60
+ function RoleLabel({ icon, children }) {
61
+ return (_jsxs("div", { className: "text-muted-foreground mb-1.5 flex items-center gap-1.5 text-[11px] font-medium [&_svg]:size-3", children: [icon, children] }));
62
+ }
63
+ function MessageText({ content, className }) {
64
+ const structured = useMemo(() => parseStructuredJson(content), [content]);
65
+ if (structured !== undefined) {
66
+ return _jsx(JsonTree, { value: structured, defaultExpandDepth: 2 });
67
+ }
68
+ return (_jsx("div", { className: cn('text-[13px] leading-relaxed break-words whitespace-pre-wrap', className), children: content }));
69
+ }
70
+ function ToolCard({ icon, name, meta, children, }) {
71
+ return (_jsxs("div", { className: "border-border bg-card/60 overflow-hidden rounded-lg border", children: [_jsxs("div", { className: "border-border/70 flex h-7 items-center gap-1.5 border-b px-2.5 text-[11px] [&_svg]:size-3", children: [_jsx("span", { className: "text-muted-foreground", children: icon }), _jsx("span", { className: "font-mono font-medium", children: name }), meta ? (_jsx("span", { className: "text-muted-foreground ml-auto truncate font-mono text-[10px]", children: meta })) : null] }), _jsx("div", { className: "px-2.5 py-1.5", children: children })] }));
72
+ }
73
+ function ToolCallCard({ call }) {
74
+ return (_jsx(ToolCard, { icon: _jsx(Wrench, {}), name: call.name, meta: shortId(call.id), children: _jsx(JsonTree, { value: call.arguments, defaultExpandDepth: 2 }) }));
75
+ }
76
+ function MessageItem({ line, toolName }) {
77
+ switch (line.role) {
78
+ case 'user':
79
+ return (_jsxs(_Fragment, { children: [_jsx(RoleLabel, { icon: _jsx(User, {}), children: "User" }), _jsx("div", { className: "bg-secondary w-fit max-w-full rounded-lg px-3 py-2", children: _jsx(MessageText, { content: line.content }) })] }));
80
+ case 'assistant':
81
+ return (_jsxs(_Fragment, { children: [_jsx(RoleLabel, { icon: _jsx(Bot, {}), children: "Assistant" }), _jsxs("div", { className: "grid gap-2", children: [line.content ? _jsx(MessageText, { content: line.content }) : null, line.toolCalls?.map((call) => (_jsx(ToolCallCard, { call: call }, call.id)))] })] }));
82
+ case 'tool':
83
+ return (_jsx(ToolCard, { icon: _jsx(CornerDownRight, {}), name: `${line.name ?? toolName ?? 'tool'} result`, meta: line.toolCallId ? shortId(line.toolCallId) : undefined, children: _jsx(MessageText, { content: line.content }) }));
84
+ case 'system':
85
+ return (_jsxs(_Fragment, { children: [_jsx(RoleLabel, { icon: _jsx(Settings2, {}), children: "System" }), _jsx(MessageText, { content: line.content, className: "text-muted-foreground" })] }));
86
+ default: {
87
+ const exhaustive = line.role;
88
+ return exhaustive;
89
+ }
90
+ }
91
+ }
92
+ function StreamingItem({ text }) {
93
+ return (_jsxs(_Fragment, { children: [_jsx(RoleLabel, { icon: _jsx(Bot, {}), children: "Assistant" }), _jsxs("div", { className: "text-[13px] leading-relaxed break-words whitespace-pre-wrap", children: [text, _jsx("span", { "aria-label": "Generating", className: "bg-foreground/70 ml-0.5 inline-block h-3.5 w-1.5 animate-pulse rounded-[1px] align-[-2px]" })] })] }));
94
+ }
95
+ function Transcript({ runId }) {
96
+ const store = useRunStore(runId);
97
+ const convo = useProjection(store, conversation);
98
+ const streaming = useEventFold(store, streamingFold);
99
+ const stream = streaming.turnStarted ? streaming.parts.join('') : null;
100
+ const toolNames = useMemo(() => {
101
+ const names = new Map();
102
+ for (const line of convo.lines) {
103
+ for (const call of line.toolCalls ?? []) {
104
+ names.set(call.id, call.name);
105
+ }
106
+ }
107
+ return names;
108
+ }, [convo.lines]);
109
+ const items = useMemo(() => {
110
+ const lines = convo.lines.map((line, index) => ({
111
+ kind: 'line',
112
+ line,
113
+ index,
114
+ }));
115
+ return stream === null ? lines : [...lines, { kind: 'stream', text: stream }];
116
+ }, [convo.lines, stream]);
117
+ return (_jsx(FollowList, { items: items, estimateSize: 96, overscan: 4, paddingStart: 8, paddingEnd: 12, getKey: (item) => (item.kind === 'stream' ? 'stream' : item.index), empty: _jsx("p", { className: "text-muted-foreground p-6 text-center text-xs", children: "Waiting for messages\u2026" }), renderItem: (item) => (_jsx("div", { className: "px-4 py-2", children: item.kind === 'stream' ? (_jsx(StreamingItem, { text: item.text })) : (_jsx(MessageItem, { line: item.line, toolName: item.line.toolCallId ? toolNames.get(item.line.toolCallId) : undefined })) })) }));
118
+ }
119
+ function DeliverySelect({ value, onChange, }) {
120
+ return (_jsxs(Select, { value: value, onValueChange: (next) => {
121
+ const option = deliveryOptions.find((candidate) => candidate.value === next);
122
+ if (option) {
123
+ onChange(option.value);
124
+ }
125
+ }, children: [_jsx(SelectTrigger, { "aria-label": "Delivery", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { align: "start", className: "w-60", children: deliveryOptions.map((option) => (_jsx(SelectItem, { value: option.value, description: option.description, children: option.label }, option.value))) })] }));
126
+ }
127
+ export function AgentChat(context) {
128
+ const { definition, runId } = context;
129
+ const { client } = useDebugger();
130
+ const [draft, setDraft] = useState('');
131
+ const [delivery, setDelivery] = useState('followUp');
132
+ const { start, pending, error } = useStartRun(context);
133
+ const session = definition.kind === 'agent-session';
134
+ const form = inputForm(definition.inputSchema);
135
+ function send() {
136
+ const content = draft.trim();
137
+ const input = session ? null : messageInput(form, content);
138
+ if (!content || input === undefined) {
139
+ return;
140
+ }
141
+ setDraft('');
142
+ start(input, session
143
+ ? (started) => client.signal(started.runId, agentSessionMessageEvents(createEventId(), content, { threadId: started.threadId }))
144
+ : undefined);
145
+ }
146
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [runId ? (_jsx(Transcript, { runId: runId })) : (_jsx("div", { className: "min-h-0 flex-1", children: _jsx(EmptyState, { icon: _jsx(MessageSquare, {}), title: `Talk to ${definition.name}`, children: "Your first message starts a new run." }) })), runId ? (_jsx(FollowUp, { runId: runId, session: session, delivery: delivery, setDelivery: setDelivery, startError: error })) : (_jsx(Composer, { value: draft, onChange: setDraft, onSubmit: send, pending: pending, placeholder: `Message ${definition.name}…`, submitLabel: "Start", error: error }))] }));
147
+ }
148
+ function FollowUp({ runId, session, delivery, setDelivery, startError, }) {
149
+ const store = useRunStore(runId);
150
+ const { rootThreadId } = useRunSummary(store);
151
+ const [draft, setDraft] = useState('');
152
+ const [pending, setPending] = useState(false);
153
+ const [error, setError] = useState(null);
154
+ function send() {
155
+ const content = draft.trim();
156
+ if (!content || !rootThreadId) {
157
+ return;
158
+ }
159
+ setPending(true);
160
+ setError(null);
161
+ const events = session
162
+ ? agentSessionMessageEvents(createEventId(), content, {
163
+ delivery,
164
+ threadId: rootThreadId,
165
+ activeChildThreadId: delivery === 'steer' ? activeSessionChild(store.events()) : undefined,
166
+ })
167
+ : [userMessage(content, { threadId: rootThreadId })];
168
+ const [only] = events;
169
+ const commit = events.length === 1 && only ? store.commit(only) : store.commit(events);
170
+ commit
171
+ .then(() => setDraft(''))
172
+ .catch((cause) => setError(errorMessage(cause)))
173
+ .finally(() => setPending(false));
174
+ }
175
+ return (_jsx(Composer, { value: draft, onChange: setDraft, onSubmit: send, pending: pending, disabled: !rootThreadId, placeholder: "Send a follow-up\u2026", error: error ?? startError, toolbar: session ? _jsx(DeliverySelect, { value: delivery, onChange: setDelivery }) : null }));
176
+ }
@@ -0,0 +1,5 @@
1
+ import { type DebuggerPlugin } from '@swirls/looms/debugger';
2
+ import { AgentChat } from './agent-chat';
3
+ import { TokenUsageView } from './token-usage';
4
+ export declare const agentDebugger: DebuggerPlugin;
5
+ export { AgentChat, TokenUsageView };
@@ -0,0 +1,19 @@
1
+ import { inputForm, messageInput } from '@swirls/looms/debugger';
2
+ import { tokenUsage } from '../projections.js';
3
+ import { AgentChat } from './agent-chat.js';
4
+ import { summarizeAgentEvent } from './summarize.js';
5
+ import { TokenUsageView } from './token-usage.js';
6
+ function isChat(definition) {
7
+ return messageInput(inputForm(definition.inputSchema), '') !== undefined;
8
+ }
9
+ export const agentDebugger = {
10
+ name: 'agent',
11
+ families: [{ family: 'agent', color: 'oklch(0.74 0.12 240)', summarize: summarizeAgentEvent }],
12
+ workspace: {
13
+ kinds: ['agent', 'agent-session'],
14
+ matches: (definition) => definition.kind === 'agent-session' || isChat(definition),
15
+ component: AgentChat,
16
+ },
17
+ projections: [{ name: tokenUsage.name, component: TokenUsageView }],
18
+ };
19
+ export { AgentChat, TokenUsageView };
@@ -0,0 +1,2 @@
1
+ import { type DebuggerEvent, type EventSummary } from '@swirls/looms/debugger';
2
+ export declare function summarizeAgentEvent(event: DebuggerEvent): EventSummary | undefined;
@@ -0,0 +1,51 @@
1
+ import { isJsonObject, isJsonString } from '@swirls/looms/core';
2
+ import { compactJson, jsonFields, jsonNumberText, jsonText, } from '@swirls/looms/debugger';
3
+ function messageContent(payload) {
4
+ const message = jsonFields(payload).message;
5
+ return isJsonObject(message) && isJsonString(message.content) ? message.content : undefined;
6
+ }
7
+ function toolCallSummary(payload) {
8
+ const call = jsonFields(payload).toolCall;
9
+ if (!isJsonObject(call)) {
10
+ return { name: 'tool', args: null };
11
+ }
12
+ return {
13
+ name: isJsonString(call.name) ? call.name : 'tool',
14
+ args: call.arguments ?? null,
15
+ };
16
+ }
17
+ export function summarizeAgentEvent(event) {
18
+ const payload = jsonFields(event.payload);
19
+ switch (event.type) {
20
+ case 'agent.message.received':
21
+ return { title: 'user message', detail: messageContent(event.payload) };
22
+ case 'agent.turn.started':
23
+ return { title: 'turn started', detail: `turn ${jsonNumberText(payload.turn) ?? ''}` };
24
+ case 'agent.turn.text_delta':
25
+ return { title: 'text delta', detail: jsonText(payload.delta) };
26
+ case 'agent.message':
27
+ return { title: 'agent message', detail: messageContent(event.payload) };
28
+ case 'agent.tool_call.requested': {
29
+ const call = toolCallSummary(event.payload);
30
+ return { title: `tool ${call.name}`, detail: compactJson(call.args) };
31
+ }
32
+ case 'agent.tool.result':
33
+ return {
34
+ title: jsonText(payload.error)
35
+ ? `tool error ${jsonText(payload.name)}`
36
+ : `tool result ${jsonText(payload.name)}`,
37
+ detail: jsonText(payload.error) ?? compactJson(payload.result ?? null),
38
+ };
39
+ case 'agent.steered':
40
+ return { title: 'steered', detail: messageContent(event.payload) };
41
+ case 'agent.spawn.requested':
42
+ return {
43
+ title: 'agent spawn',
44
+ detail: `${jsonText(payload.kind) ?? 'agent'}:${jsonText(payload.definitionName) ?? 'unknown'}`,
45
+ };
46
+ case 'agent.effects.requested':
47
+ return { title: 'agent effects', detail: jsonText(payload.toolCallId) };
48
+ default:
49
+ return undefined;
50
+ }
51
+ }
@@ -0,0 +1,3 @@
1
+ export declare function TokenUsageView({ runId }: {
2
+ runId: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useProjection, useRunStore } from '@swirls/looms/react';
3
+ import { tokenUsage } from '../projections.js';
4
+ const format = new Intl.NumberFormat();
5
+ function Stat({ label, value }) {
6
+ return (_jsxs("div", { className: "border-border bg-card/60 rounded-lg border px-2.5 py-2", children: [_jsx("p", { className: "text-muted-foreground text-[11px]", children: label }), _jsx("p", { className: "font-mono text-[13px] font-medium tabular-nums", children: format.format(value) })] }));
7
+ }
8
+ export function TokenUsageView({ runId }) {
9
+ const state = useProjection(useRunStore(runId), tokenUsage);
10
+ return (_jsxs("div", { className: "grid grid-cols-3 gap-2", children: [_jsx(Stat, { label: "Input", value: state.input }), _jsx(Stat, { label: "Output", value: state.output }), _jsx(Stat, { label: "Total", value: state.input + state.output })] }));
11
+ }
@@ -13,7 +13,6 @@ export interface FunctionTool<TName extends string = string, TInput = JsonValue,
13
13
  readonly name: TName;
14
14
  readonly description: string;
15
15
  readonly input?: StandardSchemaV1<any, TInput> | Schema.ConstraintDecoder<TInput>;
16
- readonly inputSchema?: JsonValue;
17
16
  handler(input: TInput, ctx: ToolContext): Promise<TOutput> | TOutput;
18
17
  }
19
18
  export interface ThreadTool {
@@ -21,7 +20,6 @@ export interface ThreadTool {
21
20
  readonly name: string;
22
21
  readonly description: string;
23
22
  readonly input?: SchemaInput;
24
- readonly inputSchema?: JsonValue;
25
23
  readonly childKind: string;
26
24
  readonly childName: string;
27
25
  readonly childVersion: string;
@@ -37,7 +35,6 @@ export interface EffectsTool {
37
35
  readonly name: string;
38
36
  readonly description: string;
39
37
  readonly input?: SchemaInput;
40
- readonly inputSchema?: JsonValue;
41
38
  effects(input: JsonValue): RuntimeEffect[];
42
39
  waitOn?: {
43
40
  type: string | readonly string[];
@@ -76,6 +73,7 @@ export interface AgentDefinition<TName extends string = string, TInput = JsonVal
76
73
  readonly kind: 'agent';
77
74
  readonly name: TName;
78
75
  readonly version: string;
76
+ readonly description?: string;
79
77
  readonly model?: string;
80
78
  readonly instructions: string;
81
79
  readonly input?: StandardSchemaV1<any, TInput> | Schema.ConstraintDecoder<TInput>;
@@ -90,6 +88,7 @@ export interface AgentSessionDefinition<TName extends string = string> {
90
88
  readonly kind: 'agent-session';
91
89
  readonly name: TName;
92
90
  readonly version: string;
91
+ readonly description?: string;
93
92
  /** The agent definition is pinned when the session definition is created. */
94
93
  readonly agent: Pick<AgentDefinition, 'kind' | 'name' | 'version'>;
95
94
  /** Max idle milliseconds before parking. Zero waits indefinitely. */
@@ -97,6 +96,7 @@ export interface AgentSessionDefinition<TName extends string = string> {
97
96
  }
98
97
  export declare function defineAgentSession<TName extends string>(def: {
99
98
  readonly name: TName;
99
+ readonly description?: string;
100
100
  readonly agent: Pick<AgentDefinition, 'kind' | 'name' | 'version'>;
101
101
  readonly version?: string;
102
102
  readonly idleTimeoutMs?: number;
@@ -111,7 +111,6 @@ export declare function asThreadTool(def: {
111
111
  name?: string;
112
112
  description?: string;
113
113
  input?: SchemaInput;
114
- inputSchema?: JsonValue;
115
114
  child: {
116
115
  kind: string;
117
116
  name: string;
@@ -123,14 +122,12 @@ export declare function asAgentTool(def: {
123
122
  name?: string;
124
123
  description?: string;
125
124
  input?: SchemaInput;
126
- inputSchema?: JsonValue;
127
125
  agent: {
128
126
  kind: 'agent';
129
127
  name: string;
130
128
  version?: string;
131
129
  instructions?: string;
132
130
  input?: SchemaInput;
133
- inputSchema?: JsonValue;
134
131
  };
135
132
  mapInput?: (input: JsonValue) => JsonValue;
136
133
  }): ThreadTool;
@@ -139,13 +136,11 @@ type EvaluatorToolTarget = {
139
136
  name: string;
140
137
  description?: string;
141
138
  input?: SchemaInput;
142
- inputSchema?: JsonValue;
143
139
  };
144
140
  export declare function asEvaluatorTool(def: {
145
141
  name?: string;
146
142
  description?: string;
147
143
  input?: SchemaInput;
148
- inputSchema?: JsonValue;
149
144
  evaluator: EvaluatorToolTarget;
150
145
  mapInput?: (input: JsonValue) => JsonValue;
151
146
  }): ThreadTool;
@@ -153,14 +148,12 @@ export declare function asWorkflowTool(def: {
153
148
  name?: string;
154
149
  description?: string;
155
150
  input?: SchemaInput;
156
- inputSchema?: JsonValue;
157
151
  workflow: {
158
152
  kind: 'workflow';
159
153
  name: string;
160
154
  version?: string;
161
155
  description?: string;
162
156
  input?: SchemaInput;
163
- inputSchema?: JsonValue;
164
157
  };
165
158
  mapInput?: (input: JsonValue) => JsonValue;
166
159
  }): ThreadTool;
@@ -168,7 +161,6 @@ export declare function asEffectsTool(def: {
168
161
  name: string;
169
162
  description: string;
170
163
  input?: SchemaInput;
171
- inputSchema?: JsonValue;
172
164
  effects: (input: JsonValue) => RuntimeEffect[];
173
165
  waitOn?: {
174
166
  type: string | readonly string[];
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_DEFINITION_VERSION, } from '@swirls/looms/core';
2
2
  export function defineAgentSession(def) {
3
- return {
3
+ const definition = {
4
4
  kind: 'agent-session',
5
5
  name: def.name,
6
6
  version: def.version ?? DEFAULT_DEFINITION_VERSION,
@@ -11,6 +11,10 @@ export function defineAgentSession(def) {
11
11
  },
12
12
  idleTimeoutMs: Math.max(0, def.idleTimeoutMs ?? 0),
13
13
  };
14
+ if (def.description === undefined) {
15
+ return definition;
16
+ }
17
+ return { ...definition, description: def.description };
14
18
  }
15
19
  export function normalizeTools(tools = []) {
16
20
  return tools.map((entry) => {
@@ -51,7 +55,6 @@ export function asThreadTool(def) {
51
55
  name: def.name ?? def.child.name,
52
56
  description: def.description ?? def.child.name,
53
57
  input: def.input,
54
- inputSchema: def.inputSchema,
55
58
  childKind: def.child.kind,
56
59
  childName: def.child.name,
57
60
  childVersion: def.child.version ?? DEFAULT_DEFINITION_VERSION,
@@ -64,7 +67,6 @@ export function asAgentTool(def) {
64
67
  name: def.name,
65
68
  description: def.description ?? def.agent.instructions,
66
69
  input: def.input ?? def.agent.input,
67
- inputSchema: def.inputSchema ?? def.agent.inputSchema,
68
70
  child: { kind: 'agent', name: def.agent.name, version: def.agent.version },
69
71
  mapInput: def.mapInput,
70
72
  });
@@ -74,7 +76,6 @@ export function asEvaluatorTool(def) {
74
76
  name: def.name,
75
77
  description: def.description ?? def.evaluator.description,
76
78
  input: def.input ?? def.evaluator.input,
77
- inputSchema: def.inputSchema ?? def.evaluator.inputSchema,
78
79
  child: { kind: 'evaluator', name: def.evaluator.name },
79
80
  mapInput: def.mapInput,
80
81
  });
@@ -84,7 +85,6 @@ export function asWorkflowTool(def) {
84
85
  name: def.name,
85
86
  description: def.description ?? def.workflow.description,
86
87
  input: def.input ?? def.workflow.input,
87
- inputSchema: def.inputSchema ?? def.workflow.inputSchema,
88
88
  child: { kind: 'workflow', name: def.workflow.name, version: def.workflow.version },
89
89
  mapInput: def.mapInput,
90
90
  });
@@ -95,7 +95,6 @@ export function asEffectsTool(def) {
95
95
  name: def.name,
96
96
  description: def.description,
97
97
  input: def.input,
98
- inputSchema: def.inputSchema,
99
98
  effects: def.effects,
100
99
  waitOn: def.waitOn,
101
100
  };
@@ -35,6 +35,8 @@ interface AgentSessionSignalClient<Result> {
35
35
  signal(runId: string, events: ReadonlyArray<EventInput>): Promise<Result>;
36
36
  cancel?(runId: string, threadId?: string): Promise<Result>;
37
37
  }
38
+ /** Events for one session message. A steer aimed at `activeChildThreadId` interrupts that turn in the same batch. */
39
+ export declare function agentSessionMessageEvents(messageId: string, text: string, options?: AgentSessionMessageOptions): EventInput[];
38
40
  export declare function sendAgentSessionMessage<Result>(client: AgentSessionSignalClient<Result>, runId: string, messageId: string, text: string, options?: Omit<AgentSessionMessageOptions, 'activeChildThreadId'>): Promise<Result>;
39
41
  export declare function cancelActiveAgentTurn<Result>(client: AgentSessionSignalClient<Result>, runId: string, options?: AgentSignalOptions): Promise<Result | Awaited<ReturnType<AgentSessionSignalClient<Result>['getRun']>>>;
40
42
  export {};
@@ -54,26 +54,28 @@ function activeTurnFrom(value) {
54
54
  }
55
55
  : null;
56
56
  }
57
+ /** Events for one session message. A steer aimed at `activeChildThreadId` interrupts that turn in the same batch. */
58
+ export function agentSessionMessageEvents(messageId, text, options = {}) {
59
+ const events = [submitAgentMessage(messageId, text, options)];
60
+ if (options.delivery === 'steer' && options.activeChildThreadId) {
61
+ events.push(agentModule.input('steered', {
62
+ turn: 0,
63
+ messageId,
64
+ message: { role: 'user', content: text },
65
+ interrupt: true,
66
+ }, { threadId: options.activeChildThreadId }));
67
+ }
68
+ return events;
69
+ }
57
70
  export function sendAgentSessionMessage(client, runId, messageId, text, options = {}) {
58
71
  return client.getRun(runId).then((run) => {
59
72
  const threadId = options.threadId ?? run.rootThreadId ?? undefined;
60
73
  const active = options.delivery === 'steer' && threadId ? activeTurnFrom(run.threads[threadId]?.state) : null;
61
- const events = [
62
- submitAgentMessage(messageId, text, {
63
- ...options,
64
- threadId,
65
- activeChildThreadId: active?.childThreadId,
66
- }),
67
- ];
68
- if (active) {
69
- events.push(agentModule.input('steered', {
70
- turn: 0,
71
- messageId,
72
- message: { role: 'user', content: text },
73
- interrupt: true,
74
- }, { threadId: active.childThreadId }));
75
- }
76
- return client.signal(runId, events);
74
+ return client.signal(runId, agentSessionMessageEvents(messageId, text, {
75
+ ...options,
76
+ threadId,
77
+ activeChildThreadId: active?.childThreadId,
78
+ }));
77
79
  });
78
80
  }
79
81
  export function cancelActiveAgentTurn(client, runId, options = {}) {
@@ -1,21 +1,5 @@
1
- import { Schema } from 'effect';
2
- import type { JsonValue, SchemaInput } from '@swirls/looms/core';
1
+ import { type JsonValue } from '@swirls/looms/core';
3
2
  import { type AgentToolEntry, type ToolLike } from './definitions';
4
3
  import type { LlmToolSpec } from './llm';
5
- interface StandardJsonSchemaConverter {
6
- readonly input: (options: {
7
- target: string;
8
- }) => JsonValue;
9
- }
10
- interface StandardJsonSchemaHolder {
11
- readonly '~standard': {
12
- readonly jsonSchema?: {
13
- readonly input?: StandardJsonSchemaConverter['input'];
14
- };
15
- };
16
- }
17
- type SchemaCandidate = SchemaInput | StandardJsonSchemaHolder | Schema.Codec<JsonValue>;
18
4
  export declare function toolJsonSchema(tool: ToolLike): JsonValue;
19
5
  export declare function toolSpecs(tools?: ReadonlyArray<AgentToolEntry>): LlmToolSpec[];
20
- export declare function jsonSchemaFromStandard(schema: SchemaCandidate | null | undefined): JsonValue;
21
- export {};
@@ -1,43 +1,7 @@
1
- import { Schema } from 'effect';
1
+ import { jsonSchemaOfInput } from '@swirls/looms/core';
2
2
  import { normalizeTools } from './definitions.js';
3
3
  const EMPTY_OBJECT_SCHEMA = { type: 'object' };
4
- function isStandardJsonSchemaHolder(value) {
5
- return '~standard' in value;
6
- }
7
- function fromStandardJsonSchema(schema) {
8
- if (!isStandardJsonSchemaHolder(schema)) {
9
- return undefined;
10
- }
11
- const input = schema['~standard'].jsonSchema?.input;
12
- if (!input) {
13
- return undefined;
14
- }
15
- try {
16
- return input({ target: 'draft-2020-12' });
17
- }
18
- catch {
19
- return undefined;
20
- }
21
- }
22
- function fromEffectSchema(schema) {
23
- if (!Schema.isSchema(schema)) {
24
- return undefined;
25
- }
26
- try {
27
- const standard = Schema.toStandardJSONSchemaV1(schema);
28
- return fromStandardJsonSchema(standard);
29
- }
30
- catch {
31
- return undefined;
32
- }
33
- }
34
- function fromInputCandidate(input) {
35
- if (!input) {
36
- return undefined;
37
- }
38
- return fromStandardJsonSchema(input) ?? fromEffectSchema(input);
39
- }
40
- function toolInputCandidate(tool) {
4
+ function toolInput(tool) {
41
5
  switch (tool.kind) {
42
6
  case 'function':
43
7
  return tool.input;
@@ -52,7 +16,7 @@ function toolInputCandidate(tool) {
52
16
  }
53
17
  }
54
18
  export function toolJsonSchema(tool) {
55
- return tool.inputSchema ?? fromInputCandidate(toolInputCandidate(tool)) ?? EMPTY_OBJECT_SCHEMA;
19
+ return jsonSchemaOfInput(toolInput(tool)) ?? EMPTY_OBJECT_SCHEMA;
56
20
  }
57
21
  export function toolSpecs(tools = []) {
58
22
  return normalizeTools(tools).map((tool) => ({
@@ -61,6 +25,3 @@ export function toolSpecs(tools = []) {
61
25
  inputJsonSchema: toolJsonSchema(tool),
62
26
  }));
63
27
  }
64
- export function jsonSchemaFromStandard(schema) {
65
- return fromInputCandidate(schema ?? undefined) ?? EMPTY_OBJECT_SCHEMA;
66
- }
@@ -1,3 +1,3 @@
1
1
  export { vercelLlm, type VercelLlmOptions } from './vercel-llm';
2
- export { toEvaluatorAnswers, vercelEvaluator, type VercelEvaluatorOptions, } from './vercel-evaluator';
2
+ export { toEvaluatorAnswers, vercelEvaluator, type EvaluationModelProvider, type VercelEvaluatorOptions, } from './vercel-evaluator';
3
3
  export { toLoomsToolCalls, toModelMessages } from './messages';