botmux 2.33.0 → 2.34.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 (281) hide show
  1. package/README.en.md +12 -1
  2. package/README.md +45 -1
  3. package/dist/adapters/cli/claude-code.d.ts.map +1 -1
  4. package/dist/adapters/cli/claude-code.js +11 -0
  5. package/dist/adapters/cli/claude-code.js.map +1 -1
  6. package/dist/cli/bots-list-output.d.ts +21 -0
  7. package/dist/cli/bots-list-output.d.ts.map +1 -0
  8. package/dist/cli/bots-list-output.js +23 -0
  9. package/dist/cli/bots-list-output.js.map +1 -0
  10. package/dist/cli/workflow.d.ts +13 -0
  11. package/dist/cli/workflow.d.ts.map +1 -0
  12. package/dist/cli/workflow.js +781 -0
  13. package/dist/cli/workflow.js.map +1 -0
  14. package/dist/cli.js +69 -14
  15. package/dist/cli.js.map +1 -1
  16. package/dist/core/command-handler.d.ts.map +1 -1
  17. package/dist/core/command-handler.js +219 -6
  18. package/dist/core/command-handler.js.map +1 -1
  19. package/dist/core/session-manager.d.ts +6 -1
  20. package/dist/core/session-manager.d.ts.map +1 -1
  21. package/dist/core/session-manager.js +22 -12
  22. package/dist/core/session-manager.js.map +1 -1
  23. package/dist/core/worker-pool.d.ts +13 -0
  24. package/dist/core/worker-pool.d.ts.map +1 -1
  25. package/dist/core/worker-pool.js +100 -6
  26. package/dist/core/worker-pool.js.map +1 -1
  27. package/dist/daemon.d.ts +3 -0
  28. package/dist/daemon.d.ts.map +1 -1
  29. package/dist/daemon.js +884 -3
  30. package/dist/daemon.js.map +1 -1
  31. package/dist/dashboard/auth.d.ts +36 -0
  32. package/dist/dashboard/auth.d.ts.map +1 -1
  33. package/dist/dashboard/auth.js +22 -0
  34. package/dist/dashboard/auth.js.map +1 -1
  35. package/dist/dashboard/web/app.js +20 -1
  36. package/dist/dashboard/web/app.js.map +1 -1
  37. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  38. package/dist/dashboard/web/i18n.js +356 -0
  39. package/dist/dashboard/web/i18n.js.map +1 -1
  40. package/dist/dashboard/web/workflow-catalog.d.ts +2 -0
  41. package/dist/dashboard/web/workflow-catalog.d.ts.map +1 -0
  42. package/dist/dashboard/web/workflow-catalog.js +323 -0
  43. package/dist/dashboard/web/workflow-catalog.js.map +1 -0
  44. package/dist/dashboard/web/workflows.d.ts +2 -0
  45. package/dist/dashboard/web/workflows.d.ts.map +1 -0
  46. package/dist/dashboard/web/workflows.js +1618 -0
  47. package/dist/dashboard/web/workflows.js.map +1 -0
  48. package/dist/dashboard/workflow-api.d.ts +23 -0
  49. package/dist/dashboard/workflow-api.d.ts.map +1 -0
  50. package/dist/dashboard/workflow-api.js +463 -0
  51. package/dist/dashboard/workflow-api.js.map +1 -0
  52. package/dist/dashboard-web/app.js +494 -199
  53. package/dist/dashboard-web/index.html +1 -0
  54. package/dist/dashboard-web/style.css +160 -6
  55. package/dist/dashboard-web/terminal-replay.html +227 -0
  56. package/dist/dashboard.js +29 -12
  57. package/dist/dashboard.js.map +1 -1
  58. package/dist/i18n/en.d.ts.map +1 -1
  59. package/dist/i18n/en.js +12 -0
  60. package/dist/i18n/en.js.map +1 -1
  61. package/dist/i18n/zh.d.ts.map +1 -1
  62. package/dist/i18n/zh.js +12 -0
  63. package/dist/i18n/zh.js.map +1 -1
  64. package/dist/im/lark/card-handler.d.ts +3 -0
  65. package/dist/im/lark/card-handler.d.ts.map +1 -1
  66. package/dist/im/lark/card-handler.js +27 -1
  67. package/dist/im/lark/card-handler.js.map +1 -1
  68. package/dist/im/lark/client.d.ts +19 -2
  69. package/dist/im/lark/client.d.ts.map +1 -1
  70. package/dist/im/lark/client.js +21 -2
  71. package/dist/im/lark/client.js.map +1 -1
  72. package/dist/im/lark/workflow-card-handler.d.ts +50 -0
  73. package/dist/im/lark/workflow-card-handler.d.ts.map +1 -0
  74. package/dist/im/lark/workflow-card-handler.js +152 -0
  75. package/dist/im/lark/workflow-card-handler.js.map +1 -0
  76. package/dist/im/lark/workflow-cards.d.ts +46 -0
  77. package/dist/im/lark/workflow-cards.d.ts.map +1 -0
  78. package/dist/im/lark/workflow-cards.js +226 -0
  79. package/dist/im/lark/workflow-cards.js.map +1 -0
  80. package/dist/im/lark/workflow-progress-card.d.ts +76 -0
  81. package/dist/im/lark/workflow-progress-card.d.ts.map +1 -0
  82. package/dist/im/lark/workflow-progress-card.js +279 -0
  83. package/dist/im/lark/workflow-progress-card.js.map +1 -0
  84. package/dist/im/lark/workflow-slash-command.d.ts +92 -0
  85. package/dist/im/lark/workflow-slash-command.d.ts.map +1 -0
  86. package/dist/im/lark/workflow-slash-command.js +185 -0
  87. package/dist/im/lark/workflow-slash-command.js.map +1 -0
  88. package/dist/services/group-creator.d.ts.map +1 -1
  89. package/dist/services/group-creator.js +17 -4
  90. package/dist/services/group-creator.js.map +1 -1
  91. package/dist/services/groups-store.d.ts +11 -0
  92. package/dist/services/groups-store.d.ts.map +1 -1
  93. package/dist/services/groups-store.js +26 -0
  94. package/dist/services/groups-store.js.map +1 -1
  95. package/dist/services/jsonl-cursor.d.ts +12 -0
  96. package/dist/services/jsonl-cursor.d.ts.map +1 -0
  97. package/dist/services/jsonl-cursor.js +45 -0
  98. package/dist/services/jsonl-cursor.js.map +1 -0
  99. package/dist/services/schedule-store.d.ts +35 -0
  100. package/dist/services/schedule-store.d.ts.map +1 -1
  101. package/dist/services/schedule-store.js +108 -1
  102. package/dist/services/schedule-store.js.map +1 -1
  103. package/dist/skills/definitions.d.ts.map +1 -1
  104. package/dist/skills/definitions.js +399 -0
  105. package/dist/skills/definitions.js.map +1 -1
  106. package/dist/types.d.ts +4 -0
  107. package/dist/types.d.ts.map +1 -1
  108. package/dist/utils/cli-usage-limit.d.ts.map +1 -1
  109. package/dist/utils/cli-usage-limit.js +4 -0
  110. package/dist/utils/cli-usage-limit.js.map +1 -1
  111. package/dist/worker.js +118 -14
  112. package/dist/worker.js.map +1 -1
  113. package/dist/workflows/attempt-resume.d.ts +114 -0
  114. package/dist/workflows/attempt-resume.d.ts.map +1 -0
  115. package/dist/workflows/attempt-resume.js +385 -0
  116. package/dist/workflows/attempt-resume.js.map +1 -0
  117. package/dist/workflows/attempt-terminal.d.ts +21 -0
  118. package/dist/workflows/attempt-terminal.d.ts.map +1 -0
  119. package/dist/workflows/attempt-terminal.js +7 -0
  120. package/dist/workflows/attempt-terminal.js.map +1 -0
  121. package/dist/workflows/blob.d.ts +27 -0
  122. package/dist/workflows/blob.d.ts.map +1 -0
  123. package/dist/workflows/blob.js +39 -0
  124. package/dist/workflows/blob.js.map +1 -0
  125. package/dist/workflows/cancel-run.d.ts +45 -0
  126. package/dist/workflows/cancel-run.d.ts.map +1 -0
  127. package/dist/workflows/cancel-run.js +99 -0
  128. package/dist/workflows/cancel-run.js.map +1 -0
  129. package/dist/workflows/cancel.d.ts +111 -0
  130. package/dist/workflows/cancel.d.ts.map +1 -0
  131. package/dist/workflows/cancel.js +120 -0
  132. package/dist/workflows/cancel.js.map +1 -0
  133. package/dist/workflows/catalog.d.ts +60 -0
  134. package/dist/workflows/catalog.d.ts.map +1 -0
  135. package/dist/workflows/catalog.js +119 -0
  136. package/dist/workflows/catalog.js.map +1 -0
  137. package/dist/workflows/cold-attach.d.ts +30 -0
  138. package/dist/workflows/cold-attach.d.ts.map +1 -0
  139. package/dist/workflows/cold-attach.js +40 -0
  140. package/dist/workflows/cold-attach.js.map +1 -0
  141. package/dist/workflows/cold-scan.d.ts +21 -0
  142. package/dist/workflows/cold-scan.d.ts.map +1 -0
  143. package/dist/workflows/cold-scan.js +70 -0
  144. package/dist/workflows/cold-scan.js.map +1 -0
  145. package/dist/workflows/daemon-spawn.d.ts +117 -0
  146. package/dist/workflows/daemon-spawn.d.ts.map +1 -0
  147. package/dist/workflows/daemon-spawn.js +551 -0
  148. package/dist/workflows/daemon-spawn.js.map +1 -0
  149. package/dist/workflows/definition.d.ts +1309 -0
  150. package/dist/workflows/definition.d.ts.map +1 -0
  151. package/dist/workflows/definition.js +334 -0
  152. package/dist/workflows/definition.js.map +1 -0
  153. package/dist/workflows/effect-input.d.ts +4 -0
  154. package/dist/workflows/effect-input.d.ts.map +1 -0
  155. package/dist/workflows/effect-input.js +18 -0
  156. package/dist/workflows/effect-input.js.map +1 -0
  157. package/dist/workflows/events/append.d.ts +77 -0
  158. package/dist/workflows/events/append.d.ts.map +1 -0
  159. package/dist/workflows/events/append.js +214 -0
  160. package/dist/workflows/events/append.js.map +1 -0
  161. package/dist/workflows/events/idempotency.d.ts +77 -0
  162. package/dist/workflows/events/idempotency.d.ts.map +1 -0
  163. package/dist/workflows/events/idempotency.js +116 -0
  164. package/dist/workflows/events/idempotency.js.map +1 -0
  165. package/dist/workflows/events/index.d.ts +7 -0
  166. package/dist/workflows/events/index.d.ts.map +1 -0
  167. package/dist/workflows/events/index.js +7 -0
  168. package/dist/workflows/events/index.js.map +1 -0
  169. package/dist/workflows/events/payloads.d.ts +917 -0
  170. package/dist/workflows/events/payloads.d.ts.map +1 -0
  171. package/dist/workflows/events/payloads.js +337 -0
  172. package/dist/workflows/events/payloads.js.map +1 -0
  173. package/dist/workflows/events/replay.d.ts +238 -0
  174. package/dist/workflows/events/replay.d.ts.map +1 -0
  175. package/dist/workflows/events/replay.js +608 -0
  176. package/dist/workflows/events/replay.js.map +1 -0
  177. package/dist/workflows/events/schema.d.ts +5242 -0
  178. package/dist/workflows/events/schema.d.ts.map +1 -0
  179. package/dist/workflows/events/schema.js +295 -0
  180. package/dist/workflows/events/schema.js.map +1 -0
  181. package/dist/workflows/events/types.d.ts +34 -0
  182. package/dist/workflows/events/types.d.ts.map +1 -0
  183. package/dist/workflows/events/types.js +2 -0
  184. package/dist/workflows/events/types.js.map +1 -0
  185. package/dist/workflows/fanout.d.ts +36 -0
  186. package/dist/workflows/fanout.d.ts.map +1 -0
  187. package/dist/workflows/fanout.js +114 -0
  188. package/dist/workflows/fanout.js.map +1 -0
  189. package/dist/workflows/hostExecutors/botmux-schedule.d.ts +41 -0
  190. package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -0
  191. package/dist/workflows/hostExecutors/botmux-schedule.js +121 -0
  192. package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -0
  193. package/dist/workflows/hostExecutors/feishu-im.d.ts +12 -0
  194. package/dist/workflows/hostExecutors/feishu-im.d.ts.map +1 -0
  195. package/dist/workflows/hostExecutors/feishu-im.js +49 -0
  196. package/dist/workflows/hostExecutors/feishu-im.js.map +1 -0
  197. package/dist/workflows/hostExecutors/feishu-reply.d.ts +24 -0
  198. package/dist/workflows/hostExecutors/feishu-reply.d.ts.map +1 -0
  199. package/dist/workflows/hostExecutors/feishu-reply.js +88 -0
  200. package/dist/workflows/hostExecutors/feishu-reply.js.map +1 -0
  201. package/dist/workflows/hostExecutors/feishu-send.d.ts +23 -0
  202. package/dist/workflows/hostExecutors/feishu-send.d.ts.map +1 -0
  203. package/dist/workflows/hostExecutors/feishu-send.js +124 -0
  204. package/dist/workflows/hostExecutors/feishu-send.js.map +1 -0
  205. package/dist/workflows/hostExecutors/index.d.ts +8 -0
  206. package/dist/workflows/hostExecutors/index.d.ts.map +1 -0
  207. package/dist/workflows/hostExecutors/index.js +8 -0
  208. package/dist/workflows/hostExecutors/index.js.map +1 -0
  209. package/dist/workflows/hostExecutors/protocol.d.ts +42 -0
  210. package/dist/workflows/hostExecutors/protocol.d.ts.map +1 -0
  211. package/dist/workflows/hostExecutors/protocol.js +181 -0
  212. package/dist/workflows/hostExecutors/protocol.js.map +1 -0
  213. package/dist/workflows/hostExecutors/registry.d.ts +10 -0
  214. package/dist/workflows/hostExecutors/registry.d.ts.map +1 -0
  215. package/dist/workflows/hostExecutors/registry.js +36 -0
  216. package/dist/workflows/hostExecutors/registry.js.map +1 -0
  217. package/dist/workflows/hostExecutors/types.d.ts +78 -0
  218. package/dist/workflows/hostExecutors/types.d.ts.map +1 -0
  219. package/dist/workflows/hostExecutors/types.js +2 -0
  220. package/dist/workflows/hostExecutors/types.js.map +1 -0
  221. package/dist/workflows/loader.d.ts +16 -0
  222. package/dist/workflows/loader.d.ts.map +1 -0
  223. package/dist/workflows/loader.js +56 -0
  224. package/dist/workflows/loader.js.map +1 -0
  225. package/dist/workflows/loop.d.ts +50 -0
  226. package/dist/workflows/loop.d.ts.map +1 -0
  227. package/dist/workflows/loop.js +350 -0
  228. package/dist/workflows/loop.js.map +1 -0
  229. package/dist/workflows/ops-projection.d.ts +168 -0
  230. package/dist/workflows/ops-projection.d.ts.map +1 -0
  231. package/dist/workflows/ops-projection.js +707 -0
  232. package/dist/workflows/ops-projection.js.map +1 -0
  233. package/dist/workflows/orchestrator.d.ts +107 -0
  234. package/dist/workflows/orchestrator.d.ts.map +1 -0
  235. package/dist/workflows/orchestrator.js +197 -0
  236. package/dist/workflows/orchestrator.js.map +1 -0
  237. package/dist/workflows/output-binding.d.ts +70 -0
  238. package/dist/workflows/output-binding.d.ts.map +1 -0
  239. package/dist/workflows/output-binding.js +265 -0
  240. package/dist/workflows/output-binding.js.map +1 -0
  241. package/dist/workflows/params.d.ts +61 -0
  242. package/dist/workflows/params.d.ts.map +1 -0
  243. package/dist/workflows/params.js +195 -0
  244. package/dist/workflows/params.js.map +1 -0
  245. package/dist/workflows/resume.d.ts +263 -0
  246. package/dist/workflows/resume.d.ts.map +1 -0
  247. package/dist/workflows/resume.js +808 -0
  248. package/dist/workflows/resume.js.map +1 -0
  249. package/dist/workflows/run-id.d.ts +2 -0
  250. package/dist/workflows/run-id.d.ts.map +1 -0
  251. package/dist/workflows/run-id.js +7 -0
  252. package/dist/workflows/run-id.js.map +1 -0
  253. package/dist/workflows/run-init.d.ts +48 -0
  254. package/dist/workflows/run-init.d.ts.map +1 -0
  255. package/dist/workflows/run-init.js +99 -0
  256. package/dist/workflows/run-init.js.map +1 -0
  257. package/dist/workflows/runs-dir.d.ts +4 -0
  258. package/dist/workflows/runs-dir.d.ts.map +1 -0
  259. package/dist/workflows/runs-dir.js +15 -0
  260. package/dist/workflows/runs-dir.js.map +1 -0
  261. package/dist/workflows/runtime.d.ts +211 -0
  262. package/dist/workflows/runtime.d.ts.map +1 -0
  263. package/dist/workflows/runtime.js +594 -0
  264. package/dist/workflows/runtime.js.map +1 -0
  265. package/dist/workflows/spawn-bot.d.ts +165 -0
  266. package/dist/workflows/spawn-bot.d.ts.map +1 -0
  267. package/dist/workflows/spawn-bot.js +215 -0
  268. package/dist/workflows/spawn-bot.js.map +1 -0
  269. package/dist/workflows/system.d.ts +49 -0
  270. package/dist/workflows/system.d.ts.map +1 -0
  271. package/dist/workflows/system.js +48 -0
  272. package/dist/workflows/system.js.map +1 -0
  273. package/dist/workflows/trigger-run.d.ts +70 -0
  274. package/dist/workflows/trigger-run.d.ts.map +1 -0
  275. package/dist/workflows/trigger-run.js +88 -0
  276. package/dist/workflows/trigger-run.js.map +1 -0
  277. package/dist/workflows/wait.d.ts +120 -0
  278. package/dist/workflows/wait.d.ts.map +1 -0
  279. package/dist/workflows/wait.js +181 -0
  280. package/dist/workflows/wait.js.map +1 -0
  281. package/package.json +3 -3
@@ -0,0 +1,165 @@
1
+ /**
2
+ * WorkerSpawnFn factories for workflow subagent dispatch.
3
+ *
4
+ * Two layers:
5
+ *
6
+ * 1. **Output protocol** — agent-facing convention for delivering the
7
+ * structured output a workflow step needs. The agent emits a
8
+ * well-known marker block in its final assistant message; the
9
+ * workflow runtime parses the marker and extracts JSON.
10
+ *
11
+ * 2. **Factories** —
12
+ * - `createStubSpawnFn(handler)`: dev / test seam. Wraps a
13
+ * user-supplied `(input) => Promise<output>` so tests can
14
+ * drive the orchestrator/loop end-to-end without spinning up
15
+ * a real worker.
16
+ * - `createDaemonSpawnFn(deps)`: real daemon-backed spawn.
17
+ * v0 ships the signature + a TODO body; the daemon wiring
18
+ * (worker-pool fork, transcript capture, kill on timeout)
19
+ * lands as a Slice D follow-up. The interface is stable so
20
+ * the orchestrator and loop don't have to change when the
21
+ * live spawner lands.
22
+ *
23
+ * Why a marker block instead of "last JSON message wins": the agent
24
+ * naturally produces conversational prose plus tool calls plus a final
25
+ * answer; a marker is the only thing that survives prose around it
26
+ * cleanly. The marker matches what the planned `botmux-workflow` skill
27
+ * (UI doc §9) will inject as a tool wrapper.
28
+ */
29
+ import type { BotSnapshot } from './events/payloads.js';
30
+ import type { WorkerSpawnFn, WorkerSpawnInput, WorkerSessionInfo } from './runtime.js';
31
+ export declare const WORKFLOW_OUTPUT_BEGIN = "<WORKFLOW_OUTPUT>";
32
+ export declare const WORKFLOW_OUTPUT_END = "</WORKFLOW_OUTPUT>";
33
+ /**
34
+ * Augment a step's prompt with the output-protocol footer. Callers
35
+ * should prepend / replace the agent prompt with this so the agent
36
+ * knows how to deliver structured output. Idempotent: re-applying
37
+ * doesn't double-stack the footer.
38
+ */
39
+ export declare function withWorkflowOutputProtocol(prompt: string): string;
40
+ export type ParseWorkflowOutputResult = {
41
+ ok: true;
42
+ value: unknown;
43
+ raw: string;
44
+ } | {
45
+ ok: false;
46
+ reason: 'no-marker' | 'unclosed-marker' | 'invalid-json';
47
+ detail?: string;
48
+ };
49
+ /**
50
+ * Extract structured JSON from an agent's final transcript.
51
+ *
52
+ * Strategy — anchored from the **last** `</WORKFLOW_OUTPUT>` (END)
53
+ * marker:
54
+ * - find the most recent END
55
+ * - find the latest BEGIN that precedes that END
56
+ * - everything between is the "final" block
57
+ * - parse as JSON
58
+ *
59
+ * Why anchor from the end: real LLM transcripts often include earlier
60
+ * draft markers (the agent revises mid-stream). A naive
61
+ * "first BEGIN → next END" scan can splice across a malformed early
62
+ * block. Anchoring from the END gives us the last *complete* block.
63
+ *
64
+ * Failure modes:
65
+ * - no BEGIN anywhere → `no-marker`
66
+ * - BEGIN(s) but no END → `unclosed-marker`
67
+ * - block exists but content isn't JSON → `invalid-json` + parser msg
68
+ */
69
+ export declare function parseWorkflowOutput(text: string): ParseWorkflowOutputResult;
70
+ export type StubSpawnHandler = (input: WorkerSpawnInput) => Promise<unknown> | unknown;
71
+ /**
72
+ * Wrap a plain handler so it satisfies `WorkerSpawnFn`. Always
73
+ * returns success; the handler's return value is treated as the
74
+ * structured output. Use for tests / dev where you just need a
75
+ * deterministic answer.
76
+ *
77
+ * NB: this factory is a structured-output shortcut. It does NOT
78
+ * simulate the agent-side marker protocol — the handler receives the
79
+ * raw `WorkerSpawnInput` (not the prompt-with-protocol-footer) and
80
+ * returns the output value directly. Tests that want to exercise
81
+ * `parseWorkflowOutput` should call `createDaemonSpawnFn` with a
82
+ * `runOneShot` that fakes the transcript.
83
+ */
84
+ export declare function createStubSpawnFn(handler: StubSpawnHandler): WorkerSpawnFn;
85
+ /**
86
+ * Input the daemon's one-shot worker invocation needs from the
87
+ * workflow runtime. Carries the FULL frozen-identity + execution-
88
+ * policy contract through — `botSnapshot` is what `runCreated`
89
+ * froze, not whatever bot-registry currently says. Per UI doc §3.4,
90
+ * mutating bots.json after a run starts must not change execution.
91
+ */
92
+ export type DaemonRunOneShotInput = {
93
+ botName: string;
94
+ botSnapshot?: BotSnapshot;
95
+ prompt: string;
96
+ workingDir?: string;
97
+ modelOverrides?: {
98
+ model?: string;
99
+ reasoningEffort?: string;
100
+ };
101
+ toolPolicy?: {
102
+ allow?: string[];
103
+ deny?: string[];
104
+ };
105
+ timeoutMs?: number;
106
+ /** Run/node/activity context — daemon may use to mint a worker id or
107
+ * tag sidecar artifacts. Required so the daemon never has to
108
+ * back-resolve identity from globals. */
109
+ runId: string;
110
+ nodeId: string;
111
+ activityId: string;
112
+ attemptId: string;
113
+ /** Conventional per-attempt execution log path. */
114
+ attemptLogPath?: string;
115
+ /**
116
+ * Cooperative cancel handle (v0.1.4-a slice 2). When `aborted` fires,
117
+ * the daemon-backed runOneShot sends the worker a close message + SIGINT
118
+ * for a graceful shutdown, then escalates to SIGKILL after `cancelGraceMs`.
119
+ * Resolves the outer Promise via `WorkflowSpawnCancelledError` so
120
+ * `createDaemonSpawnFn` can map it to `{ kind: 'cancelled', cancelOriginEventId }`.
121
+ */
122
+ cancelSignal?: AbortSignal;
123
+ };
124
+ /**
125
+ * Sentinel error class used by `runOneShot` to signal cancel — caught by
126
+ * `createDaemonSpawnFn` and translated into a `WorkerSpawnResult` of
127
+ * `kind: 'cancelled'`. Keeping it a distinct class (instead of e.g. a
128
+ * `result.cancelled?` field) lets test stubs reject with it without
129
+ * needing to know the `DaemonRunOneShotResult` shape.
130
+ */
131
+ export declare class WorkflowSpawnCancelledError extends Error {
132
+ readonly cancelOriginEventId: string;
133
+ readonly session?: WorkerSessionInfo;
134
+ constructor(cancelOriginEventId: string, session?: WorkerSessionInfo);
135
+ }
136
+ export type DaemonRunOneShotResult = {
137
+ finalTranscript: string;
138
+ session: WorkerSessionInfo;
139
+ };
140
+ /**
141
+ * Hooks the workflow runtime needs from the daemon to spawn real
142
+ * workers. Caller (daemon startup) builds an instance of this shape
143
+ * and passes it to `createDaemonSpawnFn`.
144
+ *
145
+ * v0 ships only the type; the runtime can keep going with stub spawns
146
+ * while the daemon wiring lands. The split lets us land workflow
147
+ * runtime + cards + loop without blocking on worker.ts integration.
148
+ */
149
+ export type DaemonSpawnDeps = {
150
+ /**
151
+ * Fork a worker bound to the named bot, hand it the prompt, and
152
+ * resolve with the worker's final transcript text (or reject on
153
+ * crash / timeout). Implementation reuses
154
+ * `src/core/worker-pool.ts forkWorker` + a transient root id.
155
+ */
156
+ runOneShot(input: DaemonRunOneShotInput): Promise<DaemonRunOneShotResult>;
157
+ };
158
+ /**
159
+ * Compose a `WorkerSpawnFn` that uses real daemon hooks. The hooks
160
+ * are injected so the workflows package doesn't pull in daemon
161
+ * internals. v0 placeholder is documented; the daemon integration
162
+ * lands as a follow-up.
163
+ */
164
+ export declare function createDaemonSpawnFn(deps: DaemonSpawnDeps): WorkerSpawnFn;
165
+ //# sourceMappingURL=spawn-bot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-bot.d.ts","sourceRoot":"","sources":["../../src/workflows/spawn-bot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAEhB,iBAAiB,EAClB,MAAM,cAAc,CAAC;AAItB,eAAO,MAAM,qBAAqB,sBAAsB,CAAC;AACzD,eAAO,MAAM,mBAAmB,uBAAuB,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,MAAM,MAAM,yBAAyB,GACjC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,iBAAiB,GAAG,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,yBAAyB,CAsB3E;AAiBD,MAAM,MAAM,gBAAgB,GAAG,CAC7B,KAAK,EAAE,gBAAgB,KACpB,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAEhC;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAc1E;AAID;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,UAAU,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;8CAE0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,WAAW,CAAC;CAC5B,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,iBAAiB,CAAC;gBACzB,mBAAmB,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB;CAMrE;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,iBAAiB,CAAC;CAC5B,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;OAKG;IACH,UAAU,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CAC3E,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,eAAe,GAAG,aAAa,CAmDxE"}
@@ -0,0 +1,215 @@
1
+ /**
2
+ * WorkerSpawnFn factories for workflow subagent dispatch.
3
+ *
4
+ * Two layers:
5
+ *
6
+ * 1. **Output protocol** — agent-facing convention for delivering the
7
+ * structured output a workflow step needs. The agent emits a
8
+ * well-known marker block in its final assistant message; the
9
+ * workflow runtime parses the marker and extracts JSON.
10
+ *
11
+ * 2. **Factories** —
12
+ * - `createStubSpawnFn(handler)`: dev / test seam. Wraps a
13
+ * user-supplied `(input) => Promise<output>` so tests can
14
+ * drive the orchestrator/loop end-to-end without spinning up
15
+ * a real worker.
16
+ * - `createDaemonSpawnFn(deps)`: real daemon-backed spawn.
17
+ * v0 ships the signature + a TODO body; the daemon wiring
18
+ * (worker-pool fork, transcript capture, kill on timeout)
19
+ * lands as a Slice D follow-up. The interface is stable so
20
+ * the orchestrator and loop don't have to change when the
21
+ * live spawner lands.
22
+ *
23
+ * Why a marker block instead of "last JSON message wins": the agent
24
+ * naturally produces conversational prose plus tool calls plus a final
25
+ * answer; a marker is the only thing that survives prose around it
26
+ * cleanly. The marker matches what the planned `botmux-workflow` skill
27
+ * (UI doc §9) will inject as a tool wrapper.
28
+ */
29
+ // ─── Output protocol ──────────────────────────────────────────────────────
30
+ export const WORKFLOW_OUTPUT_BEGIN = '<WORKFLOW_OUTPUT>';
31
+ export const WORKFLOW_OUTPUT_END = '</WORKFLOW_OUTPUT>';
32
+ /**
33
+ * Augment a step's prompt with the output-protocol footer. Callers
34
+ * should prepend / replace the agent prompt with this so the agent
35
+ * knows how to deliver structured output. Idempotent: re-applying
36
+ * doesn't double-stack the footer.
37
+ */
38
+ export function withWorkflowOutputProtocol(prompt) {
39
+ if (prompt.includes(WORKFLOW_OUTPUT_BEGIN))
40
+ return prompt;
41
+ return `${prompt}\n\n---\nWhen you finish, emit your final structured output between the markers below as a single valid JSON value. Do not include anything else inside the markers.\n\n${WORKFLOW_OUTPUT_BEGIN}\n{"...your JSON output..."}\n${WORKFLOW_OUTPUT_END}\n`;
42
+ }
43
+ /**
44
+ * Extract structured JSON from an agent's final transcript.
45
+ *
46
+ * Strategy — anchored from the **last** `</WORKFLOW_OUTPUT>` (END)
47
+ * marker:
48
+ * - find the most recent END
49
+ * - find the latest BEGIN that precedes that END
50
+ * - everything between is the "final" block
51
+ * - parse as JSON
52
+ *
53
+ * Why anchor from the end: real LLM transcripts often include earlier
54
+ * draft markers (the agent revises mid-stream). A naive
55
+ * "first BEGIN → next END" scan can splice across a malformed early
56
+ * block. Anchoring from the END gives us the last *complete* block.
57
+ *
58
+ * Failure modes:
59
+ * - no BEGIN anywhere → `no-marker`
60
+ * - BEGIN(s) but no END → `unclosed-marker`
61
+ * - block exists but content isn't JSON → `invalid-json` + parser msg
62
+ */
63
+ export function parseWorkflowOutput(text) {
64
+ const lastEnd = text.lastIndexOf(WORKFLOW_OUTPUT_END);
65
+ if (lastEnd < 0) {
66
+ return text.includes(WORKFLOW_OUTPUT_BEGIN)
67
+ ? { ok: false, reason: 'unclosed-marker' }
68
+ : { ok: false, reason: 'no-marker' };
69
+ }
70
+ const beginBeforeEnd = text.lastIndexOf(WORKFLOW_OUTPUT_BEGIN, lastEnd);
71
+ if (beginBeforeEnd < 0) {
72
+ return { ok: false, reason: 'no-marker' };
73
+ }
74
+ const rawBlock = text.slice(beginBeforeEnd + WORKFLOW_OUTPUT_BEGIN.length, lastEnd).trim();
75
+ const block = sanitizeWorkflowOutputBlock(rawBlock);
76
+ try {
77
+ return { ok: true, value: JSON.parse(block), raw: block };
78
+ }
79
+ catch (err) {
80
+ return {
81
+ ok: false,
82
+ reason: 'invalid-json',
83
+ detail: err instanceof Error ? err.message : String(err),
84
+ };
85
+ }
86
+ }
87
+ function sanitizeWorkflowOutputBlock(block) {
88
+ // PTY fallback transcripts can include terminal control sequences and
89
+ // hard-wrapped CR/LF bytes inside an otherwise valid one-line JSON block.
90
+ // Agent transcript files remain preferred when available; this keeps the
91
+ // fallback from rejecting clean model output just because the terminal view
92
+ // polluted it.
93
+ return block
94
+ .replace(/\x1b\][\s\S]*?(?:\x07|\x1b\\)/g, '')
95
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
96
+ .replace(/[\u0000-\u001f\u007f]/g, '')
97
+ .trim();
98
+ }
99
+ /**
100
+ * Wrap a plain handler so it satisfies `WorkerSpawnFn`. Always
101
+ * returns success; the handler's return value is treated as the
102
+ * structured output. Use for tests / dev where you just need a
103
+ * deterministic answer.
104
+ *
105
+ * NB: this factory is a structured-output shortcut. It does NOT
106
+ * simulate the agent-side marker protocol — the handler receives the
107
+ * raw `WorkerSpawnInput` (not the prompt-with-protocol-footer) and
108
+ * returns the output value directly. Tests that want to exercise
109
+ * `parseWorkflowOutput` should call `createDaemonSpawnFn` with a
110
+ * `runOneShot` that fakes the transcript.
111
+ */
112
+ export function createStubSpawnFn(handler) {
113
+ return async (input) => {
114
+ const startedAt = Date.now();
115
+ const output = await Promise.resolve(handler(input));
116
+ const session = {
117
+ sessionId: `stub-${input.activityId}-${input.attemptId}`,
118
+ botName: input.botName,
119
+ cliId: input.botSnapshot?.cliId,
120
+ workingDir: input.workingDir,
121
+ startedAt,
122
+ endedAt: Date.now(),
123
+ };
124
+ return { kind: 'success', output, session };
125
+ };
126
+ }
127
+ /**
128
+ * Sentinel error class used by `runOneShot` to signal cancel — caught by
129
+ * `createDaemonSpawnFn` and translated into a `WorkerSpawnResult` of
130
+ * `kind: 'cancelled'`. Keeping it a distinct class (instead of e.g. a
131
+ * `result.cancelled?` field) lets test stubs reject with it without
132
+ * needing to know the `DaemonRunOneShotResult` shape.
133
+ */
134
+ export class WorkflowSpawnCancelledError extends Error {
135
+ cancelOriginEventId;
136
+ session;
137
+ constructor(cancelOriginEventId, session) {
138
+ super('workflow spawn cancelled');
139
+ this.name = 'WorkflowSpawnCancelledError';
140
+ this.cancelOriginEventId = cancelOriginEventId;
141
+ this.session = session;
142
+ }
143
+ }
144
+ /**
145
+ * Compose a `WorkerSpawnFn` that uses real daemon hooks. The hooks
146
+ * are injected so the workflows package doesn't pull in daemon
147
+ * internals. v0 placeholder is documented; the daemon integration
148
+ * lands as a follow-up.
149
+ */
150
+ export function createDaemonSpawnFn(deps) {
151
+ return async (input) => {
152
+ const prompt = withWorkflowOutputProtocol(input.prompt);
153
+ let oneShot;
154
+ try {
155
+ oneShot = await deps.runOneShot({
156
+ botName: input.botName,
157
+ botSnapshot: input.botSnapshot,
158
+ prompt,
159
+ workingDir: input.workingDir,
160
+ modelOverrides: input.modelOverrides,
161
+ toolPolicy: input.toolPolicy,
162
+ runId: input.runId,
163
+ nodeId: input.nodeId,
164
+ activityId: input.activityId,
165
+ attemptId: input.attemptId,
166
+ attemptLogPath: input.attemptLogPath,
167
+ cancelSignal: input.cancelSignal,
168
+ });
169
+ }
170
+ catch (err) {
171
+ // Translate the sentinel cancel error into a cancelled spawn result.
172
+ if (err instanceof WorkflowSpawnCancelledError) {
173
+ return {
174
+ kind: 'cancelled',
175
+ cancelOriginEventId: err.cancelOriginEventId,
176
+ session: err.session,
177
+ };
178
+ }
179
+ return {
180
+ kind: 'failure',
181
+ errorCode: 'WorkerCrashed',
182
+ errorClass: 'retryable',
183
+ errorMessage: err instanceof Error ? err.message : String(err),
184
+ };
185
+ }
186
+ const parsed = parseWorkflowOutput(oneShot.finalTranscript);
187
+ if (!parsed.ok) {
188
+ return {
189
+ kind: 'failure',
190
+ errorCode: 'OutputSchemaViolation',
191
+ errorClass: 'manual',
192
+ errorMessage: formatParseFailure(parsed, oneShot.finalTranscript),
193
+ session: oneShot.session,
194
+ };
195
+ }
196
+ return {
197
+ kind: 'success',
198
+ output: parsed.value,
199
+ session: oneShot.session,
200
+ };
201
+ };
202
+ }
203
+ function formatParseFailure(parsed, transcript) {
204
+ // Truncate so log lines don't blow up — full transcript still lives
205
+ // in the worker's terminal log / session sidecar for debugging.
206
+ const SNIPPET_MAX = 240;
207
+ const snippet = transcript.length > SNIPPET_MAX
208
+ ? transcript.slice(0, SNIPPET_MAX) + '…(truncated)'
209
+ : transcript;
210
+ const detail = parsed.detail ? `: ${parsed.detail}` : '';
211
+ return (`Worker output did not contain a parseable ` +
212
+ `${WORKFLOW_OUTPUT_BEGIN}…${WORKFLOW_OUTPUT_END} block ` +
213
+ `(${parsed.reason}${detail}). Transcript: ${JSON.stringify(snippet)}`);
214
+ }
215
+ //# sourceMappingURL=spawn-bot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn-bot.js","sourceRoot":"","sources":["../../src/workflows/spawn-bot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * System / recovery boundary host API (events doc v0.1.2 §2.6, Step 10
3
+ * of 10).
4
+ *
5
+ * Step 10 deliverables (paired with Step 7 resume + Step 9 cancel):
6
+ * - `reportWorkerLost` — write `workerLost { workerId, lostActivityIds }`.
7
+ * Resume already handles the consequence (Step 7):
8
+ * * for each lost activity with dangling effectAttempted → run
9
+ * the reconcile decision tree;
10
+ * * for each lost activity without effectAttempted → write
11
+ * activityFailed { WorkerCrashed, retryable };
12
+ * * dangling waits stay alone (worker death is not a wait
13
+ * resolution).
14
+ * So this module only adds the host-side writer; the recovery
15
+ * loop is already in place.
16
+ *
17
+ * Note on the cancel fan-out (also Step 10): the fan-out lives in
18
+ * replay (not here) — it's a deterministic projection of node/run
19
+ * cancel onto in-flight activities, not a new event-writing path.
20
+ */
21
+ import type { EventLog } from './events/append.js';
22
+ import type { WorkerLostEvent } from './events/types.js';
23
+ export type ReportWorkerLostInput = {
24
+ /** Identifier of the worker that timed out / disconnected. */
25
+ workerId: string;
26
+ /**
27
+ * Activities the runtime registry believes the worker held leases
28
+ * for at the moment of detection. Resume walks these alongside the
29
+ * generic dangling-set fallback — supplying them here lets the
30
+ * runtime tag the event with audit-grade evidence even if the
31
+ * dangling set picks up additional activities later.
32
+ */
33
+ lostActivityIds: string[];
34
+ };
35
+ /**
36
+ * Record a worker timeout / heartbeat loss. Spec §2.6 v0.1.1: this is
37
+ * the system-fault path; explicitly NOT cancel. Resume's recovery
38
+ * loop (Step 7) reads dangling state, not this event — workerLost is
39
+ * primarily an audit trail. Writing it before resume runs is the
40
+ * recommended ordering so the audit reflects why recovery fired.
41
+ *
42
+ * Returns the appended event (with eventId etc).
43
+ *
44
+ * Throws if `lostActivityIds` is empty (spec mandates min 1; an empty
45
+ * list means "worker lost but had no work" which is a no-op the
46
+ * runtime should detect before writing this event).
47
+ */
48
+ export declare function reportWorkerLost(log: EventLog, input: ReportWorkerLostInput): Promise<WorkerLostEvent>;
49
+ //# sourceMappingURL=system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../src/workflows/system.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,MAAM,MAAM,qBAAqB,GAAG;IAClC,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,eAAe,CAAC,CAe1B"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * System / recovery boundary host API (events doc v0.1.2 §2.6, Step 10
3
+ * of 10).
4
+ *
5
+ * Step 10 deliverables (paired with Step 7 resume + Step 9 cancel):
6
+ * - `reportWorkerLost` — write `workerLost { workerId, lostActivityIds }`.
7
+ * Resume already handles the consequence (Step 7):
8
+ * * for each lost activity with dangling effectAttempted → run
9
+ * the reconcile decision tree;
10
+ * * for each lost activity without effectAttempted → write
11
+ * activityFailed { WorkerCrashed, retryable };
12
+ * * dangling waits stay alone (worker death is not a wait
13
+ * resolution).
14
+ * So this module only adds the host-side writer; the recovery
15
+ * loop is already in place.
16
+ *
17
+ * Note on the cancel fan-out (also Step 10): the fan-out lives in
18
+ * replay (not here) — it's a deterministic projection of node/run
19
+ * cancel onto in-flight activities, not a new event-writing path.
20
+ */
21
+ /**
22
+ * Record a worker timeout / heartbeat loss. Spec §2.6 v0.1.1: this is
23
+ * the system-fault path; explicitly NOT cancel. Resume's recovery
24
+ * loop (Step 7) reads dangling state, not this event — workerLost is
25
+ * primarily an audit trail. Writing it before resume runs is the
26
+ * recommended ordering so the audit reflects why recovery fired.
27
+ *
28
+ * Returns the appended event (with eventId etc).
29
+ *
30
+ * Throws if `lostActivityIds` is empty (spec mandates min 1; an empty
31
+ * list means "worker lost but had no work" which is a no-op the
32
+ * runtime should detect before writing this event).
33
+ */
34
+ export async function reportWorkerLost(log, input) {
35
+ if (input.lostActivityIds.length === 0) {
36
+ throw new Error(`reportWorkerLost(${input.workerId}): lostActivityIds is empty — the runtime should skip writing workerLost when the worker held no leases.`);
37
+ }
38
+ return (await log.append({
39
+ runId: log.runId,
40
+ type: 'workerLost',
41
+ actor: 'system',
42
+ payload: {
43
+ workerId: input.workerId,
44
+ lostActivityIds: input.lostActivityIds,
45
+ },
46
+ }));
47
+ }
48
+ //# sourceMappingURL=system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.js","sourceRoot":"","sources":["../../src/workflows/system.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAkBH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAa,EACb,KAA4B;IAE5B,IAAI,KAAK,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,oBAAoB,KAAK,CAAC,QAAQ,0GAA0G,CAC7I,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC;QACvB,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,QAAQ;QACf,OAAO,EAAE;YACP,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,eAAe,EAAE,KAAK,CAAC,eAAe;SACvC;KACF,CAAC,CAAoB,CAAC;AACzB,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared workflow trigger — used by the dashboard catalog `POST .../run`
3
+ * route on the daemon side. Wraps the load/coerce/createRun/drive sequence
4
+ * with injectable deps so the orchestration can be unit-tested without the
5
+ * full daemon process.
6
+ *
7
+ * IM `/workflow run` still goes through `executeWorkflowCommand`; this helper
8
+ * is the dashboard-trigger path that consumes pre-decoded JSON params and
9
+ * fires the workflow loop in the background.
10
+ */
11
+ import { type RawParamInput } from './params.js';
12
+ import { EventLog } from './events/append.js';
13
+ import { type BotResolver } from './run-init.js';
14
+ import type { WorkflowDefinition } from './definition.js';
15
+ import type { WorkflowRuntimeContext, WorkerSpawnFn } from './runtime.js';
16
+ export type TriggerInput = {
17
+ workflowId: string;
18
+ rawParams: Record<string, RawParamInput>;
19
+ chatBinding: {
20
+ chatId: string;
21
+ larkAppId: string;
22
+ };
23
+ initiator: string;
24
+ };
25
+ export type TriggerDeps = {
26
+ spawnSubagent: WorkerSpawnFn;
27
+ botResolver: BotResolver;
28
+ /** Build the ctx scaffolding (hostExecutors, reconcilers, loadEffectInput). */
29
+ makeRuntimeContext: (log: EventLog, def: WorkflowDefinition, spawnSubagent: WorkerSpawnFn) => WorkflowRuntimeContext;
30
+ /** Daemon side registers the ctx so future cancel/approve can find it. */
31
+ attachRuntime: (runId: string, ctx: WorkflowRuntimeContext) => {
32
+ ready?: Promise<unknown>;
33
+ };
34
+ /** Fire-and-forget loop drive — daemon owns the actual scheduling. */
35
+ driveRun: (runId: string) => void;
36
+ /** Test seam: override the file lookup. */
37
+ loadWorkflowDefinition?: (workflowId: string) => Promise<WorkflowDefinition>;
38
+ /** Test seam: deterministic run id. */
39
+ makeRunId?: (def: WorkflowDefinition) => string;
40
+ /** Test seam: explicit runs dir override. */
41
+ makeEventLog?: (runId: string) => EventLog;
42
+ };
43
+ export type TriggerSuccess = {
44
+ ok: true;
45
+ runId: string;
46
+ workflowId: string;
47
+ status: string;
48
+ lastSeq: number;
49
+ };
50
+ export type TriggerFailure = {
51
+ ok: false;
52
+ error: 'unknown_workflow';
53
+ message: string;
54
+ } | {
55
+ ok: false;
56
+ error: 'invalid_params';
57
+ message: string;
58
+ issues: Array<{
59
+ path: string[];
60
+ code: string;
61
+ message: string;
62
+ }>;
63
+ } | {
64
+ ok: false;
65
+ error: 'load_definition_failed' | 'internal_error';
66
+ message: string;
67
+ };
68
+ export type TriggerResult = TriggerSuccess | TriggerFailure;
69
+ export declare function triggerWorkflowRun(input: TriggerInput, deps: TriggerDeps): Promise<TriggerResult>;
70
+ //# sourceMappingURL=trigger-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trigger-run.d.ts","sourceRoot":"","sources":["../../src/workflows/trigger-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAA4C,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAG9C,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE1E,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,WAAW,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;IACzB,+EAA+E;IAC/E,kBAAkB,EAAE,CAClB,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,kBAAkB,EACvB,aAAa,EAAE,aAAa,KACzB,sBAAsB,CAAC;IAC5B,0EAA0E;IAC1E,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,sBAAsB,KAAK;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAC5F,sEAAsE;IACtE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,2CAA2C;IAC3C,sBAAsB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC7E,uCAAuC;IACvC,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,MAAM,CAAC;IAChD,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,QAAQ,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GACtB;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,gBAAgB,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,wBAAwB,GAAG,gBAAgB,CAAC;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,cAAc,CAAC;AAE5D,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,aAAa,CAAC,CAmExB"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Shared workflow trigger — used by the dashboard catalog `POST .../run`
3
+ * route on the daemon side. Wraps the load/coerce/createRun/drive sequence
4
+ * with injectable deps so the orchestration can be unit-tested without the
5
+ * full daemon process.
6
+ *
7
+ * IM `/workflow run` still goes through `executeWorkflowCommand`; this helper
8
+ * is the dashboard-trigger path that consumes pre-decoded JSON params and
9
+ * fires the workflow loop in the background.
10
+ */
11
+ import { coerceWorkflowParams, ParamCoerceFailure } from './params.js';
12
+ import { EventLog } from './events/append.js';
13
+ import { loadWorkflowDefinition as defaultLoadWorkflowDefinition } from './loader.js';
14
+ import { mintWorkflowRunId } from './run-id.js';
15
+ import { createRun } from './run-init.js';
16
+ import { replay } from './events/replay.js';
17
+ import { getRunsDir } from './runs-dir.js';
18
+ export async function triggerWorkflowRun(input, deps) {
19
+ const loadDef = deps.loadWorkflowDefinition ?? defaultLoadWorkflowDefinition;
20
+ let def;
21
+ try {
22
+ def = await loadDef(input.workflowId);
23
+ }
24
+ catch (err) {
25
+ const message = err instanceof Error ? err.message : String(err);
26
+ if (message.startsWith(`Workflow '${input.workflowId}' not found`)) {
27
+ return { ok: false, error: 'unknown_workflow', message };
28
+ }
29
+ return { ok: false, error: 'load_definition_failed', message };
30
+ }
31
+ let coerced;
32
+ try {
33
+ coerced = coerceWorkflowParams(def, input.rawParams);
34
+ }
35
+ catch (err) {
36
+ if (err instanceof ParamCoerceFailure) {
37
+ return {
38
+ ok: false,
39
+ error: 'invalid_params',
40
+ message: err.message,
41
+ issues: err.issues.map((i) => ({
42
+ path: i.name ? [i.name] : [],
43
+ code: i.code,
44
+ message: i.message,
45
+ })),
46
+ };
47
+ }
48
+ const message = err instanceof Error ? err.message : String(err);
49
+ return { ok: false, error: 'internal_error', message };
50
+ }
51
+ try {
52
+ const runId = (deps.makeRunId ?? ((d) => mintWorkflowRunId(d.workflowId, Date.now())))(def);
53
+ const log = deps.makeEventLog ? deps.makeEventLog(runId) : new EventLog(runId, getRunsDir());
54
+ const ctx = deps.makeRuntimeContext(log, def, deps.spawnSubagent);
55
+ await createRun(log, {
56
+ def,
57
+ params: coerced,
58
+ initiator: input.initiator,
59
+ botResolver: deps.botResolver,
60
+ chatBinding: input.chatBinding,
61
+ });
62
+ const watcher = deps.attachRuntime(runId, ctx);
63
+ if (watcher.ready) {
64
+ try {
65
+ await watcher.ready;
66
+ }
67
+ catch {
68
+ // watcher start failures are logged by the daemon; the run is still
69
+ // valid and will keep producing events the watcher can re-pick on
70
+ // restart, so don't abort the trigger here.
71
+ }
72
+ }
73
+ deps.driveRun(runId);
74
+ const snapshot = replay(await log.readAll());
75
+ return {
76
+ ok: true,
77
+ runId,
78
+ workflowId: def.workflowId,
79
+ status: snapshot.run.status,
80
+ lastSeq: snapshot.lastSeq,
81
+ };
82
+ }
83
+ catch (err) {
84
+ const message = err instanceof Error ? err.message : String(err);
85
+ return { ok: false, error: 'internal_error', message };
86
+ }
87
+ }
88
+ //# sourceMappingURL=trigger-run.js.map