@swirls/looms 0.2.0 → 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 (113) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/actor/host.js +4 -6
  3. package/dist/actor/index.d.ts +1 -1
  4. package/dist/actor/index.js +1 -1
  5. package/dist/actor/routing.d.ts +5 -0
  6. package/dist/actor/routing.js +14 -0
  7. package/dist/agent/agent-session.js +1 -0
  8. package/dist/agent/debugger/agent-chat.js +45 -38
  9. package/dist/agent/debugger/streaming-text.d.ts +6 -0
  10. package/dist/agent/debugger/streaming-text.js +33 -0
  11. package/dist/agent/debugger/summarize.js +2 -0
  12. package/dist/agent/effects.d.ts +30 -0
  13. package/dist/agent/events.d.ts +2 -0
  14. package/dist/agent/events.js +2 -0
  15. package/dist/agent/llm.js +1 -8
  16. package/dist/agent/module.d.ts +230 -160
  17. package/dist/agent/module.js +11 -7
  18. package/dist/agent/projections.d.ts +5 -0
  19. package/dist/agent/projections.js +43 -2
  20. package/dist/agent/scope.d.ts +2 -0
  21. package/dist/agent/signals.d.ts +5 -0
  22. package/dist/agent/signals.js +7 -0
  23. package/dist/agent/threads.js +35 -21
  24. package/dist/agent/transcript.d.ts +7 -0
  25. package/dist/agent/transcript.js +19 -0
  26. package/dist/agent/types.d.ts +10 -0
  27. package/dist/agent/types.js +4 -0
  28. package/dist/approval/debugger/approvals-view.js +5 -3
  29. package/dist/approval/effects.d.ts +12 -0
  30. package/dist/approval/module.d.ts +24 -0
  31. package/dist/client/client.d.ts +292 -0
  32. package/dist/client/client.js +17 -1
  33. package/dist/cloudflare/router.d.ts +1 -1
  34. package/dist/cloudflare/router.js +6 -8
  35. package/dist/core/attempts.d.ts +8 -0
  36. package/dist/core/attempts.js +27 -0
  37. package/dist/core/effects.d.ts +7 -0
  38. package/dist/core/effects.js +1 -0
  39. package/dist/core/fold.js +12 -6
  40. package/dist/core/fork.d.ts +35 -0
  41. package/dist/core/fork.js +129 -0
  42. package/dist/core/index.d.ts +3 -0
  43. package/dist/core/index.js +2 -0
  44. package/dist/core/module-scope.d.ts +1 -0
  45. package/dist/core/module.d.ts +2 -2
  46. package/dist/core/projection.d.ts +5 -4
  47. package/dist/core/projection.js +1 -0
  48. package/dist/core/protocol.d.ts +10 -1
  49. package/dist/core/protocol.js +11 -0
  50. package/dist/{react → core}/register.d.ts +9 -8
  51. package/dist/core/replay.d.ts +8 -0
  52. package/dist/core/snapshots.js +3 -17
  53. package/dist/core/state.d.ts +14 -0
  54. package/dist/core/state.js +25 -2
  55. package/dist/core/store.d.ts +1 -0
  56. package/dist/debugger/app/assets/index-CgDzWJg7.css +2 -0
  57. package/dist/debugger/app/assets/index-D--9esm9.js +22 -0
  58. package/dist/debugger/app/assets/routes-Dakrdusf.js +47 -0
  59. package/dist/debugger/app/index.html +2 -2
  60. package/dist/debugger/components/composer.d.ts +5 -2
  61. package/dist/debugger/components/composer.js +5 -4
  62. package/dist/debugger/components/run-tree.js +2 -5
  63. package/dist/debugger/events/catalog.js +1 -3
  64. package/dist/debugger/events/protocol-summarize.js +7 -0
  65. package/dist/debugger/lib/event-counts.d.ts +2 -2
  66. package/dist/debugger/lib/event-counts.js +5 -9
  67. package/dist/debugger/shell/debugger-shell.js +1 -1
  68. package/dist/debugger/shell/run-controls.d.ts +2 -1
  69. package/dist/debugger/shell/run-controls.js +40 -13
  70. package/dist/debugger/shell/run-search.js +1 -3
  71. package/dist/evaluator/effects.d.ts +45 -33
  72. package/dist/evaluator/module.d.ts +90 -66
  73. package/dist/projectors/index-model.d.ts +13 -7
  74. package/dist/projectors/index-model.js +21 -26
  75. package/dist/projectors/memory.js +19 -11
  76. package/dist/projectors/postgres.js +20 -19
  77. package/dist/projectors/schema.js +20 -18
  78. package/dist/projectors/sqlite.js +32 -27
  79. package/dist/react/derived.d.ts +26 -8
  80. package/dist/react/derived.js +23 -26
  81. package/dist/react/index.d.ts +3 -3
  82. package/dist/react/index.js +2 -2
  83. package/dist/react/materialize.js +16 -11
  84. package/dist/react/react.d.ts +12 -9
  85. package/dist/react/react.js +9 -6
  86. package/dist/react/store.d.ts +4 -5
  87. package/dist/react/tables.d.ts +3 -2
  88. package/dist/runtime/cancellation.js +14 -27
  89. package/dist/runtime/errors.d.ts +39 -0
  90. package/dist/runtime/errors.js +32 -0
  91. package/dist/runtime/helpers.d.ts +2 -0
  92. package/dist/runtime/helpers.js +23 -1
  93. package/dist/runtime/index.d.ts +1 -1
  94. package/dist/runtime/index.js +1 -1
  95. package/dist/runtime/looms.d.ts +4 -0
  96. package/dist/runtime/looms.js +1 -0
  97. package/dist/runtime/run-start.js +1 -3
  98. package/dist/runtime/runtime.d.ts +1 -1
  99. package/dist/runtime/runtime.js +45 -18
  100. package/dist/runtime/server/http.d.ts +2 -0
  101. package/dist/runtime/server/http.js +17 -2
  102. package/dist/runtime/server/routes/runs.js +35 -2
  103. package/dist/runtime/types.d.ts +8 -0
  104. package/dist/runtime/wake-engine.js +5 -2
  105. package/dist/s2/snapshot-store.js +2 -6
  106. package/dist/workflow/effects.d.ts +24 -0
  107. package/dist/workflow/effects.js +1 -0
  108. package/dist/workflow/module.d.ts +48 -0
  109. package/package.json +2 -2
  110. package/dist/debugger/app/assets/index-B3Ah8WAE.css +0 -2
  111. package/dist/debugger/app/assets/index-Qb371t_O.js +0 -22
  112. package/dist/debugger/app/assets/routes-CjtEHgPQ.js +0 -47
  113. /package/dist/{react → core}/register.js +0 -0
