@swirls/looms 0.0.1 → 0.1.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 (257) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +2 -0
  3. package/dist/actor/cell.d.ts +9 -4
  4. package/dist/actor/cell.js +7 -11
  5. package/dist/actor/host.d.ts +7 -3
  6. package/dist/actor/host.js +55 -32
  7. package/dist/actor/index.d.ts +1 -1
  8. package/dist/actor/index.js +1 -1
  9. package/dist/actor/routing.d.ts +12 -1
  10. package/dist/actor/routing.js +57 -51
  11. package/dist/agent/agent-session.d.ts +5 -0
  12. package/dist/agent/agent-session.js +270 -0
  13. package/dist/agent/definitions-store.d.ts +2 -3
  14. package/dist/agent/definitions-store.js +2 -4
  15. package/dist/agent/definitions.d.ts +46 -20
  16. package/dist/agent/definitions.js +36 -8
  17. package/dist/agent/effects.d.ts +359 -2
  18. package/dist/agent/effects.js +60 -34
  19. package/dist/agent/events.d.ts +175 -19
  20. package/dist/agent/events.js +45 -10
  21. package/dist/agent/index.d.ts +1 -0
  22. package/dist/agent/index.js +1 -0
  23. package/dist/agent/llm.d.ts +11 -2
  24. package/dist/agent/llm.js +13 -3
  25. package/dist/agent/module.d.ts +812 -78
  26. package/dist/agent/module.js +16 -4
  27. package/dist/agent/scope.d.ts +174 -18
  28. package/dist/agent/signals.d.ts +24 -1
  29. package/dist/agent/signals.js +77 -0
  30. package/dist/agent/threads.d.ts +3 -33
  31. package/dist/agent/threads.js +20 -25
  32. package/dist/agent/tool-schema.js +0 -1
  33. package/dist/agent/types.d.ts +49 -4
  34. package/dist/agent/types.js +5 -5
  35. package/dist/ai-vercel/index.d.ts +1 -0
  36. package/dist/ai-vercel/index.js +1 -0
  37. package/dist/ai-vercel/messages.js +22 -7
  38. package/dist/ai-vercel/vercel-evaluator.d.ts +23 -0
  39. package/dist/ai-vercel/vercel-evaluator.js +68 -0
  40. package/dist/ai-vercel/vercel-llm.js +64 -44
  41. package/dist/approval/effects.d.ts +89 -0
  42. package/dist/approval/module.d.ts +179 -29
  43. package/dist/approval/projections.d.ts +0 -14
  44. package/dist/approval/projections.js +20 -3
  45. package/dist/approval/signals.d.ts +2 -0
  46. package/dist/approval/signals.js +18 -2
  47. package/dist/cli/cli.js +2 -0
  48. package/dist/cli/commands.js +12 -16
  49. package/dist/client/client.d.ts +1195 -18
  50. package/dist/client/client.js +118 -120
  51. package/dist/client/index.d.ts +1 -1
  52. package/dist/client/index.js +1 -1
  53. package/dist/client/sse.d.ts +13 -0
  54. package/dist/client/sse.js +38 -44
  55. package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
  56. package/dist/cloudflare/alarm-scheduler.js +34 -7
  57. package/dist/cloudflare/durable-object.d.ts +10 -2
  58. package/dist/cloudflare/durable-object.js +59 -28
  59. package/dist/cloudflare/event-store.d.ts +7 -2
  60. package/dist/cloudflare/event-store.js +8 -7
  61. package/dist/cloudflare/index.d.ts +2 -2
  62. package/dist/cloudflare/index.js +2 -2
  63. package/dist/cloudflare/router.js +13 -14
  64. package/dist/core/catalog.d.ts +3 -3
  65. package/dist/core/catalog.js +2 -5
  66. package/dist/core/coalesce.js +5 -3
  67. package/dist/core/compose.d.ts +2 -1
  68. package/dist/core/compose.js +18 -2
  69. package/dist/core/definition-store.d.ts +11 -0
  70. package/dist/core/definition-store.js +6 -0
  71. package/dist/core/effects.d.ts +127 -15
  72. package/dist/core/effects.js +89 -17
  73. package/dist/core/envelope.d.ts +27 -7
  74. package/dist/core/envelope.js +20 -6
  75. package/dist/core/event-codec.d.ts +4 -2
  76. package/dist/core/event-codec.js +10 -5
  77. package/dist/core/event-index.js +1 -3
  78. package/dist/core/fold.d.ts +3 -0
  79. package/dist/core/fold.js +171 -24
  80. package/dist/core/ids.d.ts +5 -0
  81. package/dist/core/ids.js +35 -2
  82. package/dist/core/index.d.ts +5 -1
  83. package/dist/core/index.js +5 -1
  84. package/dist/core/kind.d.ts +22 -0
  85. package/dist/core/kind.js +17 -0
  86. package/dist/core/limits.d.ts +34 -0
  87. package/dist/core/limits.js +46 -0
  88. package/dist/core/match.d.ts +1 -0
  89. package/dist/core/match.js +10 -1
  90. package/dist/core/module-scope.d.ts +22 -20
  91. package/dist/core/module-scope.js +38 -28
  92. package/dist/core/module.d.ts +16 -5
  93. package/dist/core/module.js +4 -0
  94. package/dist/core/projection.d.ts +13 -13
  95. package/dist/core/projection.js +9 -5
  96. package/dist/core/protocol.d.ts +68 -11
  97. package/dist/core/protocol.js +82 -11
  98. package/dist/core/replay.d.ts +312 -3
  99. package/dist/core/replay.js +11 -1
  100. package/dist/core/schema.d.ts +8 -19
  101. package/dist/core/schema.js +25 -80
  102. package/dist/core/serialize.js +2 -4
  103. package/dist/core/snapshot-store.d.ts +2 -1
  104. package/dist/core/snapshot-store.js +8 -2
  105. package/dist/core/snapshots.d.ts +5 -1
  106. package/dist/core/snapshots.js +10 -15
  107. package/dist/core/sqlite-store.js +56 -11
  108. package/dist/core/state.d.ts +142 -4
  109. package/dist/core/state.js +78 -0
  110. package/dist/core/store.d.ts +38 -3
  111. package/dist/core/store.js +77 -22
  112. package/dist/core/thread.d.ts +10 -9
  113. package/dist/core/types.d.ts +5 -8
  114. package/dist/core/types.js +28 -24
  115. package/dist/core/validate-event.js +14 -4
  116. package/dist/core/worker-callback.d.ts +17 -0
  117. package/dist/core/worker-callback.js +1 -0
  118. package/dist/debugger/components/event-stream.js +9 -10
  119. package/dist/debugger/events/protocol-summarize.js +26 -0
  120. package/dist/debugger/lib/event-meta.js +1 -2
  121. package/dist/evaluator/definitions-store.d.ts +11 -0
  122. package/dist/evaluator/definitions-store.js +8 -0
  123. package/dist/evaluator/definitions.d.ts +18 -0
  124. package/dist/evaluator/definitions.js +4 -0
  125. package/dist/evaluator/effects.d.ts +226 -0
  126. package/dist/evaluator/effects.js +132 -0
  127. package/dist/evaluator/evaluator.d.ts +24 -0
  128. package/dist/evaluator/evaluator.js +67 -0
  129. package/dist/evaluator/events.d.ts +59 -0
  130. package/dist/evaluator/events.js +21 -0
  131. package/dist/evaluator/index.d.ts +11 -0
  132. package/dist/evaluator/index.js +11 -0
  133. package/dist/evaluator/module.d.ts +606 -0
  134. package/dist/evaluator/module.js +25 -0
  135. package/dist/evaluator/projections.d.ts +50 -0
  136. package/dist/evaluator/projections.js +20 -0
  137. package/dist/evaluator/scope.d.ts +59 -0
  138. package/dist/evaluator/scope.js +7 -0
  139. package/dist/evaluator/signals.d.ts +3 -0
  140. package/dist/evaluator/signals.js +17 -0
  141. package/dist/evaluator/threads.d.ts +62 -0
  142. package/dist/evaluator/threads.js +117 -0
  143. package/dist/evaluator/types.d.ts +125 -0
  144. package/dist/evaluator/types.js +51 -0
  145. package/dist/projectors/delivery.d.ts +83 -0
  146. package/dist/projectors/delivery.js +182 -0
  147. package/dist/projectors/index-model.d.ts +1 -1
  148. package/dist/projectors/index-model.js +4 -6
  149. package/dist/projectors/index.d.ts +2 -0
  150. package/dist/projectors/index.js +2 -0
  151. package/dist/projectors/memory.d.ts +1 -1
  152. package/dist/projectors/memory.js +10 -8
  153. package/dist/projectors/postgres.js +44 -58
  154. package/dist/projectors/projector.d.ts +17 -0
  155. package/dist/projectors/projector.js +26 -1
  156. package/dist/projectors/sqlite-delivery.d.ts +4 -0
  157. package/dist/projectors/sqlite-delivery.js +147 -0
  158. package/dist/projectors/sqlite.js +23 -23
  159. package/dist/projectors/with-projectors.d.ts +7 -2
  160. package/dist/projectors/with-projectors.js +27 -28
  161. package/dist/react/notify.js +11 -8
  162. package/dist/react/react.d.ts +2 -3
  163. package/dist/react/react.js +14 -30
  164. package/dist/react/sse.d.ts +1 -1
  165. package/dist/react/sse.js +1 -1
  166. package/dist/react/store.d.ts +2 -2
  167. package/dist/react/store.js +109 -119
  168. package/dist/runtime/cancellation.d.ts +16 -0
  169. package/dist/runtime/cancellation.js +129 -0
  170. package/dist/runtime/deadline-transition-planner.d.ts +31 -0
  171. package/dist/runtime/deadline-transition-planner.js +139 -0
  172. package/dist/runtime/dispatch-effect.d.ts +3 -2
  173. package/dist/runtime/dispatch-effect.js +7 -13
  174. package/dist/runtime/errors.d.ts +51 -0
  175. package/dist/runtime/errors.js +39 -0
  176. package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
  177. package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
  178. package/dist/runtime/index.d.ts +3 -3
  179. package/dist/runtime/index.js +3 -3
  180. package/dist/runtime/ingress.d.ts +15 -0
  181. package/dist/runtime/ingress.js +42 -0
  182. package/dist/runtime/local-effect-executor.d.ts +21 -0
  183. package/dist/runtime/local-effect-executor.js +137 -0
  184. package/dist/runtime/looms.d.ts +22 -4
  185. package/dist/runtime/looms.js +54 -36
  186. package/dist/runtime/observer.d.ts +45 -0
  187. package/dist/runtime/observer.js +11 -0
  188. package/dist/runtime/repository.d.ts +55 -0
  189. package/dist/runtime/repository.js +278 -0
  190. package/dist/runtime/retry-policy.d.ts +16 -0
  191. package/dist/runtime/retry-policy.js +29 -0
  192. package/dist/runtime/run-execution-context.d.ts +30 -0
  193. package/dist/runtime/run-execution-context.js +56 -0
  194. package/dist/runtime/run-start.d.ts +4 -0
  195. package/dist/runtime/run-start.js +44 -0
  196. package/dist/runtime/runtime.d.ts +6 -87
  197. package/dist/runtime/runtime.js +233 -470
  198. package/dist/runtime/server/auth.d.ts +40 -0
  199. package/dist/runtime/server/auth.js +40 -0
  200. package/dist/runtime/server/fetch-handler.d.ts +14 -0
  201. package/dist/runtime/server/fetch-handler.js +51 -0
  202. package/dist/runtime/server/http.d.ts +37 -0
  203. package/dist/runtime/server/http.js +62 -0
  204. package/dist/runtime/server/route-table.d.ts +55 -0
  205. package/dist/runtime/server/route-table.js +74 -0
  206. package/dist/runtime/server/routes/effects.d.ts +3 -0
  207. package/dist/runtime/server/routes/effects.js +59 -0
  208. package/dist/runtime/server/routes/operations.d.ts +3 -0
  209. package/dist/runtime/server/routes/operations.js +14 -0
  210. package/dist/runtime/server/routes/runs.d.ts +3 -0
  211. package/dist/runtime/server/routes/runs.js +192 -0
  212. package/dist/runtime/server.d.ts +2 -8
  213. package/dist/runtime/server.js +4 -211
  214. package/dist/runtime/sse.d.ts +2 -1
  215. package/dist/runtime/sse.js +24 -17
  216. package/dist/runtime/types.d.ts +115 -0
  217. package/dist/runtime/types.js +1 -0
  218. package/dist/runtime/wake-engine.d.ts +20 -0
  219. package/dist/runtime/wake-engine.js +311 -0
  220. package/dist/runtime/wake-scheduler.d.ts +3 -4
  221. package/dist/runtime/wake-scheduler.js +25 -31
  222. package/dist/runtime/worker-callbacks.d.ts +16 -0
  223. package/dist/runtime/worker-callbacks.js +167 -0
  224. package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
  225. package/dist/runtime/worker-effect-dispatch.js +126 -0
  226. package/dist/s2/config.d.ts +1 -1
  227. package/dist/s2/config.js +1 -1
  228. package/dist/s2/lite.js +70 -82
  229. package/dist/s2/pagination.d.ts +15 -0
  230. package/dist/s2/pagination.js +36 -0
  231. package/dist/s2/projector.js +6 -5
  232. package/dist/s2/snapshot-store.js +148 -163
  233. package/dist/s2/store.d.ts +3 -21
  234. package/dist/s2/store.js +71 -165
  235. package/dist/testing/index.d.ts +15 -0
  236. package/dist/testing/index.js +53 -16
  237. package/dist/workflow/definitions-store.d.ts +2 -3
  238. package/dist/workflow/definitions-store.js +2 -2
  239. package/dist/workflow/definitions.d.ts +78 -22
  240. package/dist/workflow/definitions.js +80 -14
  241. package/dist/workflow/effects.d.ts +359 -13
  242. package/dist/workflow/effects.js +214 -115
  243. package/dist/workflow/events.d.ts +149 -5
  244. package/dist/workflow/events.js +27 -5
  245. package/dist/workflow/graph.d.ts +56 -0
  246. package/dist/workflow/graph.js +207 -0
  247. package/dist/workflow/index.d.ts +1 -0
  248. package/dist/workflow/index.js +1 -0
  249. package/dist/workflow/module.d.ts +1047 -266
  250. package/dist/workflow/projections.d.ts +14 -12
  251. package/dist/workflow/projections.js +30 -8
  252. package/dist/workflow/scope.d.ts +146 -4
  253. package/dist/workflow/threads.d.ts +383 -155
  254. package/dist/workflow/threads.js +308 -110
  255. package/package.json +8 -2
  256. package/dist/runtime/events-api.d.ts +0 -4
  257. package/dist/runtime/events-api.js +0 -84
