@swirls/looms 0.0.2 → 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 (375) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/dist/actor/cell.d.ts +9 -4
  3. package/dist/actor/cell.js +7 -11
  4. package/dist/actor/host.d.ts +7 -3
  5. package/dist/actor/host.js +57 -33
  6. package/dist/actor/index.d.ts +1 -1
  7. package/dist/actor/index.js +1 -1
  8. package/dist/actor/routing.d.ts +12 -1
  9. package/dist/actor/routing.js +57 -51
  10. package/dist/agent/agent-session.d.ts +5 -0
  11. package/dist/agent/agent-session.js +270 -0
  12. package/dist/agent/debugger/agent-chat.d.ts +2 -0
  13. package/dist/agent/debugger/agent-chat.js +176 -0
  14. package/dist/agent/debugger/index.d.ts +5 -0
  15. package/dist/agent/debugger/index.js +19 -0
  16. package/dist/agent/debugger/summarize.d.ts +2 -0
  17. package/dist/agent/debugger/summarize.js +51 -0
  18. package/dist/agent/debugger/token-usage.d.ts +3 -0
  19. package/dist/agent/debugger/token-usage.js +11 -0
  20. package/dist/agent/definitions-store.d.ts +2 -3
  21. package/dist/agent/definitions-store.js +2 -4
  22. package/dist/agent/definitions.d.ts +47 -29
  23. package/dist/agent/definitions.js +39 -12
  24. package/dist/agent/effects.d.ts +359 -2
  25. package/dist/agent/effects.js +60 -34
  26. package/dist/agent/events.d.ts +175 -19
  27. package/dist/agent/events.js +45 -10
  28. package/dist/agent/index.d.ts +1 -0
  29. package/dist/agent/index.js +1 -0
  30. package/dist/agent/llm.d.ts +11 -2
  31. package/dist/agent/llm.js +13 -3
  32. package/dist/agent/module.d.ts +812 -78
  33. package/dist/agent/module.js +16 -4
  34. package/dist/agent/scope.d.ts +174 -18
  35. package/dist/agent/signals.d.ts +26 -1
  36. package/dist/agent/signals.js +79 -0
  37. package/dist/agent/threads.d.ts +3 -33
  38. package/dist/agent/threads.js +20 -25
  39. package/dist/agent/tool-schema.d.ts +1 -17
  40. package/dist/agent/tool-schema.js +3 -43
  41. package/dist/agent/types.d.ts +49 -4
  42. package/dist/agent/types.js +5 -5
  43. package/dist/ai-vercel/index.d.ts +1 -0
  44. package/dist/ai-vercel/index.js +1 -0
  45. package/dist/ai-vercel/messages.js +22 -7
  46. package/dist/ai-vercel/vercel-evaluator.d.ts +36 -0
  47. package/dist/ai-vercel/vercel-evaluator.js +73 -0
  48. package/dist/ai-vercel/vercel-llm.js +64 -44
  49. package/dist/approval/debugger/approvals-view.d.ts +3 -0
  50. package/dist/approval/debugger/approvals-view.js +16 -0
  51. package/dist/approval/debugger/index.d.ts +4 -0
  52. package/dist/approval/debugger/index.js +11 -0
  53. package/dist/approval/debugger/summarize.d.ts +2 -0
  54. package/dist/approval/debugger/summarize.js +17 -0
  55. package/dist/approval/effects.d.ts +89 -0
  56. package/dist/approval/module.d.ts +179 -29
  57. package/dist/approval/projections.d.ts +0 -14
  58. package/dist/approval/projections.js +20 -3
  59. package/dist/approval/signals.d.ts +2 -0
  60. package/dist/approval/signals.js +18 -2
  61. package/dist/cli/cli.js +2 -0
  62. package/dist/cli/commands.js +12 -16
  63. package/dist/client/client.d.ts +1213 -18
  64. package/dist/client/client.js +120 -120
  65. package/dist/client/index.d.ts +1 -1
  66. package/dist/client/index.js +1 -1
  67. package/dist/client/sse.d.ts +13 -0
  68. package/dist/client/sse.js +38 -44
  69. package/dist/cloudflare/alarm-scheduler.d.ts +12 -2
  70. package/dist/cloudflare/alarm-scheduler.js +34 -7
  71. package/dist/cloudflare/durable-object.d.ts +10 -2
  72. package/dist/cloudflare/durable-object.js +59 -28
  73. package/dist/cloudflare/event-store.d.ts +7 -2
  74. package/dist/cloudflare/event-store.js +8 -7
  75. package/dist/cloudflare/index.d.ts +2 -2
  76. package/dist/cloudflare/index.js +2 -2
  77. package/dist/cloudflare/router.d.ts +1 -1
  78. package/dist/cloudflare/router.js +15 -16
  79. package/dist/core/catalog.d.ts +3 -3
  80. package/dist/core/catalog.js +2 -5
  81. package/dist/core/coalesce.js +5 -3
  82. package/dist/core/compose.d.ts +2 -1
  83. package/dist/core/compose.js +19 -2
  84. package/dist/core/debugger-host.d.ts +16 -0
  85. package/dist/core/debugger-host.js +1 -0
  86. package/dist/core/definition-store.d.ts +11 -0
  87. package/dist/core/definition-store.js +6 -0
  88. package/dist/core/effects.d.ts +127 -15
  89. package/dist/core/effects.js +89 -17
  90. package/dist/core/envelope.d.ts +27 -7
  91. package/dist/core/envelope.js +20 -6
  92. package/dist/core/event-codec.d.ts +4 -2
  93. package/dist/core/event-codec.js +10 -5
  94. package/dist/core/event-index.js +1 -3
  95. package/dist/core/fold.d.ts +3 -0
  96. package/dist/core/fold.js +171 -24
  97. package/dist/core/ids.d.ts +5 -0
  98. package/dist/core/ids.js +35 -2
  99. package/dist/core/index.d.ts +7 -1
  100. package/dist/core/index.js +6 -1
  101. package/dist/core/kind.d.ts +22 -0
  102. package/dist/core/kind.js +17 -0
  103. package/dist/core/limits.d.ts +34 -0
  104. package/dist/core/limits.js +46 -0
  105. package/dist/core/match.d.ts +1 -0
  106. package/dist/core/match.js +10 -1
  107. package/dist/core/module-scope.d.ts +22 -20
  108. package/dist/core/module-scope.js +38 -28
  109. package/dist/core/module.d.ts +18 -5
  110. package/dist/core/module.js +4 -0
  111. package/dist/core/projection.d.ts +13 -13
  112. package/dist/core/projection.js +9 -5
  113. package/dist/core/protocol.d.ts +68 -11
  114. package/dist/core/protocol.js +82 -11
  115. package/dist/core/published.d.ts +43 -0
  116. package/dist/core/published.js +44 -0
  117. package/dist/core/replay.d.ts +312 -3
  118. package/dist/core/replay.js +11 -1
  119. package/dist/core/schema.d.ts +11 -20
  120. package/dist/core/schema.js +63 -80
  121. package/dist/core/serialize.js +2 -4
  122. package/dist/core/snapshot-store.d.ts +6 -1
  123. package/dist/core/snapshot-store.js +15 -2
  124. package/dist/core/snapshots.d.ts +5 -1
  125. package/dist/core/snapshots.js +10 -15
  126. package/dist/core/sqlite-store.js +95 -11
  127. package/dist/core/state.d.ts +142 -4
  128. package/dist/core/state.js +78 -0
  129. package/dist/core/store.d.ts +38 -3
  130. package/dist/core/store.js +77 -22
  131. package/dist/core/thread.d.ts +10 -9
  132. package/dist/core/types.d.ts +5 -8
  133. package/dist/core/types.js +28 -24
  134. package/dist/core/validate-event.js +14 -4
  135. package/dist/core/worker-callback.d.ts +17 -0
  136. package/dist/core/worker-callback.js +1 -0
  137. package/dist/debugger/app/assets/index-B3Ah8WAE.css +2 -0
  138. package/dist/debugger/app/assets/index-Qb371t_O.js +22 -0
  139. package/dist/debugger/app/assets/rolldown-runtime-CbXtAM7H.js +1 -0
  140. package/dist/debugger/app/assets/routes-CjtEHgPQ.js +47 -0
  141. package/dist/debugger/app/index.html +14 -0
  142. package/dist/debugger/components/composer.d.ts +17 -0
  143. package/dist/debugger/components/composer.js +25 -0
  144. package/dist/debugger/components/debugger-split.d.ts +2 -1
  145. package/dist/debugger/components/debugger-split.js +2 -2
  146. package/dist/debugger/components/event-inspector.d.ts +2 -1
  147. package/dist/debugger/components/event-inspector.js +25 -16
  148. package/dist/debugger/components/event-stream.d.ts +2 -1
  149. package/dist/debugger/components/event-stream.js +14 -74
  150. package/dist/debugger/components/follow-list.d.ts +19 -0
  151. package/dist/debugger/components/follow-list.js +96 -0
  152. package/dist/debugger/components/json-tree.d.ts +11 -0
  153. package/dist/debugger/components/json-tree.js +120 -0
  154. package/dist/debugger/components/json-view.js +2 -2
  155. package/dist/debugger/components/run-tree.js +53 -20
  156. package/dist/debugger/components/status-dot.d.ts +4 -0
  157. package/dist/debugger/components/status-dot.js +6 -2
  158. package/dist/debugger/context.d.ts +14 -0
  159. package/dist/debugger/context.js +17 -0
  160. package/dist/debugger/contracts.d.ts +7 -4
  161. package/dist/debugger/events/builtin-families.d.ts +3 -0
  162. package/dist/debugger/events/builtin-families.js +9 -0
  163. package/dist/debugger/events/catalog.d.ts +3 -4
  164. package/dist/debugger/events/catalog.js +27 -17
  165. package/dist/debugger/events/protocol-summarize.d.ts +1 -2
  166. package/dist/debugger/events/protocol-summarize.js +42 -24
  167. package/dist/debugger/index.d.ts +20 -6
  168. package/dist/debugger/index.js +19 -5
  169. package/dist/debugger/lib/cn.d.ts +1 -0
  170. package/dist/debugger/lib/cn.js +3 -0
  171. package/dist/debugger/lib/event-meta.d.ts +2 -3
  172. package/dist/debugger/lib/event-meta.js +6 -12
  173. package/dist/debugger/lib/json.d.ts +25 -0
  174. package/dist/debugger/lib/json.js +87 -0
  175. package/dist/debugger/lib/payload.d.ts +7 -0
  176. package/dist/debugger/lib/payload.js +11 -0
  177. package/dist/debugger/lib/time.d.ts +3 -0
  178. package/dist/debugger/lib/time.js +24 -0
  179. package/dist/debugger/plugin.d.ts +36 -0
  180. package/dist/debugger/plugin.js +14 -0
  181. package/dist/debugger/server/index.d.ts +14 -0
  182. package/dist/debugger/server/index.js +114 -0
  183. package/dist/debugger/shell/debugger-shell.d.ts +15 -0
  184. package/dist/debugger/shell/debugger-shell.js +43 -0
  185. package/dist/debugger/shell/definition-key.d.ts +3 -0
  186. package/dist/debugger/shell/definition-key.js +4 -0
  187. package/dist/debugger/shell/events-pane.d.ts +12 -0
  188. package/dist/debugger/shell/events-pane.js +25 -0
  189. package/dist/debugger/shell/projections-panel.d.ts +4 -0
  190. package/dist/debugger/shell/projections-panel.js +59 -0
  191. package/dist/debugger/shell/run-controls.d.ts +4 -0
  192. package/dist/debugger/shell/run-controls.js +72 -0
  193. package/dist/debugger/shell/run-search.d.ts +6 -0
  194. package/dist/debugger/shell/run-search.js +102 -0
  195. package/dist/debugger/shell/run-type-menu.d.ts +6 -0
  196. package/dist/debugger/shell/run-type-menu.js +11 -0
  197. package/dist/debugger/shell/schema-form.d.ts +31 -0
  198. package/dist/debugger/shell/schema-form.js +117 -0
  199. package/dist/debugger/shell/start-form.d.ts +3 -0
  200. package/dist/debugger/shell/start-form.js +75 -0
  201. package/dist/debugger/shell/use-start-run.d.ts +14 -0
  202. package/dist/debugger/shell/use-start-run.js +32 -0
  203. package/dist/debugger/shell/workspace.d.ts +6 -0
  204. package/dist/debugger/shell/workspace.js +15 -0
  205. package/dist/debugger/styles.css +111 -27
  206. package/dist/debugger/ui/button.d.ts +24 -0
  207. package/dist/debugger/ui/button.js +24 -0
  208. package/dist/debugger/ui/command.d.ts +9 -0
  209. package/dist/debugger/ui/command.js +24 -0
  210. package/dist/debugger/ui/dropdown-menu.d.ts +13 -0
  211. package/dist/debugger/ui/dropdown-menu.js +37 -0
  212. package/dist/debugger/ui/panel.d.ts +14 -0
  213. package/dist/debugger/ui/panel.js +16 -0
  214. package/dist/debugger/ui/popover.d.ts +7 -0
  215. package/dist/debugger/ui/popover.js +17 -0
  216. package/dist/debugger/ui/resizable.d.ts +7 -0
  217. package/dist/debugger/ui/resizable.js +14 -0
  218. package/dist/debugger/ui/scroll-area.d.ts +2 -1
  219. package/dist/debugger/ui/scroll-area.js +5 -3
  220. package/dist/debugger/ui/select.d.ts +10 -0
  221. package/dist/debugger/ui/select.js +21 -0
  222. package/dist/debugger/ui/tabs.js +2 -2
  223. package/dist/debugger/ui/textarea.d.ts +3 -0
  224. package/dist/debugger/ui/textarea.js +7 -0
  225. package/dist/evaluator/definitions-store.d.ts +11 -0
  226. package/dist/evaluator/definitions-store.js +8 -0
  227. package/dist/evaluator/definitions.d.ts +18 -0
  228. package/dist/evaluator/definitions.js +4 -0
  229. package/dist/evaluator/effects.d.ts +226 -0
  230. package/dist/evaluator/effects.js +132 -0
  231. package/dist/evaluator/evaluator.d.ts +24 -0
  232. package/dist/evaluator/evaluator.js +67 -0
  233. package/dist/evaluator/events.d.ts +59 -0
  234. package/dist/evaluator/events.js +21 -0
  235. package/dist/evaluator/index.d.ts +11 -0
  236. package/dist/evaluator/index.js +11 -0
  237. package/dist/evaluator/module.d.ts +606 -0
  238. package/dist/evaluator/module.js +25 -0
  239. package/dist/evaluator/projections.d.ts +50 -0
  240. package/dist/evaluator/projections.js +20 -0
  241. package/dist/evaluator/scope.d.ts +59 -0
  242. package/dist/evaluator/scope.js +7 -0
  243. package/dist/evaluator/signals.d.ts +3 -0
  244. package/dist/evaluator/signals.js +17 -0
  245. package/dist/evaluator/threads.d.ts +62 -0
  246. package/dist/evaluator/threads.js +117 -0
  247. package/dist/evaluator/types.d.ts +125 -0
  248. package/dist/evaluator/types.js +51 -0
  249. package/dist/projectors/delivery.d.ts +83 -0
  250. package/dist/projectors/delivery.js +182 -0
  251. package/dist/projectors/index-model.d.ts +1 -1
  252. package/dist/projectors/index-model.js +4 -6
  253. package/dist/projectors/index.d.ts +2 -0
  254. package/dist/projectors/index.js +2 -0
  255. package/dist/projectors/memory.d.ts +1 -1
  256. package/dist/projectors/memory.js +10 -8
  257. package/dist/projectors/postgres.js +44 -58
  258. package/dist/projectors/projector.d.ts +17 -0
  259. package/dist/projectors/projector.js +26 -1
  260. package/dist/projectors/sqlite-delivery.d.ts +4 -0
  261. package/dist/projectors/sqlite-delivery.js +147 -0
  262. package/dist/projectors/sqlite.js +23 -23
  263. package/dist/projectors/with-projectors.d.ts +7 -2
  264. package/dist/projectors/with-projectors.js +27 -28
  265. package/dist/react/notify.js +11 -8
  266. package/dist/react/react.d.ts +2 -3
  267. package/dist/react/react.js +14 -30
  268. package/dist/react/sse.d.ts +1 -1
  269. package/dist/react/sse.js +1 -1
  270. package/dist/react/store.d.ts +3 -3
  271. package/dist/react/store.js +109 -119
  272. package/dist/runtime/cancellation.d.ts +16 -0
  273. package/dist/runtime/cancellation.js +129 -0
  274. package/dist/runtime/deadline-transition-planner.d.ts +31 -0
  275. package/dist/runtime/deadline-transition-planner.js +139 -0
  276. package/dist/runtime/dispatch-effect.d.ts +3 -2
  277. package/dist/runtime/dispatch-effect.js +7 -13
  278. package/dist/runtime/errors.d.ts +51 -0
  279. package/dist/runtime/errors.js +39 -0
  280. package/dist/runtime/{runtime-helpers.d.ts → helpers.d.ts} +24 -4
  281. package/dist/runtime/{runtime-helpers.js → helpers.js} +56 -19
  282. package/dist/runtime/index.d.ts +3 -3
  283. package/dist/runtime/index.js +3 -3
  284. package/dist/runtime/ingress.d.ts +15 -0
  285. package/dist/runtime/ingress.js +42 -0
  286. package/dist/runtime/local-effect-executor.d.ts +21 -0
  287. package/dist/runtime/local-effect-executor.js +137 -0
  288. package/dist/runtime/looms.d.ts +30 -4
  289. package/dist/runtime/looms.js +57 -37
  290. package/dist/runtime/observer.d.ts +45 -0
  291. package/dist/runtime/observer.js +11 -0
  292. package/dist/runtime/repository.d.ts +55 -0
  293. package/dist/runtime/repository.js +293 -0
  294. package/dist/runtime/retry-policy.d.ts +16 -0
  295. package/dist/runtime/retry-policy.js +29 -0
  296. package/dist/runtime/run-execution-context.d.ts +30 -0
  297. package/dist/runtime/run-execution-context.js +56 -0
  298. package/dist/runtime/run-start.d.ts +4 -0
  299. package/dist/runtime/run-start.js +44 -0
  300. package/dist/runtime/runtime.d.ts +6 -87
  301. package/dist/runtime/runtime.js +248 -470
  302. package/dist/runtime/server/auth.d.ts +41 -0
  303. package/dist/runtime/server/auth.js +45 -0
  304. package/dist/runtime/server/debugger-host.d.ts +11 -0
  305. package/dist/runtime/server/debugger-host.js +32 -0
  306. package/dist/runtime/server/fetch-handler.d.ts +18 -0
  307. package/dist/runtime/server/fetch-handler.js +62 -0
  308. package/dist/runtime/server/http.d.ts +38 -0
  309. package/dist/runtime/server/http.js +66 -0
  310. package/dist/runtime/server/route-table.d.ts +55 -0
  311. package/dist/runtime/server/route-table.js +74 -0
  312. package/dist/runtime/server/routes/definitions.d.ts +3 -0
  313. package/dist/runtime/server/routes/definitions.js +20 -0
  314. package/dist/runtime/server/routes/effects.d.ts +3 -0
  315. package/dist/runtime/server/routes/effects.js +59 -0
  316. package/dist/runtime/server/routes/operations.d.ts +3 -0
  317. package/dist/runtime/server/routes/operations.js +14 -0
  318. package/dist/runtime/server/routes/runs.d.ts +3 -0
  319. package/dist/runtime/server/routes/runs.js +199 -0
  320. package/dist/runtime/server.d.ts +4 -8
  321. package/dist/runtime/server.js +6 -211
  322. package/dist/runtime/sse.d.ts +2 -1
  323. package/dist/runtime/sse.js +24 -17
  324. package/dist/runtime/types.d.ts +117 -0
  325. package/dist/runtime/types.js +1 -0
  326. package/dist/runtime/wake-engine.d.ts +20 -0
  327. package/dist/runtime/wake-engine.js +311 -0
  328. package/dist/runtime/wake-scheduler.d.ts +3 -4
  329. package/dist/runtime/wake-scheduler.js +25 -31
  330. package/dist/runtime/worker-callbacks.d.ts +16 -0
  331. package/dist/runtime/worker-callbacks.js +167 -0
  332. package/dist/runtime/worker-effect-dispatch.d.ts +36 -0
  333. package/dist/runtime/worker-effect-dispatch.js +126 -0
  334. package/dist/s2/config.d.ts +1 -1
  335. package/dist/s2/config.js +1 -1
  336. package/dist/s2/lite.js +70 -82
  337. package/dist/s2/pagination.d.ts +15 -0
  338. package/dist/s2/pagination.js +36 -0
  339. package/dist/s2/projector.js +6 -5
  340. package/dist/s2/snapshot-store.js +188 -161
  341. package/dist/s2/store.d.ts +3 -21
  342. package/dist/s2/store.js +71 -165
  343. package/dist/testing/index.d.ts +15 -0
  344. package/dist/testing/index.js +53 -16
  345. package/dist/workflow/debugger/index.d.ts +4 -0
  346. package/dist/workflow/debugger/index.js +11 -0
  347. package/dist/workflow/debugger/nodes-view.d.ts +3 -0
  348. package/dist/workflow/debugger/nodes-view.js +13 -0
  349. package/dist/workflow/debugger/summarize.d.ts +2 -0
  350. package/dist/workflow/debugger/summarize.js +30 -0
  351. package/dist/workflow/definitions-store.d.ts +2 -3
  352. package/dist/workflow/definitions-store.js +2 -2
  353. package/dist/workflow/definitions.d.ts +78 -22
  354. package/dist/workflow/definitions.js +80 -14
  355. package/dist/workflow/effects.d.ts +453 -107
  356. package/dist/workflow/effects.js +214 -115
  357. package/dist/workflow/events.d.ts +149 -5
  358. package/dist/workflow/events.js +27 -5
  359. package/dist/workflow/graph.d.ts +56 -0
  360. package/dist/workflow/graph.js +207 -0
  361. package/dist/workflow/index.d.ts +1 -0
  362. package/dist/workflow/index.js +1 -0
  363. package/dist/workflow/module.d.ts +1047 -266
  364. package/dist/workflow/projections.d.ts +14 -12
  365. package/dist/workflow/projections.js +30 -8
  366. package/dist/workflow/scope.d.ts +146 -4
  367. package/dist/workflow/threads.d.ts +383 -155
  368. package/dist/workflow/threads.js +308 -110
  369. package/package.json +26 -2
  370. package/dist/debugger/events/domain-summarize.d.ts +0 -3
  371. package/dist/debugger/events/domain-summarize.js +0 -107
  372. package/dist/debugger/events/family.d.ts +0 -8
  373. package/dist/debugger/events/family.js +0 -44
  374. package/dist/runtime/events-api.d.ts +0 -4
  375. package/dist/runtime/events-api.js +0 -84
@@ -0,0 +1,11 @@
1
+ import { isJsonNumber, isJsonObject, isJsonString } from '@swirls/looms/core';
2
+ /** Fields of a JSON object, or no fields for any other value. */
3
+ export function jsonFields(value) {
4
+ return isJsonObject(value) ? value : {};
5
+ }
6
+ export function jsonText(value) {
7
+ return isJsonString(value) ? value : undefined;
8
+ }
9
+ export function jsonNumberText(value) {
10
+ return isJsonNumber(value) ? String(value) : undefined;
11
+ }
@@ -0,0 +1,3 @@
1
+ /** Run ids start with a base36 millisecond timestamp (`run_<ms36>_<counter>_<entropy>`). */
2
+ export declare function runCreatedAt(runId: string): number | undefined;
3
+ export declare function relativeTime(then: number, now: number): string;
@@ -0,0 +1,24 @@
1
+ /** Run ids start with a base36 millisecond timestamp (`run_<ms36>_<counter>_<entropy>`). */
2
+ export function runCreatedAt(runId) {
3
+ const stamp = runId.split('_')[1];
4
+ if (!stamp) {
5
+ return undefined;
6
+ }
7
+ const ms = Number.parseInt(stamp, 36);
8
+ return Number.isFinite(ms) ? ms : undefined;
9
+ }
10
+ export function relativeTime(then, now) {
11
+ const seconds = Math.max(0, Math.round((now - then) / 1000));
12
+ if (seconds < 45) {
13
+ return 'just now';
14
+ }
15
+ const minutes = Math.round(seconds / 60);
16
+ if (minutes < 60) {
17
+ return `${minutes}m ago`;
18
+ }
19
+ const hours = Math.round(minutes / 60);
20
+ if (hours < 24) {
21
+ return `${hours}h ago`;
22
+ }
23
+ return `${Math.round(hours / 24)}d ago`;
24
+ }
@@ -0,0 +1,36 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { PublishedDefinition } from '@swirls/looms/core';
3
+ import type { DebuggerEvent, EventSummary } from './contracts';
4
+ export interface EventFamilyView {
5
+ readonly family: string;
6
+ /** Event type prefix the family owns. Defaults to `${family}.`. */
7
+ readonly prefix?: string;
8
+ readonly color: string;
9
+ readonly summarize?: (event: DebuggerEvent) => EventSummary | undefined;
10
+ }
11
+ export interface WorkspaceContext {
12
+ readonly definition: PublishedDefinition;
13
+ readonly runId?: string;
14
+ readonly onStarted: (runId: string) => void;
15
+ }
16
+ export interface WorkspaceView {
17
+ readonly kinds: readonly string[];
18
+ readonly component: ComponentType<WorkspaceContext>;
19
+ /** Narrows `kinds`. When omitted, every definition of those kinds uses the workspace. */
20
+ readonly matches?: (definition: PublishedDefinition) => boolean;
21
+ }
22
+ /** A projection pane. The component reads the projection for `runId` itself. */
23
+ export interface ProjectionView {
24
+ readonly name: string;
25
+ readonly component: ComponentType<{
26
+ readonly runId: string;
27
+ }>;
28
+ }
29
+ export interface DebuggerPlugin {
30
+ readonly name: string;
31
+ readonly families?: readonly EventFamilyView[];
32
+ readonly workspace?: WorkspaceView;
33
+ readonly projections?: readonly ProjectionView[];
34
+ }
35
+ export declare function workspaceFor(plugins: readonly DebuggerPlugin[], definition: PublishedDefinition): WorkspaceView | undefined;
36
+ export declare function projectionFor(plugins: readonly DebuggerPlugin[], name: string): ProjectionView | undefined;
@@ -0,0 +1,14 @@
1
+ export function workspaceFor(plugins, definition) {
2
+ return plugins.find((plugin) => plugin.workspace !== undefined &&
3
+ plugin.workspace.kinds.includes(definition.kind) &&
4
+ (plugin.workspace.matches?.(definition) ?? true))?.workspace;
5
+ }
6
+ export function projectionFor(plugins, name) {
7
+ for (const plugin of plugins) {
8
+ const view = plugin.projections?.find((candidate) => candidate.name === name);
9
+ if (view) {
10
+ return view;
11
+ }
12
+ }
13
+ return undefined;
14
+ }
@@ -0,0 +1,14 @@
1
+ import type { DebuggerHost } from '@swirls/looms/core';
2
+ export interface DebuggerUiOptions {
3
+ /** Mount path. Defaults to `/debugger`. */
4
+ readonly path?: string;
5
+ /** Built SPA directory. Defaults to `debuggerAppRoot()`. */
6
+ readonly root?: string;
7
+ }
8
+ /**
9
+ * Built SPA directory beside the server module: `dist/debugger/app` in the published package.
10
+ * Source checkouts have no bundle there; pass `root` pointing at `apps/debugger/dist` instead.
11
+ */
12
+ export declare function debuggerAppRoot(moduleUrl?: string): string;
13
+ /** Static host for the debugger SPA. Mount it with `createLooms({ debugger: debuggerUi() })`. */
14
+ export declare function debuggerUi(options?: DebuggerUiOptions): DebuggerHost;
@@ -0,0 +1,114 @@
1
+ import { readFile, realpath, stat } from 'node:fs/promises';
2
+ import { extname, join, normalize, sep } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ function contentType(extension) {
5
+ switch (extension) {
6
+ case '.css':
7
+ return 'text/css; charset=utf-8';
8
+ case '.html':
9
+ return 'text/html; charset=utf-8';
10
+ case '.ico':
11
+ return 'image/x-icon';
12
+ case '.js':
13
+ case '.mjs':
14
+ return 'text/javascript; charset=utf-8';
15
+ case '.json':
16
+ case '.map':
17
+ return 'application/json; charset=utf-8';
18
+ case '.png':
19
+ return 'image/png';
20
+ case '.svg':
21
+ return 'image/svg+xml';
22
+ case '.txt':
23
+ return 'text/plain; charset=utf-8';
24
+ case '.woff2':
25
+ return 'font/woff2';
26
+ default:
27
+ return 'application/octet-stream';
28
+ }
29
+ }
30
+ const assetCache = 'public, max-age=31536000, immutable';
31
+ function inside(root, file) {
32
+ const rootPath = normalize(root);
33
+ const filePath = normalize(file);
34
+ const prefix = rootPath.endsWith(sep) ? rootPath : `${rootPath}${sep}`;
35
+ return filePath === rootPath || filePath.startsWith(prefix);
36
+ }
37
+ /**
38
+ * Built SPA directory beside the server module: `dist/debugger/app` in the published package.
39
+ * Source checkouts have no bundle there; pass `root` pointing at `apps/debugger/dist` instead.
40
+ */
41
+ export function debuggerAppRoot(moduleUrl = import.meta.url) {
42
+ return fileURLToPath(new URL('../app/', moduleUrl));
43
+ }
44
+ /** Relative file path for a mount path, or null when it cannot name a file inside the bundle. */
45
+ function relativeFile(path) {
46
+ let decoded;
47
+ try {
48
+ decoded = decodeURIComponent(path);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ const relative = decoded.replace(/^[/\\]+/, '');
54
+ if (relative.includes('\0') || relative.split(/[/\\]/).includes('..')) {
55
+ return null;
56
+ }
57
+ return relative;
58
+ }
59
+ function shellResponse(root, basePath, head) {
60
+ return readFile(join(root, 'index.html'), 'utf8').then((html) => {
61
+ const base = `<base href="${basePath}/">`;
62
+ const document = html.includes('<head>')
63
+ ? html.replace('<head>', `<head>${base}`)
64
+ : base + html;
65
+ return new Response(head ? null : document, {
66
+ headers: {
67
+ 'cache-control': 'no-cache',
68
+ 'content-type': 'text/html; charset=utf-8',
69
+ },
70
+ });
71
+ }, () => new Response('Debugger UI is not built', { status: 404 }));
72
+ }
73
+ function fileResponse(file, relative, head) {
74
+ const headers = new Headers({
75
+ 'cache-control': relative.startsWith('assets/') ? assetCache : 'no-cache',
76
+ 'content-type': contentType(extname(file)),
77
+ });
78
+ if (head) {
79
+ return Promise.resolve(new Response(null, { headers }));
80
+ }
81
+ return readFile(file).then((body) => new Response(body, { headers }));
82
+ }
83
+ function serveBundle(root, relative, mount, head) {
84
+ return realpath(join(root, relative)).then((file) => {
85
+ if (!inside(root, file)) {
86
+ return new Response('Invalid path', { status: 400 });
87
+ }
88
+ return stat(file).then((info) => info.isFile() && file !== join(root, 'index.html')
89
+ ? fileResponse(file, relative, head)
90
+ : shellResponse(root, mount.basePath, head));
91
+ },
92
+ // Client routes have no extension; a missing asset must 404 rather than load the HTML shell.
93
+ () => extname(relative) === ''
94
+ ? shellResponse(root, mount.basePath, head)
95
+ : new Response('Not Found', { status: 404 }));
96
+ }
97
+ function serveDebugger(root, req, mount) {
98
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
99
+ return Promise.resolve(new Response('Method Not Allowed', { status: 405 }));
100
+ }
101
+ const relative = relativeFile(mount.path);
102
+ if (relative === null) {
103
+ return Promise.resolve(new Response('Invalid path', { status: 400 }));
104
+ }
105
+ return realpath(root).then((rootReal) => serveBundle(rootReal, relative, mount, req.method === 'HEAD'), () => new Response('Debugger UI is not built', { status: 404 }));
106
+ }
107
+ /** Static host for the debugger SPA. Mount it with `createLooms({ debugger: debuggerUi() })`. */
108
+ export function debuggerUi(options = {}) {
109
+ const root = options.root ?? debuggerAppRoot();
110
+ return {
111
+ basePath: options.path ?? '/debugger',
112
+ fetch: (req, mount) => serveDebugger(root, req, mount),
113
+ };
114
+ }
@@ -0,0 +1,15 @@
1
+ import type { DebuggerPlugin } from '../plugin';
2
+ export interface DebuggerSelection {
3
+ readonly kind?: string;
4
+ readonly name?: string;
5
+ readonly run?: string;
6
+ readonly thread?: string;
7
+ readonly seq?: number;
8
+ }
9
+ export declare function DebuggerShell({ plugins, selection, onSelectionChange, endpoint, }: {
10
+ plugins: readonly DebuggerPlugin[];
11
+ selection: DebuggerSelection;
12
+ onSelectionChange: (selection: DebuggerSelection) => void;
13
+ /** Looms host base URL. Defaults to the page origin. */
14
+ endpoint?: string;
15
+ }): import("react").JSX.Element;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Activity, GitBranch, ListTree } from 'lucide-react';
3
+ import { useEffect, useState } from 'react';
4
+ import { useDefaultLayout } from 'react-resizable-panels';
5
+ import { DebuggerProvider, useDebugger } from '../context.js';
6
+ import { errorMessage } from '../lib/cn.js';
7
+ import { EmptyState, PanelHeader, PanelTitle } from '../ui/panel.js';
8
+ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../ui/resizable.js';
9
+ import { definitionKey } from './definition-key.js';
10
+ import { EventsPane, RunTreePane } from './events-pane.js';
11
+ import { ProjectionsPanel } from './projections-panel.js';
12
+ import { RunControls } from './run-controls.js';
13
+ import { RunSearch } from './run-search.js';
14
+ import { RunTypeMenu } from './run-type-menu.js';
15
+ import { Workspace } from './workspace.js';
16
+ const emptyCatalog = { definitions: [], projections: [] };
17
+ function useDefinitionCatalog() {
18
+ const { client } = useDebugger();
19
+ const [catalog, setCatalog] = useState(emptyCatalog);
20
+ const [error, setError] = useState(null);
21
+ useEffect(() => {
22
+ client.listDefinitions().then(setCatalog, (cause) => setError(errorMessage(cause)));
23
+ }, [client]);
24
+ return { catalog, error };
25
+ }
26
+ function Brand() {
27
+ return (_jsxs("div", { className: "flex shrink-0 items-center gap-2 pr-1", children: [_jsx("span", { className: "bg-foreground text-background flex size-5 items-center justify-center rounded-md", children: _jsx(GitBranch, { className: "size-3", strokeWidth: 2.5 }) }), _jsx("span", { className: "text-[13px] font-semibold tracking-tight", children: "Looms" })] }));
28
+ }
29
+ function DebuggerFrame({ selection, onSelectionChange, }) {
30
+ const { catalog, error } = useDefinitionCatalog();
31
+ const { kind, name } = selection;
32
+ const selectedKey = kind && name ? definitionKey({ kind, name }) : undefined;
33
+ const selected = catalog.definitions.find((definition) => definitionKey(definition) === selectedKey);
34
+ const layout = useDefaultLayout({ id: 'looms-debugger-columns', storage: localStorage });
35
+ return (_jsxs("div", { className: "looms-debugger bg-background text-foreground flex h-full flex-col overflow-hidden font-sans antialiased", children: [_jsxs("header", { className: "border-border bg-sidebar flex h-11 shrink-0 items-center gap-2 border-b px-3", children: [_jsx(Brand, {}), _jsx("span", { className: "text-border text-lg font-light select-none", "aria-hidden": true, children: "/" }), _jsx(RunTypeMenu, { definitions: catalog.definitions, selected: selected, onSelect: (definition) => onSelectionChange({ kind: definition.kind, name: definition.name }) }), _jsx(RunSearch, { runId: selection.run, fallbackLabel: selected?.name, onSelectRun: (run) => onSelectionChange({
36
+ kind: run.kind ?? undefined,
37
+ name: run.definitionName ?? undefined,
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 }) }) })] })] }));
40
+ }
41
+ export function DebuggerShell({ plugins, selection, onSelectionChange, endpoint, }) {
42
+ return (_jsx(DebuggerProvider, { endpoint: endpoint, plugins: plugins, children: _jsx(DebuggerFrame, { selection: selection, onSelectionChange: onSelectionChange }) }));
43
+ }
@@ -0,0 +1,3 @@
1
+ import type { PublishedDefinition } from '@swirls/looms/core';
2
+ /** Definitions are unique by kind and name; the same name can appear under two kinds. */
3
+ export declare function definitionKey(definition: Pick<PublishedDefinition, 'kind' | 'name'>): string;
@@ -0,0 +1,4 @@
1
+ /** Definitions are unique by kind and name; the same name can appear under two kinds. */
2
+ export function definitionKey(definition) {
3
+ return `${definition.kind}:${definition.name}`;
4
+ }
@@ -0,0 +1,12 @@
1
+ export declare function EventsPane({ runId, threadId, seq, onSelectSeq, onClearThread, }: {
2
+ runId: string;
3
+ threadId?: string;
4
+ seq?: number;
5
+ onSelectSeq: (seq: number | undefined) => void;
6
+ onClearThread: () => void;
7
+ }): import("react").JSX.Element;
8
+ export declare function RunTreePane({ runId, threadId, onSelectThread, }: {
9
+ runId: string;
10
+ threadId?: string;
11
+ onSelectThread: (threadId: string | undefined) => void;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useMemo } from 'react';
3
+ import { useEventCounts, useRunEvents, useRunStore, useRunSummary, useThreadTree, } from '@swirls/looms/react';
4
+ import { findThreadName } from '../components/debugger-split.js';
5
+ import { EventStream } from '../components/event-stream.js';
6
+ import { RunTree } from '../components/run-tree.js';
7
+ import { useDebugger } from '../context.js';
8
+ import { createEventCatalog } from '../events/catalog.js';
9
+ export function EventsPane({ runId, threadId, seq, onSelectSeq, onClearThread, }) {
10
+ const { client, plugins } = useDebugger();
11
+ const catalog = useMemo(() => createEventCatalog(plugins), [plugins]);
12
+ const store = useRunStore(runId);
13
+ const events = useRunEvents(store);
14
+ const summary = useRunSummary(store);
15
+ const tree = useThreadTree(store);
16
+ const loadReplayStep = useCallback(({ runId: replayRunId, seq: replaySeq }) => client.replayTo(replayRunId, replaySeq).then((result) => result.step), [client]);
17
+ return (_jsx(EventStream, { events: events, startedAt: summary.startedAt, threadId: threadId, threadLabel: findThreadName(tree.root, threadId), onClearThread: onClearThread, selectedSeq: seq, onSelectSeq: onSelectSeq, loadReplayStep: loadReplayStep, catalog: catalog }));
18
+ }
19
+ export function RunTreePane({ runId, threadId, onSelectThread, }) {
20
+ const store = useRunStore(runId);
21
+ const summary = useRunSummary(store);
22
+ const tree = useThreadTree(store);
23
+ const counts = useEventCounts(store);
24
+ return (_jsx(RunTree, { runId: runId, runStatus: summary.status, tree: tree, eventCounts: counts, selectedThread: threadId, onSelectThread: onSelectThread }));
25
+ }
@@ -0,0 +1,4 @@
1
+ export declare function ProjectionsPanel({ runId, names }: {
2
+ runId?: string;
3
+ names: readonly string[];
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,59 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronRight, Layers } from 'lucide-react';
3
+ import { useEffect, useState } from 'react';
4
+ import { asJson } from '@swirls/looms/core';
5
+ import { useRunSelector, useRunStore } from '@swirls/looms/react';
6
+ import { JsonTree } from '../components/json-tree.js';
7
+ import { useDebugger } from '../context.js';
8
+ import { cn, errorMessage } from '../lib/cn.js';
9
+ import { projectionFor } from '../plugin.js';
10
+ import { EmptyState, PanelHeader, PanelTitle } from '../ui/panel.js';
11
+ import { ScrollArea } from '../ui/scroll-area.js';
12
+ function Section({ title, children }) {
13
+ const [open, setOpen] = useState(true);
14
+ return (_jsxs("section", { className: "border-border border-b last:border-b-0", children: [_jsxs("button", { type: "button", "aria-expanded": open, onClick: () => setOpen(!open), className: "bg-background/95 hover:bg-accent/40 sticky top-0 z-10 flex h-8 w-full items-center gap-1.5 px-3 text-left backdrop-blur", children: [_jsx(ChevronRight, { "aria-hidden": true, className: cn('text-muted-foreground ease-snappy size-3 transition-transform duration-100', open && 'rotate-90') }), _jsx("h3", { className: "font-mono text-[11px] font-medium", children: title })] }), open ? _jsx("div", { className: "px-3 pt-0.5 pb-3", children: children }) : null] }));
15
+ }
16
+ function lastDurableSeq(store) {
17
+ return store.events().reduce((seq, event) => (event.ephemeral ? seq : event.seq), 0);
18
+ }
19
+ function FetchedProjection({ runId, name }) {
20
+ const { client } = useDebugger();
21
+ const durableSeq = useRunSelector(useRunStore(runId), lastDurableSeq);
22
+ const [value, setValue] = useState(null);
23
+ const [error, setError] = useState(null);
24
+ useEffect(() => {
25
+ let active = true;
26
+ client.project(runId, name).then((result) => {
27
+ if (active) {
28
+ setValue(asJson(result.value));
29
+ setError(null);
30
+ }
31
+ }, (cause) => {
32
+ if (active) {
33
+ setError(errorMessage(cause));
34
+ }
35
+ });
36
+ return () => {
37
+ active = false;
38
+ };
39
+ }, [client, durableSeq, name, runId]);
40
+ if (error) {
41
+ return _jsx("p", { className: "text-status-failed text-xs", children: error });
42
+ }
43
+ if (value === null) {
44
+ return _jsx("p", { className: "text-muted-foreground text-xs", children: "Loading\u2026" });
45
+ }
46
+ return _jsx(JsonTree, { value: value, defaultExpandDepth: 2 });
47
+ }
48
+ function ProjectionBody({ runId, name }) {
49
+ const { plugins } = useDebugger();
50
+ const view = projectionFor(plugins, name);
51
+ if (view) {
52
+ const View = view.component;
53
+ return _jsx(View, { runId: runId });
54
+ }
55
+ return _jsx(FetchedProjection, { runId: runId, name: name });
56
+ }
57
+ export function ProjectionsPanel({ runId, names }) {
58
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(PanelHeader, { children: _jsx(PanelTitle, { meta: names.length, children: "Projections" }) }), !runId ? (_jsx(EmptyState, { icon: _jsx(Layers, {}), title: "No run selected", children: "Projections fold the run's event log into live views." })) : names.length === 0 ? (_jsx(EmptyState, { icon: _jsx(Layers, {}), title: "No projections" })) : (_jsx(ScrollArea, { className: "min-h-0 flex-1", children: names.map((name) => (_jsx(Section, { title: name, children: _jsx(ProjectionBody, { runId: runId, name: name }) }, name))) }))] }));
59
+ }
@@ -0,0 +1,4 @@
1
+ /** Run-scoped actions that live beside the run switcher. */
2
+ export declare function RunControls({ runId }: {
3
+ runId: string;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,72 @@
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';
4
+ import { cleanUndefined } from '@swirls/looms/core';
5
+ import { useRunStore, useRunSummary } from '@swirls/looms/react';
6
+ import { useDebugger } from '../context.js';
7
+ import { errorMessage } from '../lib/cn.js';
8
+ import { Button } from '../ui/button.js';
9
+ import { Input } from '../ui/input.js';
10
+ import { Kbd } from '../ui/panel.js';
11
+ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover.js';
12
+ import { Textarea } from '../ui/textarea.js';
13
+ const CANCEL_TYPE = 'runtime.thread.cancel.requested';
14
+ function useRunAction() {
15
+ const [error, setError] = useState(null);
16
+ const [pending, setPending] = useState(false);
17
+ function run(action, onDone) {
18
+ setPending(true);
19
+ setError(null);
20
+ action()
21
+ .then(() => onDone?.())
22
+ .catch((cause) => setError(errorMessage(cause)))
23
+ .finally(() => setPending(false));
24
+ }
25
+ return { run, pending, error, setError };
26
+ }
27
+ function Field({ label, hint, children }) {
28
+ return (_jsxs("label", { className: "grid gap-1.5", children: [_jsxs("span", { className: "flex items-baseline justify-between text-[11px]", children: [_jsx("span", { className: "text-foreground font-medium", children: label }), hint ? _jsx("span", { className: "text-muted-foreground", children: hint }) : null] }), children] }));
29
+ }
30
+ function SignalForm({ runId, rootThreadId }) {
31
+ const { client } = useDebugger();
32
+ const [type, setType] = useState(CANCEL_TYPE);
33
+ const [payload, setPayload] = useState('{}');
34
+ const [thread, setThread] = useState('');
35
+ const [sent, setSent] = useState(false);
36
+ const { run, pending, error, setError } = useRunAction();
37
+ function submit() {
38
+ setSent(false);
39
+ let parsed;
40
+ try {
41
+ parsed = {
42
+ type,
43
+ payload: cleanUndefined(JSON.parse(payload)),
44
+ threadId: thread || rootThreadId,
45
+ };
46
+ }
47
+ catch (cause) {
48
+ setError(errorMessage(cause));
49
+ return;
50
+ }
51
+ run(() => client.signal(runId, [parsed]), () => setSent(true));
52
+ }
53
+ return (_jsxs("form", { className: "grid gap-3", onSubmit: (event) => {
54
+ event.preventDefault();
55
+ 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
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
58
+ event.preventDefault();
59
+ submit();
60
+ }
61
+ }, 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
+ /** Run-scoped actions that live beside the run switcher. */
64
+ export function RunControls({ runId }) {
65
+ 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 }) })] })] }));
72
+ }
@@ -0,0 +1,6 @@
1
+ import type { RunSummary } from '@swirls/looms/core';
2
+ export declare function RunSearch({ runId, fallbackLabel, onSelectRun, }: {
3
+ runId?: string;
4
+ fallbackLabel?: string;
5
+ onSelectRun: (run: RunSummary) => void;
6
+ }): import("react").JSX.Element;
@@ -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
+ }