@@ -4,9 +4,9 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Looms debugger</title>
7
- <script type="module" crossorigin src="./assets/index-Qb371t_O.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-D--9esm9.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-CbXtAM7H.js">
9
- <link rel="stylesheet" crossorigin href="./assets/index-B3Ah8WAE.css">
9
+ <link rel="stylesheet" crossorigin href="./assets/index-CgDzWJg7.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
@@ -1,12 +1,15 @@
1
1
  import type { ReactNode } from 'react';
2
2
  /**
3
3
  * Message input with one primary action. Enter sends, Shift+Enter adds a newline,
4
- * and `toolbar` holds secondary controls such as delivery mode.
4
+ * and `toolbar` holds secondary controls such as delivery mode. `onStop` adds a
5
+ * Stop button beside Send while work is in progress.
5
6
  */
6
- export declare function Composer({ value, onChange, onSubmit, pending, disabled, placeholder, submitLabel, toolbar, error, className, }: {
7
+ export declare function Composer({ value, onChange, onSubmit, onStop, stopping, pending, disabled, placeholder, submitLabel, toolbar, error, className, }: {
7
8
  value: string;
8
9
  onChange: (value: string) => void;
9
10
  onSubmit: () => void;
11
+ onStop?: () => void;
12
+ stopping?: boolean;
10
13
  pending?: boolean;
11
14
  disabled?: boolean;
12
15
  placeholder?: string;
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ArrowUp, LoaderCircle } from 'lucide-react';
2
+ import { ArrowUp, LoaderCircle, Square } from 'lucide-react';
3
3
  import { cn } from '../lib/cn.js';
4
4
  import { Button } from '../ui/button.js';
5
5
  import { Kbd } from '../ui/panel.js';
6
6
  /**
7
7
  * Message input with one primary action. Enter sends, Shift+Enter adds a newline,
8
- * and `toolbar` holds secondary controls such as delivery mode.
8
+ * and `toolbar` holds secondary controls such as delivery mode. `onStop` adds a
9
+ * Stop button beside Send while work is in progress.
9
10
  */
10
- export function Composer({ value, onChange, onSubmit, pending = false, disabled = false, placeholder = 'Message', submitLabel = 'Send', toolbar, error, className, }) {
11
+ export function Composer({ value, onChange, onSubmit, onStop, stopping = false, pending = false, disabled = false, placeholder = 'Message', submitLabel = 'Send', toolbar, error, className, }) {
11
12
  const canSubmit = !pending && !disabled && value.trim().length > 0;
12
13
  return (_jsxs("form", { className: cn('border-border shrink-0 border-t p-3', className), onSubmit: (event) => {
13
14
  event.preventDefault();
@@ -21,5 +22,5 @@ export function Composer({ value, onChange, onSubmit, pending = false, disabled
21
22
  onSubmit();
22
23
  }
23
24
  }
24
- }, className: "placeholder:text-muted-foreground block field-sizing-content max-h-48 min-h-11 w-full resize-none bg-transparent px-3 pt-2.5 pb-1 text-[13px] leading-relaxed outline-none disabled:cursor-not-allowed disabled:opacity-50" }), _jsxs("div", { className: "flex h-10 items-center gap-1 pr-1.5 pl-1.5", children: [toolbar, _jsxs("span", { className: "text-muted-foreground ml-auto hidden items-center gap-1 pr-1 text-[10px] @sm:flex", children: [_jsx(Kbd, { children: "\u21B5" }), submitLabel.toLowerCase(), _jsx(Kbd, { className: "ml-1.5", children: "\u21E7\u21B5" }), "newline"] }), _jsx(Button, { type: "submit", size: "icon-sm", "aria-label": submitLabel, disabled: !canSubmit, className: "ml-auto rounded-full @sm:ml-0", children: pending ? _jsx(LoaderCircle, { className: "animate-spin" }) : _jsx(ArrowUp, {}) })] })] }), error ? _jsx("p", { className: "text-status-failed mt-2 px-1 text-[11px]", children: error }) : null] }));
25
+ }, className: "placeholder:text-muted-foreground block field-sizing-content max-h-48 min-h-11 w-full resize-none bg-transparent px-3 pt-2.5 pb-1 text-[13px] leading-relaxed outline-none disabled:cursor-not-allowed disabled:opacity-50" }), _jsxs("div", { className: "flex h-10 items-center gap-1 pr-1.5 pl-1.5", children: [toolbar, _jsxs("span", { className: "text-muted-foreground ml-auto hidden items-center gap-1 pr-1 text-[10px] @sm:flex", children: [_jsx(Kbd, { children: "\u21B5" }), submitLabel.toLowerCase(), _jsx(Kbd, { className: "ml-1.5", children: "\u21E7\u21B5" }), "newline"] }), onStop ? (_jsx(Button, { type: "button", size: "icon-sm", variant: "outline", "aria-label": "Stop", title: "Stop the current turn", disabled: stopping, onClick: onStop, className: "ml-auto rounded-full @sm:ml-0", children: stopping ? (_jsx(LoaderCircle, { className: "animate-spin" })) : (_jsx(Square, { className: "fill-current" })) })) : null, _jsx(Button, { type: "submit", size: "icon-sm", "aria-label": submitLabel, disabled: !canSubmit, className: cn('rounded-full', !onStop && 'ml-auto @sm:ml-0'), children: pending ? _jsx(LoaderCircle, { className: "animate-spin" }) : _jsx(ArrowUp, {}) })] })] }), error ? _jsx("p", { className: "text-status-failed mt-2 px-1 text-[11px]", children: error }) : null] }));
25
26
  }