package/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # @swirls/looms
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2176eac: Add `defineKind` and `createKind` so custom startable types share the same `{ kind, name }` builder as `defineAgent` and `defineWorkflow`.
8
+ - a879e8b: Add `@swirls/looms/evaluator` so typed questions can score a run and branch a workflow. Pass a model id such as `typesafe-ai/jev`; the thread kind stays `evaluator`.
9
+
10
+ ## 0.0.2
11
+
12
+ ### Patch Changes
13
+
14
+ - 9f3fd62: Include the package changelog in published npm artifacts.
15
+
16
+ ## 0.0.1
17
+
18
+ ### Patch Changes
19
+
20
+ - e83496a: **BREAKING**: Modules own their definitions. Pass `workflow({ definitions: [checkout] })` / `agent({ definitions: [...] })` instead of a top-level `definitions` list on `createLooms`, `createRuntime`, actor cells/hosts, or Durable Objects. Omitting `modules` installs none (no longer defaults to agent/workflow/approval).
21
+
22
+ **BREAKING**: `defineModule(options, setup)` returns the finished module. Returned `effects` / `threads` / `projections` / `definitions` are registered and exposed (e.g. `payments.effects.charge`). The old scope-only + `.build()` form is replaced by `createModuleScope` for multi-file authoring.
23
+
24
+ Compose rejects duplicate definitions and definitions whose owning module does not implement the thread kind. Starting or spawning an unsupported kind fails before run events are written; folding an unsupported kind marks the thread failed instead of inventing empty state.
25
+
26
+ - ee5f92f: Initial 0.0.1 OSS release: durable Effect runtime for agents and workflows, S2 adapter, HTTP client, React bindings, CLI, optional projectors, and demo app.
27
+ - 4c8410e: Looms DX overhaul:
28
+
29
+ - Add Standard Schema `input` typing and runtime validation for `defineAgent`, `defineWorkflow`, and `defineTool`.
30
+ - Provide Promise-based `createLooms()` facade with lazy init, typed start overloads, and universal `fetch(req) -> Response | null`.
31
+ - Add Looms-native event streaming directly against EventStore and `/runs/:id/events` (eliminating the dual-log S2 bridge).
32
+ - Add `s2Lite()` / `startS2Lite()` auto-starting dev process manager.
33
+ - Typed client generics for `createLoomsClient<typeof definitions>()`.
34
+ - Single server setup for dev and prod in demo app with no `effect` dependency.
35
+
36
+ - 4c8410e: Introduce the generic `@swirls/looms/projectors` module. Implement `Projector` or use `memory()`,
37
+ `postgres({ url })`, and `sqlite({ path })`. `createLooms({ projectors })` initializes projectors,
38
+ wraps append, and disposes them. S2 helpers now take an explicit environment bag; `serveHttp` no
39
+ longer reads `PORT`.
40
+ - f1cceb0: Optimize React SDK and Debugger for high-velocity event streaming:
41
+
42
+ - **BREAKING (`@swirls/looms/react`)**: `useRunStore(runId, options)` is now a non-subscribing handle. Subscribe via fine-grained hooks (`useProjection`, `useRunEvents`, `useRunSummary`, `useThreadTree`, `useEventCounts`, `useEventBySeq`, `useLatestEvent`, `useEventFold`, `useRunSelector`). Removed `createLoomsHooks` and `pollIntervalMs` — use `LoomsRegister` or explicit `EventsOf<…>` generics instead.
43
+ - **BREAKING (`@swirls/looms/react`)**: Materialized tables no longer include `events_log`; the event log lives only in `store.events()` / `EventIndex`. `StoreListener` is now `() => void` (re-read via store APIs).
44
+ - **Incremental Projection Caching (`@swirls/looms/react`)**: `store.project(def)` and `useProjection` fold newly arrived events from cached state (O(batch)), preserving references when unchanged.
45
+ - **Adaptive Coalescing (`@swirls/looms/react`)**: Default `coalesce: 'adaptive'` (macrotask when quiet, animation-frame under burst); also `frame`, `immediate`, `{ delayMs }`. Notify scheduling lives in `createNotifyScheduler`. Added `store.flush()`.
46
+ - **Shared `EventIndex` (`@swirls/looms/core`)**: Append-only thread/seq/count/latest indexes with lazy snapshots; used by the React client store and the debugger.
47
+ - **Debugger (`@swirls/looms/debugger`)**: Virtualized `EventStream` with stable default catalog, catalog-aware meta cache, and `useMemo` visibility filtering.
48
+
49
+ - e398380: Publish the React integration at `@swirls/looms/react` and drop the former LiveStore protocol names.
50
+
51
+ - Package and React provider are now `@swirls/looms/react` / `LoomsProvider`.
52
+ - Live event HTTP is `GET /runs/:id/events` with SSE content negotiation. Wire batches are Looms event envelopes.
53
+ - `LiveStoreGlobalEncoded` is now `EncodedLoomsEvent`.
package/README.md CHANGED
@@ -7,3 +7,5 @@ bun add @swirls/looms
7
7
  ```
8
8
 
9
9
  Import the core runtime from `@swirls/looms`, or use explicit modules such as `@swirls/looms/agent` and `@swirls/looms/react`. See the [repository README](https://github.com/ByteSliceHQ/looms#readme) for the full guide.
10
+
11
+ Release notes are available in [`CHANGELOG.md`](./CHANGELOG.md).
@@ -1,5 +1,5 @@
1
- import { type AnyRuntimeModule, type EventStore, type RunState, type SnapshotStore } from '@swirls/looms/core';
2
- import { type LoomsRuntime, type WakeScheduler } from '@swirls/looms/runtime';
1
+ import { type AnyRuntimeModule, type EventStore, type EventStoreTrimCoverage, type RunState, type SnapshotStore } from '@swirls/looms/core';
2
+ import { type Authorize, type LoomsRuntime, type WakeScheduler } from '@swirls/looms/runtime';
3
3
  export interface ActorCellOptions<TModules extends readonly AnyRuntimeModule[] = readonly AnyRuntimeModule[]> {
4
4
  readonly runId: string;
5
5
  readonly store: EventStore;
@@ -7,10 +7,14 @@ export interface ActorCellOptions<TModules extends readonly AnyRuntimeModule[] =
7
7
  readonly modules: TModules;
8
8
  readonly snapshotEvery?: number;
9
9
  readonly maxWakeIterations?: number;
10
+ readonly maxPendingRunOperations?: number;
10
11
  readonly snapshotStore?: SnapshotStore;
11
12
  readonly trimAfterSnapshot?: {
12
- keepSnapshots: number;
13
+ readonly keepSnapshots: number;
14
+ readonly coverage?: (runId: string) => EventStoreTrimCoverage | Promise<EventStoreTrimCoverage>;
13
15
  };
16
+ /** Authorizes the cell's HTTP API; see `createFetchHandler`. */
17
+ readonly authorize?: Authorize;
14
18
  }
15
19
  export interface ActorCell<TModules extends readonly AnyRuntimeModule[] = readonly AnyRuntimeModule[]> {
16
20
  readonly runId: string;
@@ -18,7 +22,8 @@ export interface ActorCell<TModules extends readonly AnyRuntimeModule[] = readon
18
22
  readonly store: EventStore;
19
23
  fetch(req: Request): Promise<Response>;
20
24
  wake(): Promise<RunState>;
21
- dispose(): void;
25
+ recoverWake(): Promise<number>;
26
+ dispose(): Promise<void>;
22
27
  }
23
28
  /**
24
29
  * Creates an in-process actor cell that owns a single `runId`.
@@ -14,28 +14,24 @@ export function createActorCell(options) {
14
14
  scheduler,
15
15
  snapshotEvery: options.snapshotEvery,
16
16
  maxWakeIterations: options.maxWakeIterations,
17
+ maxPendingRunOperations: options.maxPendingRunOperations,
17
18
  snapshotStore: options.snapshotStore,
18
19
  trimAfterSnapshot: options.trimAfterSnapshot,
19
20
  });
20
- const fetchHandler = createFetchHandler({ runtime, store });
21
+ const fetchHandler = createFetchHandler({ runtime, store, authorize: options.authorize });
21
22
  return {
22
23
  runId,
23
24
  runtime,
24
25
  store,
25
- fetch: async (req) => {
26
+ fetch: (req) => {
26
27
  const targetRunId = runIdFromRequest(req);
27
28
  if (targetRunId && targetRunId !== runId) {
28
- return new Response('Not Found', { status: 404 });
29
+ return Promise.resolve(new Response('Not Found', { status: 404 }));
29
30
  }
30
- const res = await fetchHandler(req);
31
- if (res === null) {
32
- return new Response('Not Found', { status: 404 });
33
- }
34
- return res;
31
+ return fetchHandler(req).then((res) => res ?? new Response('Not Found', { status: 404 }));
35
32
  },
36
33
  wake: () => Effect.runPromise(Effect.provideService(runtime.wake(runId), EventStoreTag, store)),
37
- dispose: () => {
38
- runtime.dispose();
39
- },
34
+ recoverWake: () => Effect.runPromise(Effect.provideService(runtime.rescanTimers, EventStoreTag, store)),
35
+ dispose: () => Effect.runPromise(runtime.dispose),
40
36
  };
41
37
  }
@@ -1,4 +1,5 @@
1
- import type { AnyRuntimeModule, EventStore, SnapshotStore } from '@swirls/looms/core';
1
+ import type { AnyRuntimeModule, EventStore, EventStoreTrimCoverage, SnapshotStore } from '@swirls/looms/core';
2
+ import { type Authorize } from '@swirls/looms/runtime';
2
3
  import { type ActorCell } from './cell';
3
4
  export interface LocalActorHostOptions<TModules extends readonly AnyRuntimeModule[] = readonly AnyRuntimeModule[]> {
4
5
  readonly createStore: (runId: string) => EventStore | Promise<EventStore>;
@@ -7,18 +8,21 @@ export interface LocalActorHostOptions<TModules extends readonly AnyRuntimeModul
7
8
  readonly snapshotEvery?: number;
8
9
  readonly maxWakeIterations?: number;
9
10
  readonly trimAfterSnapshot?: {
10
- keepSnapshots: number;
11
+ readonly keepSnapshots: number;
12
+ readonly coverage?: (runId: string) => EventStoreTrimCoverage | Promise<EventStoreTrimCoverage>;
11
13
  };
12
14
  /**
13
15
  * After creating a cell, rescan its store for pending timers (process restart).
14
16
  * Default true. No-op for empty in-memory stores.
15
17
  */
16
18
  readonly rescanTimers?: boolean;
19
+ /** Authorizes each cell's HTTP API; see `createFetchHandler`. */
20
+ readonly authorize?: Authorize;
17
21
  }
18
22
  export interface LocalActorHost<TModules extends readonly AnyRuntimeModule[] = readonly AnyRuntimeModule[]> {
19
23
  getCell(runId: string): Promise<ActorCell<TModules>>;
20
24
  fetch(req: Request): Promise<Response | null>;
21
- dispose(): void;
25
+ dispose(): Promise<void>;
22
26
  }
23
27
  /**
24
28
  * Creates an in-process host that routes requests to distinct actor cells.
@@ -1,8 +1,26 @@
1
- import { Effect } from 'effect';
1
+ import { Data, Effect } from 'effect';
2
2
  import { EventStoreTag } from '@swirls/looms/core';
3
3
  import { createTimeoutScheduler, isLoomsApiPath } from '@swirls/looms/runtime';
4
4
  import { createActorCell } from './cell.js';
5
- import { resolveRunTarget } from './routing.js';
5
+ import { resolveRunTarget, RunTargetError } from './routing.js';
6
+ class ActorWakeError extends Data.TaggedError('ActorWakeError') {
7
+ constructor(cause) {
8
+ super({
9
+ cause,
10
+ message: cause instanceof Error ? cause.message : String(cause),
11
+ });
12
+ this.name = 'ActorWakeError';
13
+ }
14
+ }
15
+ class ActorHostError extends Data.TaggedError('ActorHostError') {
16
+ constructor(cause) {
17
+ super({
18
+ cause,
19
+ message: cause instanceof Error ? cause.message : String(cause),
20
+ });
21
+ this.name = 'ActorHostError';
22
+ }
23
+ }
6
24
  /**
7
25
  * Creates an in-process host that routes requests to distinct actor cells.
8
26
  * Useful for local Bun actor backends, tests, and lightweight simulation.
@@ -15,18 +33,19 @@ export function createLocalActorHost(options) {
15
33
  if (existing) {
16
34
  return existing;
17
35
  }
18
- const cellPromise = (async () => {
19
- const store = await options.createStore(runId);
36
+ const cellPromise = Effect.runPromise(Effect.gen(function* () {
37
+ const store = yield* Effect.tryPromise({
38
+ try: () => Promise.resolve(options.createStore(runId)),
39
+ catch: (cause) => new ActorHostError(cause),
40
+ });
20
41
  // Assign after createActorCell so the scheduler never closes over an unassigned cell.
21
42
  let wake;
22
- const scheduler = createTimeoutScheduler((scheduledRunId) => {
23
- if (scheduledRunId !== runId) {
24
- return;
25
- }
26
- void wake?.().catch((err) => {
27
- console.error('[actor host wake error]', err);
28
- });
29
- });
43
+ const scheduler = createTimeoutScheduler((scheduledRunId) => scheduledRunId === runId && wake
44
+ ? Effect.tryPromise({
45
+ try: wake,
46
+ catch: (cause) => new ActorWakeError(cause),
47
+ }).pipe(Effect.tapError(Effect.logError), Effect.ignore)
48
+ : Effect.void);
30
49
  const cell = createActorCell({
31
50
  runId,
32
51
  store,
@@ -36,47 +55,51 @@ export function createLocalActorHost(options) {
36
55
  snapshotEvery: options.snapshotEvery,
37
56
  maxWakeIterations: options.maxWakeIterations,
38
57
  trimAfterSnapshot: options.trimAfterSnapshot,
58
+ authorize: options.authorize,
39
59
  });
40
60
  wake = () => cell.wake();
41
61
  if (rescanTimers) {
42
- await Effect.runPromise(Effect.provideService(cell.runtime.rescanTimers(), EventStoreTag, store)).catch((err) => {
43
- console.error('[actor host rescan timers error]', err);
44
- });
62
+ yield* Effect.provideService(cell.runtime.rescanTimers, EventStoreTag, store).pipe(Effect.tapError(Effect.logError), Effect.ignore);
45
63
  }
46
64
  return cell;
47
- })();
65
+ }));
48
66
  cells.set(runId, cellPromise);
49
67
  return cellPromise;
50
68
  };
51
69
  return {
52
70
  getCell,
53
- fetch: async (req) => {
71
+ fetch: (req) => {
54
72
  const url = new URL(req.url);
55
73
  if (!isLoomsApiPath(url.pathname)) {
56
- return null;
74
+ return Promise.resolve(null);
57
75
  }
58
76
  if (url.pathname === '/health') {
59
- return Response.json({ ok: true });
77
+ return Promise.resolve(Response.json({ ok: true }));
60
78
  }
61
79
  if (req.method === 'GET' && url.pathname === '/runs') {
62
- return Response.json({
80
+ return Promise.resolve(Response.json({
63
81
  error: 'Global run listing is not supported in the actor host without a global index',
64
- }, { status: 501 });
65
- }
66
- try {
67
- const { runId, request } = await resolveRunTarget(req);
68
- const cell = await getCell(runId);
69
- return cell.fetch(request);
70
- }
71
- catch {
72
- return new Response('Not Found', { status: 404 });
82
+ }, { status: 501 }));
73
83
  }
84
+ return Effect.runPromise(Effect.gen(function* () {
85
+ const { runId, request } = yield* Effect.tryPromise({
86
+ try: () => resolveRunTarget(req),
87
+ catch: (cause) => new ActorHostError(cause),
88
+ });
89
+ const cell = yield* Effect.tryPromise({
90
+ try: () => getCell(runId),
91
+ catch: (cause) => new ActorHostError(cause),
92
+ });
93
+ return yield* Effect.tryPromise({
94
+ try: () => cell.fetch(request),
95
+ catch: (cause) => new ActorHostError(cause),
96
+ });
97
+ }).pipe(Effect.catchIf((error) => error.cause instanceof RunTargetError, () => Effect.succeed(new Response('Not Found', { status: 404 })))));
74
98
  },
75
99
  dispose: () => {
76
- for (const promise of cells.values()) {
77
- void promise.then((cell) => cell.dispose());
78
- }
100
+ const disposals = [...cells.values()].map((promise) => promise.then((cell) => cell.dispose()));
79
101
  cells.clear();
102
+ return Promise.all(disposals).then(() => undefined);
80
103
  },
81
104
  };
82
105
  }
@@ -1,3 +1,3 @@
1
1
  export { createActorCell, type ActorCell, type ActorCellOptions } from './cell';
2
- export { runIdFromRequest, resolveRunTarget, type ResolvedRunTarget } from './routing';
2
+ export { runIdFromRequest, resolveRunTarget, RunTargetError, type ResolvedRunTarget, } from './routing';
3
3
  export { createLocalActorHost, type LocalActorHost, type LocalActorHostOptions } from './host';
@@ -1,3 +1,3 @@
1
1
  export { createActorCell } from './cell.js';
2
- export { runIdFromRequest, resolveRunTarget } from './routing.js';
2
+ export { runIdFromRequest, resolveRunTarget, RunTargetError, } from './routing.js';
3
3
  export { createLocalActorHost } from './host.js';
@@ -1,15 +1,26 @@
1
1
  /**
2
2
  * Extracts the target `runId` from a Looms HTTP request.
3
- * Inspects `x-looms-run-id`, `/runs/:runId/*`, and `/api/events?runId=*`.
3
+ * Inspects `x-looms-run-id` and `/runs/:runId/*`.
4
4
  */
