botmux 2.33.0 → 2.33.1

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 +211 -4
  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,214 @@
1
+ import { promises as fs, existsSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { INLINE_PAYLOAD_MAX_BYTES, PayloadRefSchema, isPayloadRef, parseEvent, } from './schema.js';
4
+ import { withFileLock } from '../../utils/file-lock.js';
5
+ // ─── Mutex (per-runId append serialization, in-process) ─────────────────────
6
+ /**
7
+ * Minimal promise-chain mutex. Single-process serialization layer.
8
+ */
9
+ class Mutex {
10
+ tail = Promise.resolve();
11
+ async run(fn) {
12
+ const prior = this.tail;
13
+ let release;
14
+ this.tail = new Promise((r) => {
15
+ release = r;
16
+ });
17
+ try {
18
+ await prior;
19
+ return await fn();
20
+ }
21
+ finally {
22
+ release();
23
+ }
24
+ }
25
+ }
26
+ /**
27
+ * Module-level mutex map keyed by runId. Codex round 4 fix: a single
28
+ * instance's mutex doesn't protect against two `new EventLog(runId, base)`
29
+ * instances inside the same process — they would each hold a fresh mutex
30
+ * and race on seq assignment. Sharing the mutex per-runId at module scope
31
+ * closes that hole. Cross-process is closed by `withFileLock` (below).
32
+ */
33
+ const RUN_MUTEXES = new Map();
34
+ function getRunMutex(runId) {
35
+ let m = RUN_MUTEXES.get(runId);
36
+ if (!m) {
37
+ m = new Mutex();
38
+ RUN_MUTEXES.set(runId, m);
39
+ }
40
+ return m;
41
+ }
42
+ // ─── EventLog ───────────────────────────────────────────────────────────────
43
+ export class EventLog {
44
+ runId;
45
+ runDir;
46
+ eventsFile;
47
+ blobDir;
48
+ // Cached seq + file metadata for cross-process change detection.
49
+ seq = 0;
50
+ seqLoaded = false;
51
+ cachedMtimeMs = 0;
52
+ cachedSize = 0;
53
+ constructor(runId, baseDir) {
54
+ if (!runId)
55
+ throw new Error('EventLog: runId required');
56
+ if (!baseDir)
57
+ throw new Error('EventLog: baseDir required');
58
+ this.runId = runId;
59
+ this.runDir = join(baseDir, runId);
60
+ this.eventsFile = join(this.runDir, 'events.ndjson');
61
+ this.blobDir = join(this.runDir, 'blobs');
62
+ if (!existsSync(this.runDir))
63
+ mkdirSync(this.runDir, { recursive: true });
64
+ if (!existsSync(this.blobDir))
65
+ mkdirSync(this.blobDir, { recursive: true });
66
+ }
67
+ /**
68
+ * Append one event. Atomic across:
69
+ * 1. all EventLog instances for the same runId in this process
70
+ * (module-level mutex map), and
71
+ * 2. other OS processes touching the same events file
72
+ * (`withFileLock` over `events.ndjson.lock`).
73
+ *
74
+ * Codex round 4 finding 1: payload envelope MUST be inline. Large
75
+ * business data goes through `OutputRef`-shaped fields (e.g.
76
+ * `runCreated.inputRef`, `activitySucceeded.outputRef`); the caller is
77
+ * responsible for writing the blob and passing a fully-formed
78
+ * `OutputRef` inline. The append path no longer auto-spills payloads —
79
+ * doing so silently broke replay for any ref-payload event because the
80
+ * existing replay projection unconditionally skipped the ref branch.
81
+ *
82
+ * Payload-ref payloads (`{ ref, bytes, schemaVersion }`) are still
83
+ * supported for callers who genuinely need to ref-out the envelope
84
+ * payload (e.g. a custom dashboard projection), but the caller must
85
+ * supply both the blob and `payloadHash` upfront; this path is not
86
+ * exercised by the v0 runtime.
87
+ */
88
+ async append(draft) {
89
+ return getRunMutex(this.runId).run(() => withFileLock(this.eventsFile, () => this.appendLocked(draft)));
90
+ }
91
+ async appendLocked(draft) {
92
+ await this.refreshSeqIfStale();
93
+ const nextSeq = this.seq + 1;
94
+ const timestamp = draft.timestamp ?? Date.now();
95
+ const candidate = {
96
+ eventId: `${this.runId}-${nextSeq}`,
97
+ runId: this.runId,
98
+ timestamp,
99
+ type: draft.type,
100
+ schemaVersion: 1,
101
+ actor: draft.actor,
102
+ payload: draft.payload,
103
+ };
104
+ if ('payloadHash' in draft && draft.payloadHash !== undefined) {
105
+ candidate.payloadHash = draft.payloadHash;
106
+ }
107
+ // Reject inline payloads that exceed the cap. The runtime should
108
+ // restructure to use `OutputRef`-shaped fields for large business
109
+ // data; envelope payloads are metadata + small refs only.
110
+ if (!isPayloadRef(draft.payload)) {
111
+ const inlineSize = Buffer.byteLength(JSON.stringify(draft.payload), 'utf-8');
112
+ if (inlineSize > INLINE_PAYLOAD_MAX_BYTES) {
113
+ throw new Error(`EventLog(${this.runId}).append: inline payload (${inlineSize} bytes) exceeds ` +
114
+ `INLINE_PAYLOAD_MAX_BYTES (${INLINE_PAYLOAD_MAX_BYTES}). Restructure large fields ` +
115
+ `to use OutputRef-shaped fields (events doc v0.1.2 §3.1) instead of stuffing them ` +
116
+ `into the envelope payload.`);
117
+ }
118
+ }
119
+ const parsed = parseEvent(candidate);
120
+ const line = JSON.stringify(parsed) + '\n';
121
+ await fs.appendFile(this.eventsFile, line, 'utf-8');
122
+ // Cache update — we just wrote, so size grew. Stat would also work.
123
+ const stat = await fs.stat(this.eventsFile);
124
+ this.seq = nextSeq;
125
+ this.cachedMtimeMs = stat.mtimeMs;
126
+ this.cachedSize = stat.size;
127
+ this.seqLoaded = true;
128
+ return parsed;
129
+ }
130
+ /**
131
+ * Read all events in append order. Used by replay (events doc §5.2)
132
+ * and seq recovery on restart. Returns [] if the log doesn't exist
133
+ * yet.
134
+ *
135
+ * Throws if any line fails schema validation — events doc treats the
136
+ * log as authoritative and corruption should fail loud, not silently
137
+ * skip lines.
138
+ */
139
+ async readAll() {
140
+ if (!existsSync(this.eventsFile))
141
+ return [];
142
+ const content = await fs.readFile(this.eventsFile, 'utf-8');
143
+ const events = [];
144
+ let lineNo = 0;
145
+ for (const raw of content.split('\n')) {
146
+ lineNo++;
147
+ if (!raw)
148
+ continue;
149
+ try {
150
+ const obj = JSON.parse(raw);
151
+ events.push(parseEvent(obj));
152
+ }
153
+ catch (err) {
154
+ throw new Error(`EventLog(${this.runId}): corrupt event at line ${lineNo}: ${err instanceof Error ? err.message : String(err)}`);
155
+ }
156
+ }
157
+ return events;
158
+ }
159
+ /**
160
+ * Read the blob referenced by a ref-payload event. Used when a caller
161
+ * elected to spill their own OutputRef payload to disk; not used by
162
+ * envelope-payload paths since v0.1.2 round-4 disallows envelope spill.
163
+ */
164
+ async readBlob(ref) {
165
+ return fs.readFile(ref);
166
+ }
167
+ /** Current seq counter — exposed for tests / dashboard. */
168
+ async currentSeq() {
169
+ return getRunMutex(this.runId).run(() => withFileLock(this.eventsFile, async () => {
170
+ await this.refreshSeqIfStale();
171
+ return this.seq;
172
+ }));
173
+ }
174
+ /**
175
+ * Refresh `this.seq` if the events file has changed since we last loaded.
176
+ * Stat is cheap; full re-scan only fires when the cached mtime/size differ
177
+ * from disk — protects against another process having appended since our
178
+ * last write.
179
+ */
180
+ async refreshSeqIfStale() {
181
+ if (!existsSync(this.eventsFile)) {
182
+ this.seq = 0;
183
+ this.seqLoaded = true;
184
+ this.cachedMtimeMs = 0;
185
+ this.cachedSize = 0;
186
+ return;
187
+ }
188
+ const stat = await fs.stat(this.eventsFile);
189
+ if (this.seqLoaded &&
190
+ stat.mtimeMs === this.cachedMtimeMs &&
191
+ stat.size === this.cachedSize) {
192
+ return;
193
+ }
194
+ // Rescan from disk. Linear in events for v0; future optimization
195
+ // could read from end-of-file backwards to find the last seq line.
196
+ const events = await this.readAll();
197
+ let maxSeq = 0;
198
+ for (const e of events) {
199
+ const m = e.eventId.match(/-(\d+)$/);
200
+ if (m) {
201
+ const s = parseInt(m[1], 10);
202
+ if (s > maxSeq)
203
+ maxSeq = s;
204
+ }
205
+ }
206
+ this.seq = maxSeq;
207
+ this.cachedMtimeMs = stat.mtimeMs;
208
+ this.cachedSize = stat.size;
209
+ this.seqLoaded = true;
210
+ }
211
+ }
212
+ // ─── Reexport schemas the EventLog returns, for ergonomic call sites ────────
213
+ export { PayloadRefSchema, INLINE_PAYLOAD_MAX_BYTES };
214
+ //# sourceMappingURL=append.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append.js","sourceRoot":"","sources":["../../../src/workflows/events/append.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Serialize `value` to canonical JSON (deterministic across object key order
3
+ * and identical-content equality). Used by:
4
+ * - `computeInputHash` to derive inputHash for attempt input immutability
5
+ * checks (events doc §4.2 + §3.6).
6
+ * - any code path that needs hash-stable serialization of structured data.
7
+ *
8
+ * Rules:
9
+ * - Object keys are sorted ascending (lexicographic on UTF-16 code units,
10
+ * matching JS's default Array.sort).
11
+ * - `undefined` properties are dropped (matches `JSON.stringify` behaviour
12
+ * for plain objects).
13
+ * - `null` is preserved as `"null"`.
14
+ * - Arrays preserve order — they're ordered data, not bags.
15
+ * - Strings, numbers, booleans use `JSON.stringify` (handles escapes).
16
+ * - Non-finite numbers (`NaN`, `±Infinity`), BigInt, Function, Symbol,
17
+ * Date, and class instances throw. Dates would need a project-wide
18
+ * decision on epoch-ms vs ISO string; v0 throws so the caller is forced
19
+ * to normalize upstream.
20
+ */
21
+ export declare function canonicalJson(value: unknown): string;
22
+ /**
23
+ * The 5-tuple that anchors workflow idempotency (events doc §3.2 / §4.2).
24
+ * Each attempt is uniquely identified by this combination; the derived
25
+ * key feeds into provider uuid fields (Feishu IM uuid, schedule-store id).
26
+ */
27
+ export type IdempotencyKeyTuple = {
28
+ workflowId: string;
29
+ revisionId: string;
30
+ runId: string;
31
+ nodeId: string;
32
+ attemptId: string;
33
+ };
34
+ export type DeriveIdempotencyKeyOptions = {
35
+ /**
36
+ * String prefix prepended to the truncated hash. Defaults to `wf_`,
37
+ * which keeps workflow-generated ids in a separate namespace from
38
+ * randomUUID-derived ids (events doc §2.2 schedule case). Pass empty
39
+ * string to disable. Must be ≤ `maxLength - 1`.
40
+ */
41
+ namespace?: string;
42
+ /**
43
+ * Max output length. Defaults to 50 to match Feishu IM uuid field's
44
+ * documented upper bound (spike report §1.2).
45
+ */
46
+ maxLength?: number;
47
+ };
48
+ /**
49
+ * Deterministically derive an idempotency key from the 5-tuple. Same tuple
50
+ * always produces the same key; collisions are bounded by the truncated
51
+ * SHA-256 birthday term. With default namespace `wf_` and maxLength 50:
52
+ *
53
+ * key = "wf_" + sha256(workflowId:revisionId:runId:nodeId:attemptId)[:47]
54
+ *
55
+ * 47 hex chars = 188 bits of entropy, ample for collision-free workflow
56
+ * lifetimes.
57
+ */
58
+ export declare function deriveIdempotencyKey(tuple: IdempotencyKeyTuple, opts?: DeriveIdempotencyKeyOptions): string;
59
+ /**
60
+ * Hash an attempt's canonical input. Returned in `sha256:<hex>` form so
61
+ * it slots directly into event payloads (`effectAttempted.inputHash` and
62
+ * resume reconcile evidence).
63
+ *
64
+ * The hash is over the **full canonical input** — for send/reply that
65
+ * includes `receive_id`, `root_message_id?`, `msg_type`, `content`; for
66
+ * schedule it includes the entire create-task input. Spike report §1.3
67
+ * + reply test 3c proved partial hashes leak silent state drift.
68
+ *
69
+ * inputHash is **separate** from idempotencyKey by design (codex v0.1.1
70
+ * round 2): mixing content into the key would convert "input changed" into
71
+ * "new effect", bypassing attempt-immutability. inputHash lives as a
72
+ * post-fact validator: same attemptId must always produce the same
73
+ * inputHash; mismatches trigger `IdempotencyInputMismatch` (events doc
74
+ * §4.2).
75
+ */
76
+ export declare function computeInputHash(input: unknown): string;
77
+ //# sourceMappingURL=idempotency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../../src/workflows/events/idempotency.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAuCD;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,mBAAmB,EAC1B,IAAI,GAAE,2BAAgC,GACrC,MAAM,CAuBR;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAIvD"}
@@ -0,0 +1,116 @@
1
+ import { createHash } from 'node:crypto';
2
+ // ─── canonical JSON ─────────────────────────────────────────────────────────
3
+ /**
4
+ * Serialize `value` to canonical JSON (deterministic across object key order
5
+ * and identical-content equality). Used by:
6
+ * - `computeInputHash` to derive inputHash for attempt input immutability
7
+ * checks (events doc §4.2 + §3.6).
8
+ * - any code path that needs hash-stable serialization of structured data.
9
+ *
10
+ * Rules:
11
+ * - Object keys are sorted ascending (lexicographic on UTF-16 code units,
12
+ * matching JS's default Array.sort).
13
+ * - `undefined` properties are dropped (matches `JSON.stringify` behaviour
14
+ * for plain objects).
15
+ * - `null` is preserved as `"null"`.
16
+ * - Arrays preserve order — they're ordered data, not bags.
17
+ * - Strings, numbers, booleans use `JSON.stringify` (handles escapes).
18
+ * - Non-finite numbers (`NaN`, `±Infinity`), BigInt, Function, Symbol,
19
+ * Date, and class instances throw. Dates would need a project-wide
20
+ * decision on epoch-ms vs ISO string; v0 throws so the caller is forced
21
+ * to normalize upstream.
22
+ */
23
+ export function canonicalJson(value) {
24
+ return serialize(value);
25
+ }
26
+ function serialize(v) {
27
+ if (v === null)
28
+ return 'null';
29
+ if (typeof v === 'boolean')
30
+ return v ? 'true' : 'false';
31
+ if (typeof v === 'number') {
32
+ if (!Number.isFinite(v)) {
33
+ throw new Error(`canonicalJson: non-finite number (${String(v)}) not serializable`);
34
+ }
35
+ return JSON.stringify(v);
36
+ }
37
+ if (typeof v === 'string')
38
+ return JSON.stringify(v);
39
+ if (Array.isArray(v)) {
40
+ return '[' + v.map((x) => serialize(x)).join(',') + ']';
41
+ }
42
+ if (typeof v === 'object') {
43
+ // Reject anything that's not a plain object. This guards against Date,
44
+ // Map, Set, Buffer, class instances etc. that would otherwise leak
45
+ // internal state into the hash.
46
+ if (Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null) {
47
+ throw new Error(`canonicalJson: non-plain-object (${v?.constructor?.name ?? 'unknown'}) not serializable — normalize upstream`);
48
+ }
49
+ const obj = v;
50
+ const keys = Object.keys(obj)
51
+ .filter((k) => obj[k] !== undefined)
52
+ .sort();
53
+ return '{' + keys.map((k) => JSON.stringify(k) + ':' + serialize(obj[k])).join(',') + '}';
54
+ }
55
+ if (typeof v === 'bigint') {
56
+ throw new Error('canonicalJson: bigint not serializable — convert to string upstream');
57
+ }
58
+ // function, symbol, undefined at the root
59
+ throw new Error(`canonicalJson: cannot serialize ${typeof v}`);
60
+ }
61
+ /**
62
+ * Deterministically derive an idempotency key from the 5-tuple. Same tuple
63
+ * always produces the same key; collisions are bounded by the truncated
64
+ * SHA-256 birthday term. With default namespace `wf_` and maxLength 50:
65
+ *
66
+ * key = "wf_" + sha256(workflowId:revisionId:runId:nodeId:attemptId)[:47]
67
+ *
68
+ * 47 hex chars = 188 bits of entropy, ample for collision-free workflow
69
+ * lifetimes.
70
+ */
71
+ export function deriveIdempotencyKey(tuple, opts = {}) {
72
+ const namespace = opts.namespace ?? 'wf_';
73
+ const maxLength = opts.maxLength ?? 50;
74
+ if (namespace.length >= maxLength) {
75
+ throw new Error(`deriveIdempotencyKey: namespace '${namespace}' (${namespace.length} chars) leaves no room for hash in maxLength ${maxLength}`);
76
+ }
77
+ for (const [k, v] of Object.entries(tuple)) {
78
+ if (typeof v !== 'string' || v.length === 0) {
79
+ throw new Error(`deriveIdempotencyKey: tuple.${k} must be non-empty string, got ${String(v)}`);
80
+ }
81
+ }
82
+ // Codex round 4 minor: original implementation `${a}:${b}:${c}:${d}:${e}`
83
+ // had a theoretical collision: two distinct tuples whose fields happen
84
+ // to span `:` boundaries differently can produce the same seed (e.g.
85
+ // `{a:'x:y', b:'z', ...}` collides with `{a:'x', b:'y:z', ...}`).
86
+ // Hashing canonicalJson(tuple) — same canonical form used everywhere
87
+ // else for hash-stable serialization — closes the hole without any
88
+ // call-site change.
89
+ const seed = canonicalJson(tuple);
90
+ const hash = createHash('sha256').update(seed, 'utf-8').digest('hex');
91
+ return namespace + hash.substring(0, maxLength - namespace.length);
92
+ }
93
+ // ─── input hash (canonical full-field sha256) ───────────────────────────────
94
+ /**
95
+ * Hash an attempt's canonical input. Returned in `sha256:<hex>` form so
96
+ * it slots directly into event payloads (`effectAttempted.inputHash` and
97
+ * resume reconcile evidence).
98
+ *
99
+ * The hash is over the **full canonical input** — for send/reply that
100
+ * includes `receive_id`, `root_message_id?`, `msg_type`, `content`; for
101
+ * schedule it includes the entire create-task input. Spike report §1.3
102
+ * + reply test 3c proved partial hashes leak silent state drift.
103
+ *
104
+ * inputHash is **separate** from idempotencyKey by design (codex v0.1.1
105
+ * round 2): mixing content into the key would convert "input changed" into
106
+ * "new effect", bypassing attempt-immutability. inputHash lives as a
107
+ * post-fact validator: same attemptId must always produce the same
108
+ * inputHash; mismatches trigger `IdempotencyInputMismatch` (events doc
109
+ * §4.2).
110
+ */
111
+ export function computeInputHash(input) {
112
+ const canonical = canonicalJson(input);
113
+ const hex = createHash('sha256').update(canonical, 'utf-8').digest('hex');
114
+ return `sha256:${hex}`;
115
+ }
116
+ //# sourceMappingURL=idempotency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/workflows/events/idempotency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export * from './payloads.js';
2
+ export * from './schema.js';
3
+ export * from './types.js';
4
+ export * from './append.js';
5
+ export * from './replay.js';
6
+ export * from './idempotency.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/workflows/events/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export * from './payloads.js';
2
+ export * from './schema.js';
3
+ export * from './types.js';
4
+ export * from './append.js';
5
+ export * from './replay.js';
6
+ export * from './idempotency.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/workflows/events/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC"}