@@ -21,11 +21,8 @@ function flatten(root, collapsed) {
21
21
  }
22
22
  rows.push(next);
23
23
  if (!collapsed.has(next.node.threadId)) {
24
- for (let index = next.node.children.length - 1; index >= 0; index--) {
25
- const child = next.node.children[index];
26
- if (child) {
27
- stack.push({ node: child, depth: next.depth + 1 });
28
- }
24
+ for (const child of next.node.children.toReversed()) {
25
+ stack.push({ node: child, depth: next.depth + 1 });
29
26
  }
30
27
  }
31
28
  }
@@ -12,9 +12,7 @@ function searchText(event, summary) {
12
12
  /** Each event type belongs to the family view with the longest matching prefix. */
13
13
  export function createEventCatalog(plugins = []) {
14
14
  const views = [...plugins.flatMap((plugin) => plugin.families ?? []), ...builtinFamilies];
15
- // ES2022 has no Array#toSorted. `views` is a fresh array.
16
- // oxlint-disable-next-line unicorn/no-array-sort
17
- const byPrefix = views.sort((left, right) => prefixOf(right).length - prefixOf(left).length);
15
+ const byPrefix = views.toSorted((left, right) => prefixOf(right).length - prefixOf(left).length);
18
16
  const colors = new Map(views.map((view) => [view.family, view.color]));
19
17
  function viewOf(type) {
20
18
  return byPrefix.find((view) => type.startsWith(prefixOf(view))) ?? runtimeFamily;
@@ -19,6 +19,13 @@ export function summarizeProtocolEvent(event) {
19
19
  };
20
20
  case 'runtime.run.cancelled':
21
21
  return { title: 'run cancelled', detail: jsonText(payload.reason) };
22
+ case 'runtime.run.forked':
23
+ return {
24
+ title: 'run forked',
25
+ detail: `from ${jsonText(payload.sourceRunId) ?? 'unknown'} at seq ${jsonNumberText(payload.sourceSeq) ?? '?'}`,
26
+ };
27
+ case 'runtime.fork.elided':
28
+ return { title: 'left out of fork', detail: jsonText(payload.type) };
22
29
  case 'runtime.thread.started':
23
30
  return {
24
31
  title: 'thread started',
@@ -1,5 +1,5 @@
1
- import { type EventEnvelope } from '@swirls/looms/core';
1
+ import { type EventEnvelope, type RunStatus } from '@swirls/looms/core';
2
2
  /** Build thread→count map via the shared EventIndex. */
3
3
  export declare function countEventsByThread(events: readonly EventEnvelope[]): Map<string, number>;
4
- export declare function runStatusFromEvents(events: readonly EventEnvelope[]): string;
4
+ export declare function runStatusFromEvents(events: readonly EventEnvelope[]): RunStatus;
5
5
  export declare function startedAtFromEvents(events: readonly EventEnvelope[]): number | undefined;
@@ -1,4 +1,4 @@
1
- import { EventIndex, isJsonObject, isJsonString } from '@swirls/looms/core';
1
+ import { completedRunStatus, EventIndex } from '@swirls/looms/core';
2
2
  /** Build thread→count map via the shared EventIndex. */
3
3
  export function countEventsByThread(events) {
4
4
  const index = new EventIndex();
@@ -6,15 +6,11 @@ export function countEventsByThread(events) {
6
6
  return new Map(index.getCounts());
7
7
  }
8
8
  export function runStatusFromEvents(events) {
9
- for (let index = events.length - 1; index >= 0; index -= 1) {
10
- const event = events[index];
11
- if (!event || event.type !== 'runtime.run.completed') {
12
- continue;
13
- }
14
- const payload = isJsonObject(event.payload) ? event.payload : {};
15
- return isJsonString(payload.error) && payload.error.length > 0 ? 'failed' : 'completed';
9
+ const ended = events.findLast((event) => event.type === 'runtime.run.cancelled' || event.type === 'runtime.run.completed');
10
+ if (!ended) {
11
+ return 'running';
16
12
  }
17
- return 'running';
13
+ return ended.type === 'runtime.run.cancelled' ? 'cancelled' : completedRunStatus(ended.payload);
18
14
  }
19
15
  export function startedAtFromEvents(events) {
20
16
  const index = new EventIndex();
@@ -36,7 +36,7 @@ function DebuggerFrame({ selection, onSelectionChange, }) {
36
36
  kind: run.kind ?? undefined,
37
37
  name: run.definitionName ?? undefined,
38
38
  run: run.runId,
39
- }) }), _jsxs("div", { className: "ml-auto flex min-w-0 items-center gap-2", children: [error ? (_jsx("p", { className: "text-status-failed truncate text-[11px]", title: error, children: error })) : null, selection.run ? _jsx(RunControls, { runId: selection.run }) : null] })] }), _jsxs(ResizablePanelGroup, { id: "looms-debugger-columns", className: "min-h-0 flex-1", defaultLayout: layout.defaultLayout, onLayoutChanged: layout.onLayoutChanged, children: [_jsx(ResizablePanel, { id: "tree", defaultSize: "17%", minSize: "12rem", maxSize: "30%", className: "bg-sidebar", children: _jsx("section", { "aria-label": "Run tree", className: "h-full min-h-0 overflow-hidden", children: selection.run ? (_jsx(RunTreePane, { runId: selection.run, threadId: selection.thread, onSelectThread: (thread) => onSelectionChange({ ...selection, thread, seq: undefined }) })) : (_jsxs("div", { className: "flex h-full flex-col", children: [_jsx(PanelHeader, { children: _jsx(PanelTitle, { children: "Run tree" }) }), _jsx(EmptyState, { icon: _jsx(ListTree, {}), title: "No run selected", children: "Threads and child runs appear here once a run starts." })] })) }) }), _jsx(ResizableHandle, {}), _jsx(ResizablePanel, { id: "workspace", defaultSize: "29%", minSize: "16rem", children: _jsx("section", { "aria-label": "Workspace", className: "@container h-full min-h-0 overflow-hidden", children: _jsx(Workspace, { definition: selected, runId: selection.run, onStarted: (run) => onSelectionChange({ kind, name, run }) }) }) }), _jsx(ResizableHandle, {}), _jsx(ResizablePanel, { id: "events", defaultSize: "32%", minSize: "16rem", children: _jsx("section", { "aria-label": "Events", className: "h-full min-h-0 overflow-hidden", children: selection.run ? (_jsx(EventsPane, { runId: selection.run, threadId: selection.thread, seq: selection.seq, onSelectSeq: (seq) => onSelectionChange({ ...selection, seq }), onClearThread: () => onSelectionChange({ ...selection, thread: undefined }) })) : (_jsxs("div", { className: "flex h-full flex-col", children: [_jsx(PanelHeader, { children: _jsx(PanelTitle, { children: "Events" }) }), _jsx(EmptyState, { icon: _jsx(Activity, {}), title: "No events yet", children: "Events stream in live once a run starts." })] })) }) }), _jsx(ResizableHandle, {}), _jsx(ResizablePanel, { id: "projections", defaultSize: "22%", minSize: "14rem", className: "bg-sidebar", children: _jsx("section", { "aria-label": "Projections", className: "h-full min-h-0 overflow-hidden", children: _jsx(ProjectionsPanel, { runId: selection.run, names: catalog.projections }) }) })] })] }));
39
+ }) }), _jsxs("div", { className: "ml-auto flex min-w-0 items-center gap-2", children: [error ? (_jsx("p", { className: "text-status-failed truncate text-[11px]", title: error, children: error })) : null, selection.run ? (_jsx(RunControls, { runId: selection.run, onForked: (run) => onSelectionChange({ kind, name, run }) })) : null] })] }), _jsxs(ResizablePanelGroup, { id: "looms-debugger-columns", className: "min-h-0 flex-1", defaultLayout: layout.defaultLayout, onLayoutChanged: layout.onLayoutChanged, children: [_jsx(ResizablePanel, { id: "tree", defaultSize: "17%", minSize: "12rem", maxSize: "30%", className: "bg-sidebar", children: _jsx("section", { "aria-label": "Run tree", className: "h-full min-h-0 overflow-hidden", children: selection.run ? (_jsx(RunTreePane, { runId: selection.run, threadId: selection.thread, onSelectThread: (thread) => onSelectionChange({ ...selection, thread, seq: undefined }) })) : (_jsxs("div", { className: "flex h-full flex-col", children: [_jsx(PanelHeader, { children: _jsx(PanelTitle, { children: "Run tree" }) }), _jsx(EmptyState, { icon: _jsx(ListTree, {}), title: "No run selected", children: "Threads and child runs appear here once a run starts." })] })) }) }), _jsx(ResizableHandle, {}), _jsx(ResizablePanel, { id: "workspace", defaultSize: "29%", minSize: "16rem", children: _jsx("section", { "aria-label": "Workspace", className: "@container h-full min-h-0 overflow-hidden", children: _jsx(Workspace, { definition: selected, runId: selection.run, onStarted: (run) => onSelectionChange({ kind, name, run }) }) }) }), _jsx(ResizableHandle, {}), _jsx(ResizablePanel, { id: "events", defaultSize: "32%", minSize: "16rem", children: _jsx("section", { "aria-label": "Events", className: "h-full min-h-0 overflow-hidden", children: selection.run ? (_jsx(EventsPane, { runId: selection.run, threadId: selection.thread, seq: selection.seq, onSelectSeq: (seq) => onSelectionChange({ ...selection, seq }), onClearThread: () => onSelectionChange({ ...selection, thread: undefined }) })) : (_jsxs("div", { className: "flex h-full flex-col", children: [_jsx(PanelHeader, { children: _jsx(PanelTitle, { children: "Events" }) }), _jsx(EmptyState, { icon: _jsx(Activity, {}), title: "No events yet", children: "Events stream in live once a run starts." })] })) }) }), _jsx(ResizableHandle, {}), _jsx(ResizablePanel, { id: "projections", defaultSize: "22%", minSize: "14rem", className: "bg-sidebar", children: _jsx("section", { "aria-label": "Projections", className: "h-full min-h-0 overflow-hidden", children: _jsx(ProjectionsPanel, { runId: selection.run, names: catalog.projections }) }) })] })] }));
40
40
  }
41
41
  export function DebuggerShell({ plugins, selection, onSelectionChange, endpoint, }) {
42
42
  return (_jsx(DebuggerProvider, { endpoint: endpoint, plugins: plugins, children: _jsx(DebuggerFrame, { selection: selection, onSelectionChange: onSelectionChange }) }));
@@ -1,4 +1,5 @@
1
1
  /** Run-scoped actions that live beside the run switcher. */
2
- export declare function RunControls({ runId }: {
2
+ export declare function RunControls({ runId, onForked, }: {
3
3
  runId: string;
4
+ onForked: (runId: string) => void;
4
5
  }): import("react").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { AlarmClock, CircleStop, Radio } from 'lucide-react';
3
- import { useState } from 'react';
2
+ import { Effect, Fiber } from 'effect';
3
+ import { AlarmClock, CircleStop, GitFork, Radio } from 'lucide-react';
4
+ import { useEffect, useState } from 'react';
4
5
  import { cleanUndefined } from '@swirls/looms/core';
5
6
  import { useRunStore, useRunSummary } from '@swirls/looms/react';
6
7
  import { useDebugger } from '../context.js';
@@ -10,7 +11,7 @@ import { Input } from '../ui/input.js';
10
11
  import { Kbd } from '../ui/panel.js';
11
12
  import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover.js';
12
13
  import { Textarea } from '../ui/textarea.js';
13
- const CANCEL_TYPE = 'runtime.thread.cancel.requested';
14
+ const NOTICE_MS = 3_000;
14
15
  function useRunAction() {
15
16
  const [error, setError] = useState(null);
16
17
  const [pending, setPending] = useState(false);
@@ -18,7 +19,7 @@ function useRunAction() {
18
19
  setPending(true);
19
20
  setError(null);
20
21
  action()
21
- .then(() => onDone?.())
22
+ .then((value) => onDone?.(value))
22
23
  .catch((cause) => setError(errorMessage(cause)))
23
24
  .finally(() => setPending(false));
24
25
  }
@@ -29,7 +30,7 @@ function Field({ label, hint, children }) {
29
30
  }
30
31
  function SignalForm({ runId, rootThreadId }) {
31
32
  const { client } = useDebugger();
32
- const [type, setType] = useState(CANCEL_TYPE);
33
+ const [type, setType] = useState('');
33
34
  const [payload, setPayload] = useState('{}');
34
35
  const [thread, setThread] = useState('');
35
36
  const [sent, setSent] = useState(false);
@@ -53,7 +54,7 @@ function SignalForm({ runId, rootThreadId }) {
53
54
  return (_jsxs("form", { className: "grid gap-3", onSubmit: (event) => {
54
55
  event.preventDefault();
55
56
  submit();
56
- }, children: [_jsxs("div", { children: [_jsx("p", { className: "text-[13px] font-medium", children: "Send a signal" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Append an event to this run's log." })] }), _jsx(Field, { label: "Type", children: _jsx(Input, { value: type, onChange: (event) => setType(event.target.value), className: "h-7 font-mono text-xs", autoFocus: true }) }), _jsx(Field, { label: "Thread", hint: "defaults to root", children: _jsx(Input, { value: thread, placeholder: rootThreadId ?? 'thread id', onChange: (event) => setThread(event.target.value), className: "h-7 font-mono text-xs" }) }), _jsx(Field, { label: "Payload", hint: "JSON", children: _jsx(Textarea, { value: payload, onChange: (event) => setPayload(event.target.value), onKeyDown: (event) => {
57
+ }, children: [_jsxs("div", { children: [_jsx("p", { className: "text-[13px] font-medium", children: "Send a signal" }), _jsx("p", { className: "text-muted-foreground text-xs", children: "Append an event to this run's log." })] }), _jsx(Field, { label: "Type", children: _jsx(Input, { value: type, placeholder: "module.event", onChange: (event) => setType(event.target.value), className: "h-7 font-mono text-xs", autoFocus: true }) }), _jsx(Field, { label: "Thread", hint: "defaults to root", children: _jsx(Input, { value: thread, placeholder: rootThreadId ?? 'thread id', onChange: (event) => setThread(event.target.value), className: "h-7 font-mono text-xs" }) }), _jsx(Field, { label: "Payload", hint: "JSON", children: _jsx(Textarea, { value: payload, onChange: (event) => setPayload(event.target.value), onKeyDown: (event) => {
57
58
  if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
58
59
  event.preventDefault();
59
60
  submit();
@@ -61,12 +62,38 @@ function SignalForm({ runId, rootThreadId }) {
61
62
  }, className: "max-h-48 min-h-20 font-mono text-xs", spellCheck: false }) }), _jsxs("div", { className: "flex items-center gap-2", children: [error ? (_jsx("p", { className: "text-status-failed min-w-0 flex-1 truncate text-[11px]", title: error, children: error })) : sent ? (_jsx("p", { className: "text-status-completed flex-1 text-[11px]", children: "Signal sent" })) : (_jsxs("p", { className: "text-muted-foreground flex flex-1 items-center gap-1 text-[11px]", children: [_jsx(Kbd, { children: "\u2318" }), _jsx(Kbd, { children: "\u21B5" })] })), _jsx(Button, { type: "submit", size: "sm", disabled: pending || type.length === 0, children: "Send signal" })] })] }));
62
63
  }
63
64
  /** Run-scoped actions that live beside the run switcher. */
64
- export function RunControls({ runId }) {
65
+ export function RunControls({ runId, onForked, }) {
65
66
  const { client } = useDebugger();
66
- const { rootThreadId, status } = useRunSummary(useRunStore(runId));
67
- const { run, pending, error } = useRunAction();
68
- const finished = status === 'completed' || status === 'failed' || status === 'cancelled';
69
- return (_jsxs("div", { className: "flex items-center gap-0.5", children: [error ? (_jsx("span", { className: "text-status-failed mr-2 max-w-60 truncate text-[11px]", title: error, children: error })) : null, _jsxs(Button, { variant: "ghost", size: "sm", disabled: pending, onClick: () => run(() => client.wake(runId)), children: [_jsx(AlarmClock, {}), "Wake"] }), _jsxs(Button, { variant: "destructive", size: "sm", disabled: pending || finished, onClick: () => run(() => client.signal(runId, [
70
- { type: CANCEL_TYPE, payload: { threadId: rootThreadId ?? '' } },
71
- ])), children: [_jsx(CircleStop, {}), "Cancel"] }), _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "data-[state=open]:bg-accent data-[state=open]:text-foreground", children: [_jsx(Radio, {}), "Signal"] }) }), _jsx(PopoverContent, { align: "end", className: "w-80", children: _jsx(SignalForm, { runId: runId, rootThreadId: rootThreadId }) })] })] }));
67
+ const { rootThreadId, finished, resumable } = useRunSummary(useRunStore(runId));
68
+ // A wake holds its request open until the run parks, so Cancel needs its own pending state.
69
+ const waking = useRunAction();
70
+ const cancelling = useRunAction();
71
+ const forking = useRunAction();
72
+ const error = forking.error ?? cancelling.error ?? waking.error;
73
+ const [notice, setNotice] = useState(null);
74
+ useEffect(() => {
75
+ if (!notice) {
76
+ return undefined;
77
+ }
78
+ const clear = Effect.runFork(Effect.sleep(NOTICE_MS).pipe(Effect.andThen(Effect.sync(() => setNotice(null)))));
79
+ return () => {
80
+ Effect.runFork(Fiber.interrupt(clear));
81
+ };
82
+ }, [notice]);
83
+ function wake() {
84
+ setNotice(null);
85
+ cancelling.setError(null);
86
+ waking.run(() => client.wake(runId), ({ appended }) => setNotice(appended === 0
87
+ ? 'Nothing pending'
88
+ : `Woke: ${appended} new event${appended === 1 ? '' : 's'}`));
89
+ }
90
+ return (_jsxs("div", { className: "flex items-center gap-0.5", children: [error ? (_jsx("span", { className: "text-status-failed mr-2 max-w-60 truncate text-[11px]", title: error, children: error })) : notice ? (_jsx("span", { role: "status", className: "text-muted-foreground mr-2 text-[11px]", children: notice })) : null, _jsxs(Button, { variant: "ghost", size: "sm", disabled: waking.pending, title: "Process pending work: due timers, retries, and effects stalled by a restart", onClick: wake, children: [_jsx(AlarmClock, {}), "Wake"] }), resumable ? (_jsxs(Button, { variant: "ghost", size: "sm", disabled: forking.pending, title: "Start a new run that picks up where this one stopped", onClick: () => {
91
+ setNotice(null);
92
+ waking.setError(null);
93
+ forking.run(() => client.fork(runId), (forked) => onForked(forked.runId));
94
+ }, children: [_jsx(GitFork, {}), "Continue"] })) : (_jsxs(Button, { variant: "destructive", size: "sm", disabled: cancelling.pending || finished, onClick: () => {
95
+ setNotice(null);
96
+ waking.setError(null);
97
+ cancelling.run(() => client.cancel(runId));
98
+ }, children: [_jsx(CircleStop, {}), "Cancel"] })), _jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "data-[state=open]:bg-accent data-[state=open]:text-foreground", children: [_jsx(Radio, {}), "Signal"] }) }), _jsx(PopoverContent, { align: "end", className: "w-80", children: _jsx(SignalForm, { runId: runId, rootThreadId: rootThreadId }) })] })] }));
72
99
  }
@@ -43,9 +43,7 @@ function indexRuns(runs) {
43
43
  .join(' ')
44
44
  .toLowerCase(),
45
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);
46
+ return indexed.toSorted((left, right) => right.createdAt - left.createdAt);
49
47
  }
50
48
  function matches(entry, terms) {
51
49
  return terms.every((term) => entry.haystack.includes(term));
@@ -6,39 +6,6 @@ export declare const evaluateEffect: NoInfer<import("@swirls/looms/core").Effect
6
6
  readonly definitionName?: string | undefined;
7
7
  readonly input?: Schema.Json | undefined;
8
8
  }, EvaluatorDefinitionsTag | EvaluatorTag, (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
9
- readonly type: "evaluator.evaluated";
10
- readonly payload: {
11
- readonly evaluationId: string;
12
- readonly definitionName?: string | undefined;
13
- readonly answers: {
14
- readonly [x: string]: {
15
- readonly type: "boolean";
16
- readonly value: boolean;
17
- readonly probability: number;
18
- } | {
19
- readonly type: "choice";
20
- readonly value: string;
21
- } | {
22
- readonly type: "score";
23
- readonly value: number;
24
- };
25
- };
26
- readonly route: string;
27
- readonly reason: "model" | "skipped" | "unavailable" | "uncertain";
28
- readonly usage?: {
29
- readonly input: number;
30
- readonly output: number;
31
- } | undefined;
32
- };
33
- }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
34
- readonly type: "evaluator.requested";
35
- readonly payload: {
36
- readonly evaluationId: string;
37
- readonly definitionName?: string | undefined;
38
- readonly state: string;
39
- readonly model?: string | undefined;
40
- };
41
- }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
42
9
  readonly type: "runtime.effect.ambiguous";
