@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
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DateTime } from 'effect';
3
+ import { Search } from 'lucide-react';
4
+ import { useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { useRunStore, useRunSummary, useThreadTree } from '@swirls/looms/react';
6
+ import { StatusDot } from '../components/status-dot.js';
7
+ import { useDebugger } from '../context.js';
8
+ import { errorMessage, shortId } from '../lib/cn.js';
9
+ import { relativeTime, runCreatedAt } from '../lib/time.js';
10
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from '../ui/command.js';
11
+ import { Kbd } from '../ui/panel.js';
12
+ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover.js';
13
+ /** The list stays short enough to render eagerly; typing narrows the rest. */
14
+ const RESULT_LIMIT = 100;
15
+ function useRunSummaries(refreshKey) {
16
+ const { client } = useDebugger();
17
+ const [runs, setRuns] = useState([]);
18
+ const [error, setError] = useState(null);
19
+ useEffect(() => {
20
+ let active = true;
21
+ client.listRunSummaries().then((listed) => {
22
+ if (active) {
23
+ setRuns(listed.runs);
24
+ setError(null);
25
+ }
26
+ }, (cause) => {
27
+ if (active) {
28
+ setError(errorMessage(cause));
29
+ }
30
+ });
31
+ return () => {
32
+ active = false;
33
+ };
34
+ }, [client, refreshKey]);
35
+ return { runs, error };
36
+ }
37
+ function indexRuns(runs) {
38
+ const indexed = runs.map((run) => ({
39
+ run,
40
+ createdAt: runCreatedAt(run.runId) ?? 0,
41
+ haystack: [run.definitionName, run.kind, run.status, run.runId]
42
+ .filter(Boolean)
43
+ .join(' ')
44
+ .toLowerCase(),
45
+ }));
46
+ // ES2022 has no Array#toSorted. `indexed` is a fresh array.
47
+ // oxlint-disable-next-line unicorn/no-array-sort
48
+ return indexed.sort((left, right) => right.createdAt - left.createdAt);
49
+ }
50
+ function matches(entry, terms) {
51
+ return terms.every((term) => entry.haystack.includes(term));
52
+ }
53
+ function CurrentRun({ runId, fallbackLabel }) {
54
+ const store = useRunStore(runId);
55
+ const { status } = useRunSummary(store);
56
+ const { root } = useThreadTree(store);
57
+ return (_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2", children: [_jsx(StatusDot, { status: status }), _jsx("span", { className: "truncate", children: root?.definitionName ?? fallbackLabel ?? 'Run' }), _jsx("span", { className: "text-muted-foreground font-mono text-[11px]", children: shortId(runId) })] }));
58
+ }
59
+ function useCommandK(onToggle) {
60
+ useEffect(() => {
61
+ function onKeyDown(event) {
62
+ if (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
63
+ event.preventDefault();
64
+ onToggle();
65
+ }
66
+ }
67
+ window.addEventListener('keydown', onKeyDown);
68
+ return () => window.removeEventListener('keydown', onKeyDown);
69
+ }, [onToggle]);
70
+ }
71
+ export function RunSearch({ runId, fallbackLabel, onSelectRun, }) {
72
+ const [open, setOpen] = useState(false);
73
+ const [viaKeyboard, setViaKeyboard] = useState(false);
74
+ const [query, setQuery] = useState('');
75
+ const { runs, error } = useRunSummaries(`${runId ?? ''}:${open}`);
76
+ const indexed = useMemo(() => indexRuns(runs), [runs]);
77
+ const current = runs.find((run) => run.runId === runId);
78
+ const now = open ? DateTime.toEpochMillis(DateTime.nowUnsafe()) : 0;
79
+ const results = useMemo(() => {
80
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
81
+ const found = terms.length === 0 ? indexed : indexed.filter((entry) => matches(entry, terms));
82
+ return { shown: found.slice(0, RESULT_LIMIT), total: found.length };
83
+ }, [indexed, query]);
84
+ const toggleFromKeyboard = useCallback(() => {
85
+ setViaKeyboard(true);
86
+ setOpen((value) => !value);
87
+ }, []);
88
+ useCommandK(toggleFromKeyboard);
89
+ function change(next) {
90
+ setOpen(next);
91
+ if (!next) {
92
+ setQuery('');
93
+ setViaKeyboard(false);
94
+ }
95
+ }
96
+ return (_jsxs(Popover, { open: open, onOpenChange: change, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": "Search runs", className: "border-input bg-background/40 hover:bg-accent/60 hover:border-ring/40 focus-visible:ring-ring/50 data-[state=open]:border-ring/60 flex h-7 w-80 min-w-0 items-center gap-2 rounded-md border pr-1 pl-2.5 text-left text-xs transition-colors outline-none focus-visible:ring-[3px]", children: [_jsx(Search, { className: "text-muted-foreground size-3.5 shrink-0", "aria-hidden": true }), runId ? (_jsx(CurrentRun, { runId: runId, fallbackLabel: current?.definitionName ?? fallbackLabel })) : (_jsx("span", { className: "text-muted-foreground flex-1 truncate", children: "Search runs\u2026" })), _jsx(Kbd, { children: "\u2318K" })] }) }), _jsx(PopoverContent, { align: "start", className: "w-[26rem] p-0", "data-instant": viaKeyboard ? '' : undefined, children: _jsxs(Command, { shouldFilter: false, loop: true, children: [_jsx(CommandInput, { value: query, onValueChange: setQuery, placeholder: "Search by name, kind, status or id\u2026" }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: error ?? 'No runs match.' }), results.shown.length > 0 ? (_jsx(CommandGroup, { heading: query ? 'Matches' : 'Recent runs', children: results.shown.map(({ run, createdAt }) => (_jsxs(CommandItem, { value: run.runId, onSelect: () => {
97
+ onSelectRun(run);
98
+ change(false);
99
+ }, className: "h-8", children: [_jsx(StatusDot, { status: run.status }), _jsx("span", { className: "min-w-0 truncate font-medium", children: run.definitionName ?? shortId(run.runId) }), run.kind ? (_jsx("span", { className: "text-muted-foreground font-mono text-[10px]", children: run.kind })) : null, _jsx("span", { className: "text-muted-foreground ml-auto shrink-0 font-mono text-[10px]", children: shortId(run.runId) }), _jsx("span", { className: "text-muted-foreground/80 w-14 shrink-0 text-right text-[10px] tabular-nums", children: createdAt ? relativeTime(createdAt, now) : '' })] }, run.runId))) })) : null] }), _jsxs("div", { className: "border-border text-muted-foreground flex h-8 items-center gap-3 border-t px-3 text-[10px]", children: [_jsx("span", { className: "tabular-nums", children: results.total > results.shown.length
100
+ ? `${results.shown.length} of ${results.total} runs · keep typing to narrow`
101
+ : `${results.total} ${results.total === 1 ? 'run' : 'runs'}` }), _jsxs("span", { className: "ml-auto flex items-center gap-1", children: [_jsx(Kbd, { children: "\u2191" }), _jsx(Kbd, { children: "\u2193" }), "navigate"] }), _jsxs("span", { className: "flex items-center gap-1", children: [_jsx(Kbd, { children: "\u21B5" }), "open"] })] })] }) })] }));
102
+ }
@@ -0,0 +1,6 @@
1
+ import type { PublishedDefinition } from '@swirls/looms/core';
2
+ export declare function RunTypeMenu({ definitions, selected, onSelect, }: {
3
+ definitions: readonly PublishedDefinition[];
4
+ selected?: PublishedDefinition;
5
+ onSelect: (definition: PublishedDefinition) => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronsUpDown, Plus } from 'lucide-react';
3
+ import { Button } from '../ui/button.js';
4
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../ui/dropdown-menu.js';
5
+ import { definitionKey } from './definition-key.js';
6
+ export function RunTypeMenu({ definitions, selected, onSelect, }) {
7
+ const kinds = [...new Set(definitions.map((definition) => definition.kind))];
8
+ return (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "data-[state=open]:bg-accent data-[state=open]:text-foreground text-foreground max-w-64 gap-2 px-2", children: [selected ? (_jsxs("span", { className: "flex min-w-0 items-baseline gap-1.5", children: [_jsx("span", { className: "text-muted-foreground font-mono text-[11px] font-normal", children: selected.kind }), _jsx("span", { className: "text-muted-foreground/60 font-normal", children: "/" }), _jsx("span", { className: "truncate", children: selected.name })] })) : (_jsx("span", { className: "text-muted-foreground", children: "Select run type" })), _jsx(ChevronsUpDown, { className: "text-muted-foreground" })] }) }), _jsxs(DropdownMenuContent, { align: "start", className: "w-72", children: [_jsxs("div", { className: "text-muted-foreground flex items-center gap-1.5 px-2 pt-1.5 pb-1 text-[11px]", children: [_jsx(Plus, { className: "size-3" }), "Start a new run"] }), _jsx(DropdownMenuRadioGroup, { value: selected ? definitionKey(selected) : '', children: kinds.map((kind, index) => (_jsxs(DropdownMenuGroup, { children: [index > 0 ? _jsx(DropdownMenuSeparator, {}) : null, _jsx(DropdownMenuLabel, { className: "font-mono", children: kind }), definitions
9
+ .filter((definition) => definition.kind === kind)
10
+ .map((definition) => (_jsxs(DropdownMenuRadioItem, { value: definitionKey(definition), onSelect: () => onSelect(definition), className: "flex-col items-start gap-0", children: [_jsx("span", { className: "font-medium", children: definition.name }), definition.description ? (_jsx("span", { className: "text-muted-foreground line-clamp-1 text-[11px]", children: definition.description })) : null] }, definitionKey(definition))))] }, kind))) })] })] }));
11
+ }
@@ -0,0 +1,31 @@
1
+ import { type JsonValue } from '@swirls/looms/core';
2
+ export interface FormField {
3
+ readonly name: string;
4
+ readonly label: string;
5
+ readonly type: 'string' | 'number' | 'boolean';
6
+ readonly defaultValue: string;
7
+ }
8
+ /** How a definition's input is edited: one text box, flat scalar fields, or raw JSON. */
9
+ export type InputForm = {
10
+ readonly kind: 'text';
11
+ } | {
12
+ readonly kind: 'fields';
13
+ readonly fields: readonly FormField[];
14
+ } | {
15
+ readonly kind: 'json';
16
+ };
17
+ export interface FormValues {
18
+ /** Field values by name, for the `fields` form. */
19
+ readonly fields: Readonly<Record<string, string>>;
20
+ /** Text for the `text` form, or JSON source for the `json` form. */
21
+ readonly source: string;
22
+ }
23
+ /** Flat object fields, or undefined when the schema needs the raw JSON editor. */
24
+ export declare function formFields(schema: JsonValue | undefined): readonly FormField[] | undefined;
25
+ export declare function inputForm(schema: JsonValue | undefined): InputForm;
26
+ export declare function initialValues(form: InputForm): FormValues;
27
+ export declare function valuesToInput(fields: readonly FormField[], values: Readonly<Record<string, string>>): JsonValue;
28
+ /** Input for the form's current values. Throws when the JSON source does not parse. */
29
+ export declare function formInput(form: InputForm, values: FormValues): JsonValue;
30
+ /** Input carrying a single chat message, when the form has exactly one string to put it in. */
31
+ export declare function messageInput(form: InputForm, message: string): JsonValue | undefined;
@@ -0,0 +1,117 @@
1
+ import { Predicate } from 'effect';
2
+ import { cleanUndefined, isJsonNumber, isJsonObject, isJsonString, } from '@swirls/looms/core';
3
+ function fieldType(value) {
4
+ if (value === 'string') {
5
+ return 'string';
6
+ }
7
+ if (value === 'number' || value === 'integer') {
8
+ return 'number';
9
+ }
10
+ if (value === 'boolean') {
11
+ return 'boolean';
12
+ }
13
+ return undefined;
14
+ }
15
+ /** Flat object fields, or undefined when the schema needs the raw JSON editor. */
16
+ export function formFields(schema) {
17
+ if (!isJsonObject(schema) || schema.type !== 'object' || !isJsonObject(schema.properties)) {
18
+ return undefined;
19
+ }
20
+ const fields = [];
21
+ for (const [name, property] of Object.entries(schema.properties)) {
22
+ if (!isJsonObject(property) || !isJsonString(property.type)) {
23
+ return undefined;
24
+ }
25
+ const type = fieldType(property.type);
26
+ if (!type) {
27
+ return undefined;
28
+ }
29
+ const fallback = property.default;
30
+ const defaultValue = isJsonString(fallback) || isJsonNumber(fallback) || Predicate.isBoolean(fallback)
31
+ ? String(fallback)
32
+ : '';
33
+ fields.push({
34
+ name,
35
+ label: isJsonString(property.title) ? property.title : name,
36
+ type,
37
+ defaultValue,
38
+ });
39
+ }
40
+ return fields;
41
+ }
42
+ export function inputForm(schema) {
43
+ if (isJsonObject(schema) && schema.type === 'string') {
44
+ return { kind: 'text' };
45
+ }
46
+ const fields = formFields(schema);
47
+ return fields ? { kind: 'fields', fields } : { kind: 'json' };
48
+ }
49
+ export function initialValues(form) {
50
+ switch (form.kind) {
51
+ case 'text':
52
+ return { fields: {}, source: '' };
53
+ case 'fields':
54
+ return {
55
+ fields: Object.fromEntries(form.fields.map((field) => [field.name, field.defaultValue])),
56
+ source: '',
57
+ };
58
+ case 'json':
59
+ return { fields: {}, source: '{}' };
60
+ default: {
61
+ const exhaustive = form;
62
+ return exhaustive;
63
+ }
64
+ }
65
+ }
66
+ export function valuesToInput(fields, values) {
67
+ const input = {};
68
+ for (const field of fields) {
69
+ const raw = values[field.name] ?? field.defaultValue;
70
+ if (field.type === 'number') {
71
+ const parsed = Number(raw);
72
+ input[field.name] = Number.isFinite(parsed) ? parsed : 0;
73
+ continue;
74
+ }
75
+ if (field.type === 'boolean') {
76
+ input[field.name] = raw === 'true';
77
+ continue;
78
+ }
79
+ input[field.name] = raw;
80
+ }
81
+ return input;
82
+ }
83
+ /** Input for the form's current values. Throws when the JSON source does not parse. */
84
+ export function formInput(form, values) {
85
+ switch (form.kind) {
86
+ case 'text':
87
+ return values.source;
88
+ case 'fields':
89
+ return valuesToInput(form.fields, values.fields);
90
+ case 'json':
91
+ return cleanUndefined(JSON.parse(values.source));
92
+ default: {
93
+ const exhaustive = form;
94
+ return exhaustive;
95
+ }
96
+ }
97
+ }
98
+ /** Input carrying a single chat message, when the form has exactly one string to put it in. */
99
+ export function messageInput(form, message) {
100
+ switch (form.kind) {
101
+ case 'text':
102
+ return message;
103
+ case 'fields': {
104
+ const strings = form.fields.filter((field) => field.type === 'string');
105
+ const [only] = strings;
106
+ return strings.length === 1 && only
107
+ ? valuesToInput(form.fields, { [only.name]: message })
108
+ : undefined;
109
+ }
110
+ case 'json':
111
+ return undefined;
112
+ default: {
113
+ const exhaustive = form;
114
+ return exhaustive;
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,3 @@
1
+ import type { WorkspaceContext } from '../plugin';
2
+ /** Starts a run from a form built from the definition's input schema, then shows its result. */
3
+ export declare function StartForm(context: WorkspaceContext): import("react").JSX.Element;
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Play } from 'lucide-react';
3
+ import { useState } from 'react';
4
+ import { isJsonObject, isJsonString } from '@swirls/looms/core';
5
+ import { useRunEvents, useRunStore, useRunSummary, useThreadTree } from '@swirls/looms/react';
6
+ import { JsonTree } from '../components/json-tree.js';
7
+ import { StatusBadge } from '../components/status-dot.js';
8
+ import { Button } from '../ui/button.js';
9
+ import { Checkbox } from '../ui/checkbox.js';
10
+ import { Input } from '../ui/input.js';
11
+ import { ScrollArea } from '../ui/scroll-area.js';
12
+ import { Textarea } from '../ui/textarea.js';
13
+ import { formInput, initialValues, inputForm, } from './schema-form.js';
14
+ import { useStartRun } from './use-start-run.js';
15
+ function RunResult({ runId }) {
16
+ const store = useRunStore(runId);
17
+ const summary = useRunSummary(store);
18
+ const tree = useThreadTree(store);
19
+ const events = useRunEvents(store);
20
+ const rootThreadId = tree.root?.threadId;
21
+ const completed = events.find((event) => event.type === 'runtime.thread.completed' && event.threadId === rootThreadId);
22
+ const failed = events.find((event) => event.type === 'runtime.thread.failed' && event.threadId === rootThreadId);
23
+ const output = completed && isJsonObject(completed.payload) ? completed.payload.output : undefined;
24
+ const failure = failed && isJsonObject(failed.payload) ? failed.payload.error : undefined;
25
+ return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: "Status" }), _jsx(StatusBadge, { status: summary.status })] }), output !== undefined ? (_jsxs("section", { className: "border-border bg-card rounded-lg border p-3", children: [_jsx("h3", { className: "text-muted-foreground mb-1.5 text-[11px] font-medium", children: "Output" }), _jsx(JsonTree, { value: output, defaultExpandDepth: 2 })] })) : null, isJsonString(failure) ? (_jsx("p", { className: "text-status-failed bg-status-failed/10 rounded-md px-3 py-2 text-xs", children: failure })) : null] }));
26
+ }
27
+ function FieldInput({ field, value, onChange, }) {
28
+ switch (field.type) {
29
+ case 'boolean':
30
+ return (_jsx(Checkbox, { checked: value === 'true', onCheckedChange: (checked) => onChange(String(checked === true)) }));
31
+ case 'number':
32
+ return (_jsx(Input, { type: "number", className: "h-8 font-mono text-xs", value: value, onChange: (event) => onChange(event.target.value) }));
33
+ case 'string':
34
+ return (_jsx(Input, { className: "h-8 text-[13px]", value: value, onChange: (event) => onChange(event.target.value) }));
35
+ default: {
36
+ const exhaustive = field.type;
37
+ return exhaustive;
38
+ }
39
+ }
40
+ }
41
+ function InputFields({ form, values, onChange, }) {
42
+ switch (form.kind) {
43
+ case 'text':
44
+ return (_jsx(Textarea, { className: "max-h-64 min-h-20", value: values.source, onChange: (event) => onChange({ ...values, source: event.target.value }), "aria-label": "Input" }));
45
+ case 'fields':
46
+ return form.fields.map((field) => (_jsxs("label", { className: field.type === 'boolean' ? 'flex items-center gap-2 text-xs' : 'grid gap-1.5 text-xs', children: [_jsx("span", { className: "text-foreground font-medium", children: field.label }), _jsx(FieldInput, { field: field, value: values.fields[field.name] ?? '', onChange: (value) => onChange({ ...values, fields: { ...values.fields, [field.name]: value } }) })] }, field.name)));
47
+ case 'json':
48
+ return (_jsx(Textarea, { className: "max-h-80 min-h-28 font-mono text-xs", value: values.source, onChange: (event) => onChange({ ...values, source: event.target.value }), "aria-label": "JSON input", spellCheck: false }));
49
+ default: {
50
+ const exhaustive = form;
51
+ return exhaustive;
52
+ }
53
+ }
54
+ }
55
+ /** Starts a run from a form built from the definition's input schema, then shows its result. */
56
+ export function StartForm(context) {
57
+ const form = inputForm(context.definition.inputSchema);
58
+ const [values, setValues] = useState(() => initialValues(form));
59
+ const { start, fail, pending, error } = useStartRun(context);
60
+ function submit() {
61
+ let input;
62
+ try {
63
+ input = formInput(form, values);
64
+ }
65
+ catch (cause) {
66
+ fail(cause);
67
+ return;
68
+ }
69
+ start(input);
70
+ }
71
+ return (_jsx(ScrollArea, { className: "h-full", children: _jsxs("div", { className: "p-4", children: [context.runId ? (_jsx(RunResult, { runId: context.runId })) : (_jsxs("form", { className: "grid gap-4", onSubmit: (event) => {
72
+ event.preventDefault();
73
+ submit();
74
+ }, children: [_jsxs("div", { children: [_jsx("p", { className: "text-[13px] font-medium", children: "Input" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Fields come from the definition's input schema." })] }), _jsx(InputFields, { form: form, values: values, onChange: setValues }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Button, { type: "submit", size: "sm", disabled: pending, children: [_jsx(Play, {}), "Start run"] }), error ? _jsx("p", { className: "text-status-failed min-w-0 text-[11px]", children: error }) : null] })] })), context.runId && error ? (_jsx("p", { className: "text-status-failed mt-3 text-[11px]", children: error })) : null] }) }));
75
+ }
@@ -0,0 +1,14 @@
1
+ import { type JsonValue } from '@swirls/looms/core';
2
+ import type { WorkspaceContext } from '../plugin';
3
+ export interface StartedRun {
4
+ readonly runId: string;
5
+ readonly threadId: string;
6
+ }
7
+ export interface StartRun {
8
+ /** Starts a run of the workspace definition, then runs `after` once the start is accepted. */
9
+ readonly start: <T>(input: JsonValue, after?: (started: StartedRun) => Promise<T>) => void;
10
+ readonly fail: (cause: unknown) => void;
11
+ readonly pending: boolean;
12
+ readonly error: string | null;
13
+ }
14
+ export declare function useStartRun({ definition, onStarted }: WorkspaceContext): StartRun;
@@ -0,0 +1,32 @@
1
+ import { useState } from 'react';
2
+ import { createRunId } from '@swirls/looms/core';
3
+ import { useDebugger } from '../context.js';
4
+ import { errorMessage } from '../lib/cn.js';
5
+ export function useStartRun({ definition, onStarted }) {
6
+ const { client } = useDebugger();
7
+ const [pending, setPending] = useState(false);
8
+ const [error, setError] = useState(null);
9
+ function start(input, after) {
10
+ const runId = createRunId();
11
+ setPending(true);
12
+ setError(null);
13
+ onStarted(runId);
14
+ client
15
+ .startRun({
16
+ kind: definition.kind,
17
+ definitionName: definition.name,
18
+ definitionVersion: definition.version,
19
+ input,
20
+ runId,
21
+ })
22
+ .then((result) => after?.({ runId: result.runId, threadId: result.threadId }))
23
+ .catch((cause) => setError(errorMessage(cause)))
24
+ .finally(() => setPending(false));
25
+ }
26
+ return {
27
+ start,
28
+ fail: (cause) => setError(errorMessage(cause)),
29
+ pending,
30
+ error,
31
+ };
32
+ }
@@ -0,0 +1,6 @@
1
+ import type { PublishedDefinition } from '@swirls/looms/core';
2
+ export declare function Workspace({ definition, runId, onStarted, }: {
3
+ definition: PublishedDefinition | undefined;
4
+ runId?: string;
5
+ onStarted: (runId: string) => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MousePointerClick } from 'lucide-react';
3
+ import { useDebugger } from '../context.js';
4
+ import { workspaceFor } from '../plugin.js';
5
+ import { EmptyState, PanelHeader } from '../ui/panel.js';
6
+ import { definitionKey } from './definition-key.js';
7
+ import { StartForm } from './start-form.js';
8
+ export function Workspace({ definition, runId, onStarted, }) {
9
+ const { plugins } = useDebugger();
10
+ if (!definition) {
11
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsx(PanelHeader, {}), _jsx(EmptyState, { icon: _jsx(MousePointerClick, {}), title: "Pick a run type", children: "Choose one from the run type menu to start a run, or press \u2318K to open a past run." })] }));
12
+ }
13
+ const View = workspaceFor(plugins, definition)?.component ?? StartForm;
14
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(PanelHeader, { children: _jsxs("h2", { className: "flex min-w-0 items-baseline gap-2", children: [_jsx("span", { className: "shrink-0 text-[13px] font-medium", children: definition.name }), definition.description ? (_jsx("span", { className: "text-muted-foreground truncate text-xs", title: definition.description, children: definition.description })) : null] }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: _jsx(View, { definition: definition, runId: runId, onStarted: onStarted }, definitionKey(definition)) })] }));
15
+ }
@@ -1,31 +1,129 @@
1
- @source "./**/*.{ts,tsx}";
1
+ @source "./**/*.js";
2
+ @source "../agent/debugger/**/*.js";
3
+ @source "../workflow/debugger/**/*.js";
4
+ @source "../approval/debugger/**/*.js";
2
5
 
3
6
  @theme inline {
7
+ --color-background: var(--background);
8
+ --color-foreground: var(--foreground);
9
+ --color-card: var(--card);
10
+ --color-card-foreground: var(--card-foreground);
11
+ --color-primary: var(--primary);
12
+ --color-primary-foreground: var(--primary-foreground);
13
+ --color-secondary: var(--secondary);
14
+ --color-secondary-foreground: var(--secondary-foreground);
15
+ --color-muted: var(--muted);
16
+ --color-muted-foreground: var(--muted-foreground);
17
+ --color-accent: var(--accent);
18
+ --color-accent-foreground: var(--accent-foreground);
19
+ --color-destructive: var(--destructive);
20
+ --color-border: var(--border);
21
+ --color-input: var(--input);
22
+ --color-ring: var(--ring);
23
+ --color-sidebar: var(--sidebar);
24
+ --color-sidebar-foreground: var(--sidebar-foreground);
4
25
  --color-status-running: var(--status-running);
5
26
  --color-status-waiting: var(--status-waiting);
6
27
  --color-status-completed: var(--status-completed);
7
28
  --color-status-failed: var(--status-failed);
8
29
  --color-status-cancelled: var(--status-cancelled);
9
- --color-family-runtime: var(--family-runtime);
10
- --color-family-agent: var(--family-agent);
11
- --color-family-workflow: var(--family-workflow);
12
- --color-family-approval: var(--family-approval);
13
- --color-family-payments: var(--family-payments);
14
- --color-family-wait: var(--family-wait);
30
+ --color-json-key: var(--json-key);
31
+ --color-json-string: var(--json-string);
32
+ --color-json-number: var(--json-number);
33
+ --color-json-boolean: var(--json-boolean);
34
+ --color-json-null: var(--json-null);
35
+ --ease-snappy: var(--debugger-ease);
36
+ }
37
+
38
+ /* Layered so a host's own unlayered :root theme (for example shadcn/ui) takes precedence. */
39
+ @layer base {
40
+ :root {
41
+ --background: oklch(0.16 0.005 260);
42
+ --foreground: oklch(0.93 0.01 260);
43
+ --card: oklch(0.19 0.006 260);
44
+ --card-foreground: oklch(0.93 0.01 260);
45
+ --primary: oklch(0.93 0.01 260);
46
+ --primary-foreground: oklch(0.2 0.006 260);
47
+ --secondary: oklch(0.24 0.008 260);
48
+ --secondary-foreground: oklch(0.93 0.01 260);
49
+ --muted: oklch(0.24 0.008 260);
50
+ --muted-foreground: oklch(0.68 0.015 260);
51
+ --accent: oklch(0.26 0.01 260);
52
+ --accent-foreground: oklch(0.95 0.01 260);
53
+ --destructive: oklch(0.65 0.18 25);
54
+ --border: oklch(1 0 0 / 10%);
55
+ --input: oklch(1 0 0 / 12%);
56
+ --ring: oklch(0.55 0.02 260);
57
+ --sidebar: oklch(0.18 0.006 260);
58
+ --sidebar-foreground: oklch(0.93 0.01 260);
59
+ }
60
+
61
+ :where(.looms-debugger, .debugger-pop),
62
+ :where(.looms-debugger, .debugger-pop) *,
63
+ :where(.looms-debugger, .debugger-pop) ::before,
64
+ :where(.looms-debugger, .debugger-pop) ::after {
65
+ border-color: var(--color-border);
66
+ }
15
67
  }
16
68
 
17
69
  :root {
70
+ --debugger-ease: cubic-bezier(0.23, 1, 0.32, 1);
18
71
  --status-running: oklch(0.74 0.12 240);
19
72
  --status-waiting: oklch(0.8 0.14 85);
20
73
  --status-completed: oklch(0.76 0.13 155);
21
74
  --status-failed: oklch(0.68 0.18 25);
22
75
  --status-cancelled: oklch(0.62 0.01 260);
23
- --family-runtime: oklch(0.76 0.13 155);
24
- --family-agent: oklch(0.74 0.12 240);
25
- --family-workflow: oklch(0.76 0.12 300);
26
- --family-approval: oklch(0.8 0.14 85);
27
- --family-payments: oklch(0.78 0.1 175);
28
- --family-wait: oklch(0.8 0.14 85);
76
+ --json-key: oklch(0.8 0.06 280);
77
+ --json-string: oklch(0.8 0.1 150);
78
+ --json-number: oklch(0.8 0.1 230);
79
+ --json-boolean: oklch(0.82 0.11 70);
80
+ --json-null: oklch(0.6 0.01 260);
81
+ }
82
+
83
+ @keyframes debugger-pop-in {
84
+ from {
85
+ opacity: 0;
86
+ transform: scale(0.97);
87
+ }
88
+ }
89
+
90
+ @keyframes debugger-fade-in {
91
+ from {
92
+ opacity: 0;
93
+ }
94
+ }
95
+
96
+ /* Anchored overlays grow from their trigger; keyboard-opened ones appear instantly. */
97
+ .debugger-pop {
98
+ transform-origin: var(--radix-popper-transform-origin);
99
+ animation: debugger-pop-in 150ms var(--debugger-ease);
100
+ }
101
+
102
+ .debugger-pop[data-instant] {
103
+ animation: none;
104
+ }
105
+
106
+ .debugger-float-in {
107
+ transition:
108
+ opacity 150ms var(--debugger-ease),
109
+ transform 150ms var(--debugger-ease),
110
+ scale 150ms var(--debugger-ease),
111
+ background-color 150ms var(--debugger-ease);
112
+
113
+ @starting-style {
114
+ opacity: 0;
115
+ transform: translateY(4px);
116
+ }
117
+ }
118
+
119
+ @media (prefers-reduced-motion: reduce) {
120
+ .debugger-pop {
121
+ animation-name: debugger-fade-in;
122
+ }
123
+
124
+ .debugger-float-in {
125
+ transition-property: opacity;
126
+ }
29
127
  }
30
128
 
31
129
  .debugger-event-stream {
@@ -55,17 +153,3 @@
55
153
  display: inline;
56
154
  }
57
155
  }
58
-
59
- @container (max-width: 22rem) {
60
- .debugger-follow-label {
61
- position: absolute;
62
- width: 1px;
63
- height: 1px;
64
- padding: 0;
65
- margin: -1px;
66
- overflow: hidden;
67
- clip: rect(0, 0, 0, 0);
68
- white-space: nowrap;
69
- border: 0;
70
- }
71
- }
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ declare const variants: {
3
+ readonly default: 'bg-primary text-primary-foreground hover:bg-primary/90';
4
+ readonly secondary: 'bg-secondary text-secondary-foreground hover:bg-accent';
5
+ readonly outline: 'border-input hover:bg-accent hover:text-accent-foreground border bg-transparent';
6
+ readonly ghost: 'text-muted-foreground hover:bg-accent hover:text-foreground';
7
+ readonly destructive: 'text-muted-foreground hover:bg-status-failed/15 hover:text-status-failed';
8
+ };
9
+ declare const sizes: {
10
+ readonly xs: 'h-6 gap-1 rounded-sm px-2 text-[11px] [&_svg]:size-3';
11
+ readonly sm: 'h-7 gap-1.5 rounded-md px-2.5 text-xs [&_svg]:size-3.5';
12
+ readonly default: 'h-8 gap-1.5 rounded-md px-3 text-xs [&_svg]:size-3.5';
13
+ readonly 'icon-xs': 'size-6 rounded-sm [&_svg]:size-3';
14
+ readonly 'icon-sm': 'size-7 rounded-md [&_svg]:size-3.5';
15
+ readonly icon: 'size-8 rounded-md [&_svg]:size-4';
16
+ };
17
+ export type ButtonVariant = keyof typeof variants;
18
+ export type ButtonSize = keyof typeof sizes;
19
+ declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<'button'> & {
20
+ variant?: ButtonVariant;
21
+ size?: ButtonSize;
22
+ asChild?: boolean;
23
+ }): React.JSX.Element;
24
+ export { Button };
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Slot } from 'radix-ui';
3
+ import * as React from 'react';
4
+ import { cn } from '../lib/cn.js';
5
+ const variants = {
6
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
7
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-accent',
8
+ outline: 'border-input hover:bg-accent hover:text-accent-foreground border bg-transparent',
9
+ ghost: 'text-muted-foreground hover:bg-accent hover:text-foreground',
10
+ destructive: 'text-muted-foreground hover:bg-status-failed/15 hover:text-status-failed',
11
+ };
12
+ const sizes = {
13
+ xs: 'h-6 gap-1 rounded-sm px-2 text-[11px] [&_svg]:size-3',
14
+ sm: 'h-7 gap-1.5 rounded-md px-2.5 text-xs [&_svg]:size-3.5',
15
+ default: 'h-8 gap-1.5 rounded-md px-3 text-xs [&_svg]:size-3.5',
16
+ 'icon-xs': 'size-6 rounded-sm [&_svg]:size-3',
17
+ 'icon-sm': 'size-7 rounded-md [&_svg]:size-3.5',
18
+ icon: 'size-8 rounded-md [&_svg]:size-4',
19
+ };
20
+ function Button({ className, variant = 'default', size = 'default', asChild = false, ...props }) {
21
+ const Comp = asChild ? Slot.Root : 'button';
22
+ return (_jsx(Comp, { "data-slot": "button", type: asChild ? undefined : (props.type ?? 'button'), className: cn('focus-visible:ring-ring/50 ease-snappy inline-flex shrink-0 items-center justify-center font-medium whitespace-nowrap outline-none select-none focus-visible:ring-[3px] active:scale-[0.97] disabled:pointer-events-none disabled:opacity-50 [&_svg]:shrink-0', 'transition-[transform,background-color,color,opacity] duration-150', variants[variant], sizes[size], className), ...props }));
23
+ }
24
+ export { Button };
@@ -0,0 +1,9 @@
1
+ import { Command as CommandPrimitive } from 'cmdk';
2
+ import * as React from 'react';
3
+ declare function Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>): React.JSX.Element;
4
+ declare function CommandInput({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Input>): React.JSX.Element;
5
+ declare function CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>): React.JSX.Element;
6
+ declare function CommandEmpty({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>): React.JSX.Element;
7
+ declare function CommandGroup({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Group>): React.JSX.Element;
8
+ declare function CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>): React.JSX.Element;
9
+ export { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList };