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.
- package/README.en.md +12 -1
- package/README.md +45 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +11 -0
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/cli/bots-list-output.d.ts +21 -0
- package/dist/cli/bots-list-output.d.ts.map +1 -0
- package/dist/cli/bots-list-output.js +23 -0
- package/dist/cli/bots-list-output.js.map +1 -0
- package/dist/cli/workflow.d.ts +13 -0
- package/dist/cli/workflow.d.ts.map +1 -0
- package/dist/cli/workflow.js +781 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/cli.js +69 -14
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +219 -6
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/session-manager.d.ts +6 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +22 -12
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/worker-pool.d.ts +13 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +100 -6
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +884 -3
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +36 -0
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +22 -0
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/web/app.js +20 -1
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +356 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/workflow-catalog.d.ts +2 -0
- package/dist/dashboard/web/workflow-catalog.d.ts.map +1 -0
- package/dist/dashboard/web/workflow-catalog.js +323 -0
- package/dist/dashboard/web/workflow-catalog.js.map +1 -0
- package/dist/dashboard/web/workflows.d.ts +2 -0
- package/dist/dashboard/web/workflows.d.ts.map +1 -0
- package/dist/dashboard/web/workflows.js +1618 -0
- package/dist/dashboard/web/workflows.js.map +1 -0
- package/dist/dashboard/workflow-api.d.ts +23 -0
- package/dist/dashboard/workflow-api.d.ts.map +1 -0
- package/dist/dashboard/workflow-api.js +463 -0
- package/dist/dashboard/workflow-api.js.map +1 -0
- package/dist/dashboard-web/app.js +494 -199
- package/dist/dashboard-web/index.html +1 -0
- package/dist/dashboard-web/style.css +160 -6
- package/dist/dashboard-web/terminal-replay.html +227 -0
- package/dist/dashboard.js +29 -12
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +12 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +12 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +3 -0
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +27 -1
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +19 -2
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +21 -2
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/workflow-card-handler.d.ts +50 -0
- package/dist/im/lark/workflow-card-handler.d.ts.map +1 -0
- package/dist/im/lark/workflow-card-handler.js +152 -0
- package/dist/im/lark/workflow-card-handler.js.map +1 -0
- package/dist/im/lark/workflow-cards.d.ts +46 -0
- package/dist/im/lark/workflow-cards.d.ts.map +1 -0
- package/dist/im/lark/workflow-cards.js +226 -0
- package/dist/im/lark/workflow-cards.js.map +1 -0
- package/dist/im/lark/workflow-progress-card.d.ts +76 -0
- package/dist/im/lark/workflow-progress-card.d.ts.map +1 -0
- package/dist/im/lark/workflow-progress-card.js +279 -0
- package/dist/im/lark/workflow-progress-card.js.map +1 -0
- package/dist/im/lark/workflow-slash-command.d.ts +92 -0
- package/dist/im/lark/workflow-slash-command.d.ts.map +1 -0
- package/dist/im/lark/workflow-slash-command.js +185 -0
- package/dist/im/lark/workflow-slash-command.js.map +1 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +17 -4
- package/dist/services/group-creator.js.map +1 -1
- package/dist/services/groups-store.d.ts +11 -0
- package/dist/services/groups-store.d.ts.map +1 -1
- package/dist/services/groups-store.js +26 -0
- package/dist/services/groups-store.js.map +1 -1
- package/dist/services/jsonl-cursor.d.ts +12 -0
- package/dist/services/jsonl-cursor.d.ts.map +1 -0
- package/dist/services/jsonl-cursor.js +45 -0
- package/dist/services/jsonl-cursor.js.map +1 -0
- package/dist/services/schedule-store.d.ts +35 -0
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +108 -1
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +399 -0
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/cli-usage-limit.d.ts.map +1 -1
- package/dist/utils/cli-usage-limit.js +4 -0
- package/dist/utils/cli-usage-limit.js.map +1 -1
- package/dist/worker.js +118 -14
- package/dist/worker.js.map +1 -1
- package/dist/workflows/attempt-resume.d.ts +114 -0
- package/dist/workflows/attempt-resume.d.ts.map +1 -0
- package/dist/workflows/attempt-resume.js +385 -0
- package/dist/workflows/attempt-resume.js.map +1 -0
- package/dist/workflows/attempt-terminal.d.ts +21 -0
- package/dist/workflows/attempt-terminal.d.ts.map +1 -0
- package/dist/workflows/attempt-terminal.js +7 -0
- package/dist/workflows/attempt-terminal.js.map +1 -0
- package/dist/workflows/blob.d.ts +27 -0
- package/dist/workflows/blob.d.ts.map +1 -0
- package/dist/workflows/blob.js +39 -0
- package/dist/workflows/blob.js.map +1 -0
- package/dist/workflows/cancel-run.d.ts +45 -0
- package/dist/workflows/cancel-run.d.ts.map +1 -0
- package/dist/workflows/cancel-run.js +99 -0
- package/dist/workflows/cancel-run.js.map +1 -0
- package/dist/workflows/cancel.d.ts +111 -0
- package/dist/workflows/cancel.d.ts.map +1 -0
- package/dist/workflows/cancel.js +120 -0
- package/dist/workflows/cancel.js.map +1 -0
- package/dist/workflows/catalog.d.ts +60 -0
- package/dist/workflows/catalog.d.ts.map +1 -0
- package/dist/workflows/catalog.js +119 -0
- package/dist/workflows/catalog.js.map +1 -0
- package/dist/workflows/cold-attach.d.ts +30 -0
- package/dist/workflows/cold-attach.d.ts.map +1 -0
- package/dist/workflows/cold-attach.js +40 -0
- package/dist/workflows/cold-attach.js.map +1 -0
- package/dist/workflows/cold-scan.d.ts +21 -0
- package/dist/workflows/cold-scan.d.ts.map +1 -0
- package/dist/workflows/cold-scan.js +70 -0
- package/dist/workflows/cold-scan.js.map +1 -0
- package/dist/workflows/daemon-spawn.d.ts +117 -0
- package/dist/workflows/daemon-spawn.d.ts.map +1 -0
- package/dist/workflows/daemon-spawn.js +551 -0
- package/dist/workflows/daemon-spawn.js.map +1 -0
- package/dist/workflows/definition.d.ts +1309 -0
- package/dist/workflows/definition.d.ts.map +1 -0
- package/dist/workflows/definition.js +334 -0
- package/dist/workflows/definition.js.map +1 -0
- package/dist/workflows/effect-input.d.ts +4 -0
- package/dist/workflows/effect-input.d.ts.map +1 -0
- package/dist/workflows/effect-input.js +18 -0
- package/dist/workflows/effect-input.js.map +1 -0
- package/dist/workflows/events/append.d.ts +77 -0
- package/dist/workflows/events/append.d.ts.map +1 -0
- package/dist/workflows/events/append.js +214 -0
- package/dist/workflows/events/append.js.map +1 -0
- package/dist/workflows/events/idempotency.d.ts +77 -0
- package/dist/workflows/events/idempotency.d.ts.map +1 -0
- package/dist/workflows/events/idempotency.js +116 -0
- package/dist/workflows/events/idempotency.js.map +1 -0
- package/dist/workflows/events/index.d.ts +7 -0
- package/dist/workflows/events/index.d.ts.map +1 -0
- package/dist/workflows/events/index.js +7 -0
- package/dist/workflows/events/index.js.map +1 -0
- package/dist/workflows/events/payloads.d.ts +917 -0
- package/dist/workflows/events/payloads.d.ts.map +1 -0
- package/dist/workflows/events/payloads.js +337 -0
- package/dist/workflows/events/payloads.js.map +1 -0
- package/dist/workflows/events/replay.d.ts +238 -0
- package/dist/workflows/events/replay.d.ts.map +1 -0
- package/dist/workflows/events/replay.js +608 -0
- package/dist/workflows/events/replay.js.map +1 -0
- package/dist/workflows/events/schema.d.ts +5242 -0
- package/dist/workflows/events/schema.d.ts.map +1 -0
- package/dist/workflows/events/schema.js +295 -0
- package/dist/workflows/events/schema.js.map +1 -0
- package/dist/workflows/events/types.d.ts +34 -0
- package/dist/workflows/events/types.d.ts.map +1 -0
- package/dist/workflows/events/types.js +2 -0
- package/dist/workflows/events/types.js.map +1 -0
- package/dist/workflows/fanout.d.ts +36 -0
- package/dist/workflows/fanout.d.ts.map +1 -0
- package/dist/workflows/fanout.js +114 -0
- package/dist/workflows/fanout.js.map +1 -0
- package/dist/workflows/hostExecutors/botmux-schedule.d.ts +41 -0
- package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/botmux-schedule.js +121 -0
- package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -0
- package/dist/workflows/hostExecutors/feishu-im.d.ts +12 -0
- package/dist/workflows/hostExecutors/feishu-im.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/feishu-im.js +49 -0
- package/dist/workflows/hostExecutors/feishu-im.js.map +1 -0
- package/dist/workflows/hostExecutors/feishu-reply.d.ts +24 -0
- package/dist/workflows/hostExecutors/feishu-reply.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/feishu-reply.js +88 -0
- package/dist/workflows/hostExecutors/feishu-reply.js.map +1 -0
- package/dist/workflows/hostExecutors/feishu-send.d.ts +23 -0
- package/dist/workflows/hostExecutors/feishu-send.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/feishu-send.js +124 -0
- package/dist/workflows/hostExecutors/feishu-send.js.map +1 -0
- package/dist/workflows/hostExecutors/index.d.ts +8 -0
- package/dist/workflows/hostExecutors/index.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/index.js +8 -0
- package/dist/workflows/hostExecutors/index.js.map +1 -0
- package/dist/workflows/hostExecutors/protocol.d.ts +42 -0
- package/dist/workflows/hostExecutors/protocol.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/protocol.js +181 -0
- package/dist/workflows/hostExecutors/protocol.js.map +1 -0
- package/dist/workflows/hostExecutors/registry.d.ts +10 -0
- package/dist/workflows/hostExecutors/registry.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/registry.js +36 -0
- package/dist/workflows/hostExecutors/registry.js.map +1 -0
- package/dist/workflows/hostExecutors/types.d.ts +78 -0
- package/dist/workflows/hostExecutors/types.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/types.js +2 -0
- package/dist/workflows/hostExecutors/types.js.map +1 -0
- package/dist/workflows/loader.d.ts +16 -0
- package/dist/workflows/loader.d.ts.map +1 -0
- package/dist/workflows/loader.js +56 -0
- package/dist/workflows/loader.js.map +1 -0
- package/dist/workflows/loop.d.ts +50 -0
- package/dist/workflows/loop.d.ts.map +1 -0
- package/dist/workflows/loop.js +350 -0
- package/dist/workflows/loop.js.map +1 -0
- package/dist/workflows/ops-projection.d.ts +168 -0
- package/dist/workflows/ops-projection.d.ts.map +1 -0
- package/dist/workflows/ops-projection.js +707 -0
- package/dist/workflows/ops-projection.js.map +1 -0
- package/dist/workflows/orchestrator.d.ts +107 -0
- package/dist/workflows/orchestrator.d.ts.map +1 -0
- package/dist/workflows/orchestrator.js +197 -0
- package/dist/workflows/orchestrator.js.map +1 -0
- package/dist/workflows/output-binding.d.ts +70 -0
- package/dist/workflows/output-binding.d.ts.map +1 -0
- package/dist/workflows/output-binding.js +265 -0
- package/dist/workflows/output-binding.js.map +1 -0
- package/dist/workflows/params.d.ts +61 -0
- package/dist/workflows/params.d.ts.map +1 -0
- package/dist/workflows/params.js +195 -0
- package/dist/workflows/params.js.map +1 -0
- package/dist/workflows/resume.d.ts +263 -0
- package/dist/workflows/resume.d.ts.map +1 -0
- package/dist/workflows/resume.js +808 -0
- package/dist/workflows/resume.js.map +1 -0
- package/dist/workflows/run-id.d.ts +2 -0
- package/dist/workflows/run-id.d.ts.map +1 -0
- package/dist/workflows/run-id.js +7 -0
- package/dist/workflows/run-id.js.map +1 -0
- package/dist/workflows/run-init.d.ts +48 -0
- package/dist/workflows/run-init.d.ts.map +1 -0
- package/dist/workflows/run-init.js +99 -0
- package/dist/workflows/run-init.js.map +1 -0
- package/dist/workflows/runs-dir.d.ts +4 -0
- package/dist/workflows/runs-dir.d.ts.map +1 -0
- package/dist/workflows/runs-dir.js +15 -0
- package/dist/workflows/runs-dir.js.map +1 -0
- package/dist/workflows/runtime.d.ts +211 -0
- package/dist/workflows/runtime.d.ts.map +1 -0
- package/dist/workflows/runtime.js +594 -0
- package/dist/workflows/runtime.js.map +1 -0
- package/dist/workflows/spawn-bot.d.ts +165 -0
- package/dist/workflows/spawn-bot.d.ts.map +1 -0
- package/dist/workflows/spawn-bot.js +215 -0
- package/dist/workflows/spawn-bot.js.map +1 -0
- package/dist/workflows/system.d.ts +49 -0
- package/dist/workflows/system.d.ts.map +1 -0
- package/dist/workflows/system.js +48 -0
- package/dist/workflows/system.js.map +1 -0
- package/dist/workflows/trigger-run.d.ts +70 -0
- package/dist/workflows/trigger-run.d.ts.map +1 -0
- package/dist/workflows/trigger-run.js +88 -0
- package/dist/workflows/trigger-run.js.map +1 -0
- package/dist/workflows/wait.d.ts +120 -0
- package/dist/workflows/wait.d.ts.map +1 -0
- package/dist/workflows/wait.js +181 -0
- package/dist/workflows/wait.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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"}
|