43
10
  readonly payload: {
44
11
  readonly effectId: string;
@@ -120,6 +87,11 @@ export declare const evaluateEffect: NoInfer<import("@swirls/looms/core").Effect
120
87
  readonly attempt: number;
121
88
  readonly deadlineAt: number | null;
122
89
  };
90
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
91
+ readonly type: "runtime.fork.elided";
92
+ readonly payload: {
93
+ readonly type: string;
94
+ };
123
95
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
124
96
  readonly type: "runtime.run.cancelled";
125
97
  readonly payload: {
@@ -131,6 +103,12 @@ export declare const evaluateEffect: NoInfer<import("@swirls/looms/core").Effect
131
103
  readonly output: Schema.Json;
132
104
  readonly error: string | null;
133
105
  };
106
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
107
+ readonly type: "runtime.run.forked";
108
+ readonly payload: {
109
+ readonly sourceRunId: string;
110
+ readonly sourceSeq: number;
111
+ };
134
112
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
135
113
  readonly type: "runtime.run.started";
136
114
  readonly payload: {
@@ -163,6 +141,7 @@ export declare const evaluateEffect: NoInfer<import("@swirls/looms/core").Effect
163
141
  readonly payload: {
164
142
  readonly threadId: string;
165
143
  readonly reason?: string | undefined;
144
+ readonly resumeSeq?: number | undefined;
166
145
  };
167
146
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
168
147
  readonly type: "runtime.thread.completed";
@@ -223,4 +202,37 @@ export declare const evaluateEffect: NoInfer<import("@swirls/looms/core").Effect
223
202
  readonly payload: Schema.Json;
224
203
  };