5
5
  export declare function runIdFromRequest(req: Request): string | null;
6
6
  export interface ResolvedRunTarget {
7
7
  readonly runId: string;
8
8
  readonly request: Request;
9
9
  }
10
+ declare const RunTargetError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
11
+ readonly _tag: "RunTargetError";
12
+ } & Readonly<A>;
13
+ export declare class RunTargetError extends RunTargetError_base<{
14
+ readonly method: string;
15
+ readonly path: string;
16
+ readonly message: string;
17
+ }> {
18
+ constructor(method: string, path: string);
19
+ }
10
20
  /**
11
21
  * Resolves the destination `runId` for routing to an actor cell.
12
22
  * For `POST /runs` without a `runId`, creates a fresh `runId`, injects it
13
23
  * into the body, and sets `x-looms-run-id`.
14
24
  */
15
25
  export declare function resolveRunTarget(req: Request): Promise<ResolvedRunTarget>;
26
+ export {};
@@ -1,5 +1,5 @@
1
- import { Option, Schema } from 'effect';
2
- import { createRunId, isJsonObject } from '@swirls/looms/core';
1
+ import { Data, Effect, Option, Schema } from 'effect';
2
+ import { createRunId, isJsonObject, stringifyJson } from '@swirls/looms/core';
3
3
  const RunTargetBodySchema = Schema.Struct({
4
4
  runId: Schema.optional(Schema.NonEmptyString),
5
5
  });
@@ -7,7 +7,7 @@ const decodeRunTargetBody = Schema.decodeUnknownOption(Schema.fromJsonString(Run
7
7
  const decodePayloadRecord = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Record(Schema.String, Schema.Json)));
8
8
  /**
9
9
  * Extracts the target `runId` from a Looms HTTP request.
10
- * Inspects `x-looms-run-id`, `/runs/:runId/*`, and `/api/events?runId=*`.
10
+ * Inspects `x-looms-run-id` and `/runs/:runId/*`.
11
11
  */
12
12
  export function runIdFromRequest(req) {
13
13
  const header = req.headers.get('x-looms-run-id');
@@ -20,61 +20,67 @@ export function runIdFromRequest(req) {
20
20
  if (runsMatch && runsMatch[1]) {
21
21
  return decodeURIComponent(runsMatch[1]);
22
22
  }
23
- if (path.startsWith('/api/events')) {
24
- const runId = url.searchParams.get('runId');
25
- if (runId) {
26
- return runId;
27
- }
28
- }
29
23
  return null;
30
24
  }
25
+ export class RunTargetError extends Data.TaggedError('RunTargetError') {
26
+ constructor(method, path) {
27
+ super({
28
+ method,
29
+ path,
30
+ message: `Cannot resolve runId from request: ${method} ${path}`,
31
+ });
32
+ this.name = 'RunTargetError';
33
+ }
34
+ }
31
35
  /**
32
36
  * Resolves the destination `runId` for routing to an actor cell.
33
37
  * For `POST /runs` without a `runId`, creates a fresh `runId`, injects it
34
38
  * into the body, and sets `x-looms-run-id`.
35
39
  */
36
- export async function resolveRunTarget(req) {
37
- const existing = runIdFromRequest(req);
38
- if (existing) {
39
- if (req.headers.get('x-looms-run-id') === existing) {
40
- return { runId: existing, request: req };
40
+ export function resolveRunTarget(req) {
41
+ return Effect.runPromise(Effect.gen(function* () {
42
+ const existing = runIdFromRequest(req);
43
+ if (existing) {
44
+ if (req.headers.get('x-looms-run-id') === existing) {
45
+ return { runId: existing, request: req };
46
+ }
47
+ const headers = new Headers(req.headers);
48
+ headers.set('x-looms-run-id', existing);
49
+ const request = new Request(req.url, {
50
+ method: req.method,
51
+ headers,
52
+ body: req.body,
53
+ // SAFETY: duplex 'half' is required when streaming request body in standard Fetch.
54
+ // @ts-expect-error duplex is supported in modern runtimes
55
+ duplex: req.body ? 'half' : undefined,
56
+ });
57
+ return { runId: existing, request };
41
58
  }
42
- const headers = new Headers(req.headers);
43
- headers.set('x-looms-run-id', existing);
44
- const request = new Request(req.url, {
45
- method: req.method,
46
- headers,
47
- body: req.body,
48
- // SAFETY: duplex 'half' is required when streaming request body in standard Fetch.
49
- // @ts-expect-error duplex is supported in modern runtimes
50
- duplex: req.body ? 'half' : undefined,
51
- });
52
- return { runId: existing, request };
53
- }
54
- const url = new URL(req.url);
55
- if (req.method === 'POST' && url.pathname === '/runs') {
56
- const text = await req.clone().text();
57
- const decoded = decodeRunTargetBody(text);
58
- let runId;
59
- if (Option.isSome(decoded) && decoded.value.runId) {
60
- runId = decoded.value.runId;
61
- }
62
- else {
63
- runId = createRunId();
59
+ const url = new URL(req.url);
60
+ if (req.method === 'POST' && url.pathname === '/runs') {
61
+ const text = yield* Effect.promise(() => req.clone().text());
62
+ const decoded = decodeRunTargetBody(text);
63
+ let runId;
64
+ if (Option.isSome(decoded) && decoded.value.runId) {
65
+ runId = decoded.value.runId;
66
+ }
67
+ else {
68
+ runId = createRunId();
69
+ }
70
+ const decodedRecord = decodePayloadRecord(text);
71
+ const payloadObj = Option.isSome(decodedRecord) && isJsonObject(decodedRecord.value)
72
+ ? { ...decodedRecord.value, runId }
73
+ : { runId };
74
+ const headers = new Headers(req.headers);
75
+ headers.set('x-looms-run-id', runId);
76
+ headers.set('content-type', 'application/json');
77
+ const request = new Request(req.url, {
78
+ method: req.method,
79
+ headers,
80
+ body: stringifyJson(payloadObj),
81
+ });
82
+ return { runId, request };
64
83
  }
65
- const decodedRecord = decodePayloadRecord(text);
66
- const payloadObj = Option.isSome(decodedRecord) && isJsonObject(decodedRecord.value)
67
- ? { ...decodedRecord.value, runId }
68
- : { runId };
69
- const headers = new Headers(req.headers);
70
- headers.set('x-looms-run-id', runId);
71
- headers.set('content-type', 'application/json');
72
- const request = new Request(req.url, {
73
- method: req.method,
74
- headers,
75
- body: JSON.stringify(payloadObj),
76
- });
77
- return { runId, request };
78
- }
79
- throw new Error(`Cannot resolve runId from request: ${req.method} ${url.pathname}`);
84
+ return yield* new RunTargetError(req.method, url.pathname);
85
+ }));
80
86
  }
@@ -0,0 +1,5 @@
1
+ import { type DefinitionStore, type ThreadDefinition } from '@swirls/looms/core';
2
+ import type { AgentSessionDefinition } from './definitions';
3
+ import type { AgentSessionSnapshot, AgentSessionState } from './types';
4
+ export declare function createAgentSessionThread(definitions: DefinitionStore<AgentSessionDefinition>): ThreadDefinition<AgentSessionState>;
5
+ export declare function agentSessionSnapshot(state: AgentSessionState): AgentSessionSnapshot;