225
204
  };
205
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
206
+ readonly type: "evaluator.evaluated";
207
+ readonly payload: {
208
+ readonly evaluationId: string;
209
+ readonly definitionName?: string | undefined;
210
+ readonly answers: {
211
+ readonly [x: string]: {
212
+ readonly type: "boolean";
213
+ readonly value: boolean;
214
+ readonly probability: number;
215
+ } | {
216
+ readonly type: "choice";
217
+ readonly value: string;
218
+ } | {
219
+ readonly type: "score";
220
+ readonly value: number;
221
+ };
222
+ };
223
+ readonly route: string;
224
+ readonly reason: "model" | "skipped" | "unavailable" | "uncertain";
225
+ readonly usage?: {
226
+ readonly input: number;
227
+ readonly output: number;
228
+ } | undefined;
229
+ };
230
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
231
+ readonly type: "evaluator.requested";
232
+ readonly payload: {
233
+ readonly evaluationId: string;
234
+ readonly definitionName?: string | undefined;
235
+ readonly state: string;
236
+ readonly model?: string | undefined;
237
+ };
226
238
  })>>;
@@ -41,39 +41,6 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
41
41
  readonly definitionName?: string | undefined;
42
42
  readonly input?: import("effect/Schema").Json | undefined;
43
43
  }, import("./definitions-store").EvaluatorDefinitionsTag | EvaluatorTag, (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
44
- readonly type: "evaluator.evaluated";
45
- readonly payload: {
46
- readonly evaluationId: string;
47
- readonly definitionName?: string | undefined;
48
- readonly answers: {
49
- readonly [x: string]: {
50
- readonly type: "boolean";
51
- readonly value: boolean;
52
- readonly probability: number;
53
- } | {
54
- readonly type: "choice";
55
- readonly value: string;
56
- } | {
57
- readonly type: "score";
58
- readonly value: number;
59
- };
60
- };
61
- readonly route: string;
62
- readonly reason: "model" | "skipped" | "unavailable" | "uncertain";
63
- readonly usage?: {
64
- readonly input: number;
65
- readonly output: number;
66
- } | undefined;
67
- };
68
- }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
69
- readonly type: "evaluator.requested";
70
- readonly payload: {
71
- readonly evaluationId: string;
72
- readonly definitionName?: string | undefined;
73
- readonly state: string;
74
- readonly model?: string | undefined;
75
- };
76
- }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
77
44
  readonly type: "runtime.effect.ambiguous";
78
45
  readonly payload: {
79
46
  readonly effectId: string;
@@ -155,6 +122,11 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
155
122
  readonly attempt: number;
156
123
  readonly deadlineAt: number | null;
157
124
  };
125
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
126
+ readonly type: "runtime.fork.elided";
127
+ readonly payload: {
128
+ readonly type: string;
129
+ };
158
130
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
159
131
  readonly type: "runtime.run.cancelled";
160
132
  readonly payload: {
@@ -166,6 +138,12 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
166
138
  readonly output: import("effect/Schema").Json;
167
139
  readonly error: string | null;
168
140
  };
141
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
142
+ readonly type: "runtime.run.forked";
143
+ readonly payload: {
144
+ readonly sourceRunId: string;
145
+ readonly sourceSeq: number;
146
+ };
169
147
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
170
148
  readonly type: "runtime.run.started";
171
149
  readonly payload: {
@@ -198,6 +176,7 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
198
176
  readonly payload: {
199
177
  readonly threadId: string;
200
178
  readonly reason?: string | undefined;
179
+ readonly resumeSeq?: number | undefined;
201
180
  };
202
181
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
203
182
  readonly type: "runtime.thread.completed";
@@ -258,6 +237,39 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
258
237
  readonly payload: import("effect/Schema").Json;
259
238
  };
260
239
  };
240
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
241
+ readonly type: "evaluator.evaluated";
242
+ readonly payload: {
243
+ readonly evaluationId: string;
244
+ readonly definitionName?: string | undefined;
245
+ readonly answers: {
246
+ readonly [x: string]: {
247
+ readonly type: "boolean";
248
+ readonly value: boolean;
249
+ readonly probability: number;
250
+ } | {
251
+ readonly type: "choice";
252
+ readonly value: string;
253
+ } | {
254
+ readonly type: "score";
255
+ readonly value: number;
256
+ };
257
+ };
258
+ readonly route: string;
259
+ readonly reason: "model" | "skipped" | "unavailable" | "uncertain";
260
+ readonly usage?: {
261
+ readonly input: number;
262
+ readonly output: number;
263
+ } | undefined;
264
+ };
265
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
266
+ readonly type: "evaluator.requested";
267
+ readonly payload: {
268
+ readonly evaluationId: string;
269
+ readonly definitionName?: string | undefined;
270
+ readonly state: string;
271
+ readonly model?: string | undefined;
272
+ };
261
273
  })>;
262
274
  }, {
263
275
  evaluator: import("@swirls/looms/core").ThreadDefinition<{
@@ -324,39 +336,6 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
324
336
  readonly definitionName?: string | undefined;
325
337
  readonly input?: import("effect/Schema").Json | undefined;
326
338
  }, import("./definitions-store").EvaluatorDefinitionsTag | EvaluatorTag, (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
327
- readonly type: "evaluator.evaluated";
328
- readonly payload: {
329
- readonly evaluationId: string;
330
- readonly definitionName?: string | undefined;
331
- readonly answers: {
332
- readonly [x: string]: {
333
- readonly type: "boolean";
334
- readonly value: boolean;
335
- readonly probability: number;
336
- } | {
337
- readonly type: "choice";
338
- readonly value: string;
339
- } | {
340
- readonly type: "score";
341
- readonly value: number;
342
- };
343
- };
344
- readonly route: string;
345
- readonly reason: "model" | "skipped" | "unavailable" | "uncertain";
346
- readonly usage?: {
347
- readonly input: number;
348
- readonly output: number;
349
- } | undefined;
350
- };
351
- }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
352
- readonly type: "evaluator.requested";
353
- readonly payload: {
354
- readonly evaluationId: string;
355
- readonly definitionName?: string | undefined;
356
- readonly state: string;
357
- readonly model?: string | undefined;
358
- };
359
- }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
360
339
  readonly type: "runtime.effect.ambiguous";
361
340
  readonly payload: {
362
341
  readonly effectId: string;
@@ -438,6 +417,11 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
438
417
  readonly attempt: number;
439
418
  readonly deadlineAt: number | null;
440
419
  };
420
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
421
+ readonly type: "runtime.fork.elided";
422
+ readonly payload: {
423
+ readonly type: string;
424
+ };
441
425
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
442
426
  readonly type: "runtime.run.cancelled";
443
427
  readonly payload: {
@@ -449,6 +433,12 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
449
433
  readonly output: import("effect/Schema").Json;
450
434
  readonly error: string | null;
451
435
  };
436
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
437
+ readonly type: "runtime.run.forked";
438
+ readonly payload: {
439
+ readonly sourceRunId: string;
440
+ readonly sourceSeq: number;
441
+ };
452
442
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
453
443
  readonly type: "runtime.run.started";
454
444
  readonly payload: {
@@ -481,6 +471,7 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
481
471
  readonly payload: {
482
472
  readonly threadId: string;
483
473
  readonly reason?: string | undefined;
474
+ readonly resumeSeq?: number | undefined;
484
475
  };
485
476
  }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
486
477
  readonly type: "runtime.thread.completed";
@@ -541,6 +532,39 @@ export declare function evaluator(options?: EvaluatorModuleOptions): import("@sw
541
532
  readonly payload: import("effect/Schema").Json;
542
533
  };
543
534
  };
535
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
536
+ readonly type: "evaluator.evaluated";
537
+ readonly payload: {
538
+ readonly evaluationId: string;
539
+ readonly definitionName?: string | undefined;
540
+ readonly answers: {
541
+ readonly [x: string]: {
542
+ readonly type: "boolean";
543
+ readonly value: boolean;
544
+ readonly probability: number;
545
+ } | {
546
+ readonly type: "choice";
547
+ readonly value: string;
548
+ } | {
549
+ readonly type: "score";
550
+ readonly value: number;
551
+ };
552
+ };
553
+ readonly route: string;
554
+ readonly reason: "model" | "skipped" | "unavailable" | "uncertain";
555
+ readonly usage?: {
556
+ readonly input: number;
557
+ readonly output: number;
558
+ } | undefined;
559
+ };
560
+ }) | (Omit<import("@swirls/looms/core").EventInput, "payload" | "type"> & {
561
+ readonly type: "evaluator.requested";
562
+ readonly payload: {
563
+ readonly evaluationId: string;
564
+ readonly definitionName?: string | undefined;
565
+ readonly state: string;
566
+ readonly model?: string | undefined;
567
+ };
544
568
  })>;
545
569
  };
546
570
  readonly threads: {