@sema-agent/core 1.452.0 → 2.0.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 (162) hide show
  1. package/dist/agents/agent-transcript-tool.d.ts +15 -0
  2. package/dist/agents/agent-transcript-tool.js +122 -0
  3. package/dist/agents/cascade.js +5 -8
  4. package/dist/agents/repair-loop.d.ts +1 -1
  5. package/dist/agents/repair-loop.js +1 -1
  6. package/dist/agents/retain-ledger.d.ts +60 -0
  7. package/dist/agents/retain-ledger.js +225 -0
  8. package/dist/agents/roster-store.d.ts +1 -0
  9. package/dist/agents/roster-store.js +9 -0
  10. package/dist/agents/send-message-tool.d.ts +41 -0
  11. package/dist/agents/send-message-tool.js +469 -0
  12. package/dist/agents/subagent.d.ts +33 -108
  13. package/dist/agents/subagent.js +22 -834
  14. package/dist/agents/teacher.d.ts +0 -1
  15. package/dist/agents/teacher.js +0 -9
  16. package/dist/bin/sema-tb.js +3 -3
  17. package/dist/brain/anthropic.js +15 -40
  18. package/dist/brain/openai.js +15 -40
  19. package/dist/brain/reasoning.d.ts +1 -1
  20. package/dist/brain/stream-engine.js +2 -2
  21. package/dist/brain/stream-shared.d.ts +17 -0
  22. package/dist/brain/stream-shared.js +50 -0
  23. package/dist/brain/timeout.d.ts +1 -1
  24. package/dist/brain/timeout.js +1 -1
  25. package/dist/config/catalog.js +1 -3
  26. package/dist/config/defaults.d.ts +6 -0
  27. package/dist/config/defaults.js +6 -0
  28. package/dist/core/ask-question.d.ts +1 -1
  29. package/dist/core/background-shell.d.ts +1 -1
  30. package/dist/core/checkpoint-store.d.ts +0 -1
  31. package/dist/core/exec-gate.d.ts +1 -1
  32. package/dist/core/exec-output-tail.d.ts +1 -0
  33. package/dist/core/exec-output-tail.js +6 -0
  34. package/dist/core/file-snapshot-store.d.ts +1 -1
  35. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  36. package/dist/core/git-worktree-env.d.ts +1 -1
  37. package/dist/core/hooks.d.ts +1 -1
  38. package/dist/core/image-downsample.d.ts +44 -0
  39. package/dist/core/image-downsample.js +97 -0
  40. package/dist/core/lsp.d.ts +1 -1
  41. package/dist/core/mcp.d.ts +3 -45
  42. package/dist/core/mcp.js +2 -97
  43. package/dist/core/media-byte-cap.d.ts +1 -1
  44. package/dist/core/memory-recall.d.ts +0 -12
  45. package/dist/core/memory-recall.js +0 -222
  46. package/dist/core/memory.d.ts +0 -6
  47. package/dist/core/memory.js +0 -88
  48. package/dist/core/message-utils.d.ts +1 -1
  49. package/dist/core/oracle-isolation.d.ts +1 -1
  50. package/dist/core/remote-env.d.ts +1 -1
  51. package/dist/core/roles.d.ts +1 -1
  52. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  53. package/dist/core/runner/assemble-result.d.ts +0 -2
  54. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  55. package/dist/core/runner/prepare-memory.d.ts +16 -0
  56. package/dist/core/runner/prepare-memory.js +191 -0
  57. package/dist/core/runner/prepare-task.d.ts +4 -28
  58. package/dist/core/runner/prepare-task.js +19 -207
  59. package/dist/core/runner/prompt-suggestions.d.ts +1 -1
  60. package/dist/core/runner/runtask.d.ts +2 -3
  61. package/dist/core/runner/runtask.js +337 -529
  62. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  63. package/dist/core/runner/synthetic-tools.d.ts +5 -5
  64. package/dist/core/runner/synthetic-tools.js +4 -4
  65. package/dist/core/runner/tool-disclosure.d.ts +3 -3
  66. package/dist/core/runner/tool-disclosure.js +2 -2
  67. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  68. package/dist/core/runner/tool-output-projection.js +81 -0
  69. package/dist/core/runtime.d.ts +1 -1
  70. package/dist/core/scheduler.d.ts +1 -1
  71. package/dist/core/sensitive-path-policy.d.ts +1 -1
  72. package/dist/core/session-reconcile.d.ts +1 -1
  73. package/dist/core/session-store.d.ts +2 -1
  74. package/dist/core/session-store.js +2 -1
  75. package/dist/core/session.d.ts +0 -1
  76. package/dist/core/task-registry-agent.d.ts +115 -0
  77. package/dist/core/task-registry-agent.js +1163 -0
  78. package/dist/core/task-registry-monitor.d.ts +13 -0
  79. package/dist/core/task-registry-monitor.js +347 -0
  80. package/dist/core/task-registry-shared.d.ts +294 -0
  81. package/dist/core/task-registry-shared.js +271 -0
  82. package/dist/core/task-registry-workflow.d.ts +5 -0
  83. package/dist/core/task-registry-workflow.js +143 -0
  84. package/dist/core/task-registry.d.ts +51 -216
  85. package/dist/core/task-registry.js +127 -1940
  86. package/dist/core/task-tool-shape.d.ts +32 -0
  87. package/dist/core/task-tool-shape.js +83 -0
  88. package/dist/core/tool-policy.d.ts +1 -2
  89. package/dist/core/tool-result-budget.d.ts +1 -1
  90. package/dist/core/tool-result-store.d.ts +2 -2
  91. package/dist/core/tool-result-store.js +1 -1
  92. package/dist/core/tools.d.ts +2 -3
  93. package/dist/core/tools.js +0 -8
  94. package/dist/core/trace.d.ts +2 -2
  95. package/dist/core/types.d.ts +0 -29
  96. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  97. package/dist/core/workflow-run-store-contract.js +19 -19
  98. package/dist/engine/execution-env/node-execution-env.d.ts +13 -0
  99. package/dist/engine/execution-env/node-execution-env.js +55 -25
  100. package/dist/engine/lsp/frame-decoder.d.ts +1 -1
  101. package/dist/engine/lsp/frame-decoder.js +1 -1
  102. package/dist/engine/lsp/node-lsp-manager.d.ts +1 -1
  103. package/dist/engine/lsp/stdio-lsp-transport.js +2 -2
  104. package/dist/index.d.ts +16 -14
  105. package/dist/index.js +13 -11
  106. package/dist/internal/harness-types.d.ts +7 -0
  107. package/dist/internal/harness-types.js +1 -0
  108. package/dist/internal/harness.d.ts +1 -7
  109. package/dist/internal/harness.js +1 -0
  110. package/dist/orchestration/goal.js +2 -2
  111. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  112. package/dist/orchestration/workflow-types.d.ts +193 -0
  113. package/dist/orchestration/workflow-types.js +54 -0
  114. package/dist/orchestration/workflow.d.ts +3 -193
  115. package/dist/orchestration/workflow.js +7 -59
  116. package/dist/prompt-assembly/assemble.d.ts +3 -3
  117. package/dist/prompt-assembly/assemble.js +0 -16
  118. package/dist/prompt-assembly/explain.d.ts +1 -1
  119. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  120. package/dist/prompts/default.d.ts +1 -6
  121. package/dist/prompts/default.js +3 -11
  122. package/dist/scenarios/env.d.ts +1 -1
  123. package/dist/scenarios/full-body.d.ts +0 -3
  124. package/dist/scenarios/full-body.js +0 -2
  125. package/dist/stores/file/background-agent-store.d.ts +2 -15
  126. package/dist/stores/file/background-agent-store.js +30 -100
  127. package/dist/stores/file/checkpoint-store.d.ts +1 -14
  128. package/dist/stores/file/checkpoint-store.js +47 -119
  129. package/dist/stores/file/file-snapshot-store.d.ts +1 -1
  130. package/dist/stores/file/shared-ledger.d.ts +35 -0
  131. package/dist/stores/file/shared-ledger.js +94 -0
  132. package/dist/stores/file/workflow-run-store.d.ts +1 -14
  133. package/dist/stores/file/workflow-run-store.js +24 -93
  134. package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
  135. package/dist/tools/fs/bash-readonly-classifier.js +166 -0
  136. package/dist/tools/fs/fs-bash.d.ts +31 -0
  137. package/dist/tools/fs/fs-bash.js +672 -0
  138. package/dist/tools/fs/fs-pdf.d.ts +22 -0
  139. package/dist/tools/fs/fs-pdf.js +236 -0
  140. package/dist/tools/fs/fs-read.d.ts +8 -0
  141. package/dist/tools/fs/fs-read.js +286 -0
  142. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  143. package/dist/tools/fs/fs-search-tools.js +173 -0
  144. package/dist/tools/fs/fs-shared.d.ts +55 -0
  145. package/dist/tools/fs/fs-shared.js +144 -0
  146. package/dist/tools/fs/fs-write.d.ts +8 -0
  147. package/dist/tools/fs/fs-write.js +423 -0
  148. package/dist/tools/fs/index.d.ts +12 -65
  149. package/dist/tools/fs/index.js +26 -2090
  150. package/dist/tools/fs/pdf.d.ts +1 -1
  151. package/dist/tools/fs/repo-map.d.ts +3 -3
  152. package/dist/tools/fs/repo-map.js +1 -1
  153. package/dist/tools/fs/safety.d.ts +1 -1
  154. package/dist/tools/fs/search.d.ts +1 -1
  155. package/dist/tools/monitor.d.ts +1 -1
  156. package/dist/tools/scheduler-tools.d.ts +1 -1
  157. package/dist/tools/web.d.ts +2 -2
  158. package/dist/tools/web.js +1 -1
  159. package/dist/tools/worktree.d.ts +1 -1
  160. package/package.json +1 -1
  161. package/dist/core/dynamic-recall.d.ts +0 -32
  162. package/dist/core/dynamic-recall.js +0 -76
@@ -1,105 +1,49 @@
1
1
  import { join } from "node:path";
2
2
  import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, } from "../../core/background-agent-store.js";
3
- import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords } from "./fs-atomic.js";
4
- const sharedAgentDirs = new Map();
3
+ import { SharedLedgerTable } from "./shared-ledger.js";
4
+ const agentLedgers = new SharedLedgerTable({
5
+ keyOf: (r) => FileBackgroundAgentStore.key(r.handle, r.scope),
6
+ apply: (rows, ev) => {
7
+ if (ev.t === "delete") {
8
+ rows.delete(ev.k);
9
+ return;
10
+ }
11
+ rows.set(ev.k, ev.r);
12
+ },
13
+ });
5
14
  export class FileBackgroundAgentStore {
6
- dir;
7
- tmpDir;
8
- ledgerPath;
9
- snapshotPath;
10
15
  fsyncEnabled;
11
16
  compactEvery;
12
- shared;
17
+ ledger;
13
18
  closed = false;
14
19
  get rows() {
15
- return this.shared.rows;
16
- }
17
- get locks() {
18
- return this.shared.locks;
19
- }
20
- get ledgerEvents() {
21
- return this.shared.ledgerEvents;
22
- }
23
- set ledgerEvents(v) {
24
- this.shared.ledgerEvents = v;
25
- }
26
- get log() {
27
- return this.shared.log;
28
- }
29
- set log(v) {
30
- this.shared.log = v;
20
+ return this.ledger.rows;
31
21
  }
32
22
  constructor(root, opts = {}) {
33
- this.dir = join(root, "background-agents");
34
- this.tmpDir = join(root, "tmp");
35
- this.ledgerPath = join(this.dir, "agents.jsonl");
36
- this.snapshotPath = join(this.dir, "agents.snapshot.jsonl");
37
23
  this.fsyncEnabled = opts.fsync !== false;
38
24
  this.compactEvery = opts.compactEvery ?? 1000;
39
- ensureDir(this.dir);
40
- ensureDir(this.tmpDir);
41
- const canonical = canonicalStoreKey(this.dir);
42
- const existing = sharedAgentDirs.get(canonical);
43
- if (existing !== undefined) {
44
- existing.refs += 1;
45
- this.shared = existing;
46
- this.canonicalDir = canonical;
47
- return;
48
- }
49
- this.shared = { rows: new Map(), locks: new Map(), ledgerEvents: 0, log: undefined, refs: 1 };
50
- this.canonicalDir = canonical;
51
- sharedAgentDirs.set(canonical, this.shared);
52
- this.replay();
53
- this.log = new AppendLog(this.ledgerPath);
25
+ const dir = join(root, "background-agents");
26
+ this.ledger = agentLedgers.acquire({
27
+ dir,
28
+ tmpDir: join(root, "tmp"),
29
+ ledgerPath: join(dir, "agents.jsonl"),
30
+ snapshotPath: join(dir, "agents.snapshot.jsonl"),
31
+ });
54
32
  }
55
- canonicalDir;
56
- key(handle, scope) {
33
+ static key(handle, scope) {
57
34
  return `${scope}\u0000${handle}`;
58
35
  }
59
- replay() {
60
- for (const snap of readJsonlRecords(this.snapshotPath)) {
61
- this.rows.set(this.key(snap.handle, snap.scope), snap);
62
- }
63
- const events = readJsonlRecords(this.ledgerPath);
64
- this.ledgerEvents = events.length;
65
- for (const ev of events)
66
- this.applyEvent(ev);
67
- }
68
- applyEvent(ev) {
69
- if (ev.t === "delete") {
70
- this.rows.delete(ev.k);
71
- return;
72
- }
73
- this.rows.set(ev.k, ev.r);
74
- }
75
36
  withLock(key, fn) {
76
- const prev = this.locks.get(key) ?? Promise.resolve();
77
- const run = prev.then(() => fn(), () => fn());
78
- const tail = run.then(() => { }, () => { });
79
- this.locks.set(key, tail);
80
- void tail.then(() => {
81
- if (this.locks.get(key) === tail)
82
- this.locks.delete(key);
83
- });
84
- return run;
37
+ return this.ledger.withLock(key, fn);
85
38
  }
86
39
  commit(ev) {
87
- this.log.append(ev, this.fsyncEnabled);
88
- this.applyEvent(ev);
89
- this.ledgerEvents++;
90
- if (this.ledgerEvents >= this.compactEvery) {
91
- try {
92
- this.compact();
93
- }
94
- catch {
95
- }
96
- }
40
+ this.ledger.commit(ev, this.fsyncEnabled, this.compactEvery);
97
41
  }
98
42
  async put(record) {
99
43
  if (record.scope === undefined || record.scope === "") {
100
44
  throw new Error("BackgroundAgentStore.put: refusing a row without a scope (world-readable through the fallback)");
101
45
  }
102
- const k = this.key(record.handle, record.scope);
46
+ const k = FileBackgroundAgentStore.key(record.handle, record.scope);
103
47
  return this.withLock(k, () => {
104
48
  if (this.rows.has(k)) {
105
49
  throw new BackgroundAgentStoreError("agent_record.already_exists", "agent record already exists");
@@ -110,11 +54,11 @@ export class FileBackgroundAgentStore {
110
54
  });
111
55
  }
112
56
  async get(handle, scope) {
113
- const row = this.rows.get(this.key(handle, scope));
57
+ const row = this.rows.get(FileBackgroundAgentStore.key(handle, scope));
114
58
  return row ? structuredClone(row) : null;
115
59
  }
116
60
  async update(handle, scope, record, expect) {
117
- const k = this.key(handle, scope);
61
+ const k = FileBackgroundAgentStore.key(handle, scope);
118
62
  return this.withLock(k, () => {
119
63
  const live = this.rows.get(k);
120
64
  if (!live || live.scope !== scope || live.rev !== expect.rev)
@@ -128,7 +72,7 @@ export class FileBackgroundAgentStore {
128
72
  });
129
73
  }
130
74
  async updateIf(handle, scope, record, expect) {
131
- const k = this.key(handle, scope);
75
+ const k = FileBackgroundAgentStore.key(handle, scope);
132
76
  return this.withLock(k, async () => {
133
77
  const live = this.rows.get(k);
134
78
  if (!live || live.scope !== scope || live.rev !== expect.rev)
@@ -178,7 +122,7 @@ export class FileBackgroundAgentStore {
178
122
  return [...new Set([...this.rows.values()].map((r) => r.scope))].sort();
179
123
  }
180
124
  async delete(handle, scope, expect) {
181
- const k = this.key(handle, scope);
125
+ const k = FileBackgroundAgentStore.key(handle, scope);
182
126
  return this.withLock(k, () => {
183
127
  const live = this.rows.get(k);
184
128
  if (!live)
@@ -248,15 +192,8 @@ export class FileBackgroundAgentStore {
248
192
  }
249
193
  return changed.size;
250
194
  }
251
- compact() {
252
- const lines = [...this.rows.values()].map((r) => JSON.stringify(r)).join("\n");
253
- atomicWriteFile(this.tmpDir, this.snapshotPath, lines.length ? `${lines}\n` : "");
254
- this.log.closeForSwap();
255
- atomicWriteFile(this.tmpDir, this.ledgerPath, "");
256
- this.ledgerEvents = 0;
257
- }
258
195
  compactNow() {
259
- this.compact();
196
+ this.ledger.compact();
260
197
  }
261
198
  get size() {
262
199
  return this.rows.size;
@@ -265,14 +202,7 @@ export class FileBackgroundAgentStore {
265
202
  if (this.closed)
266
203
  return;
267
204
  this.closed = true;
268
- const st = sharedAgentDirs.get(this.canonicalDir);
269
- if (st !== undefined && st === this.shared) {
270
- st.refs -= 1;
271
- if (st.refs > 0)
272
- return;
273
- sharedAgentDirs.delete(this.canonicalDir);
274
- }
275
- this.log.close();
205
+ this.ledger.release();
276
206
  }
277
207
  }
278
208
  function toSummary(r) {
@@ -4,25 +4,13 @@ export interface FileCheckpointStoreOptions {
4
4
  compactEvery?: number;
5
5
  }
6
6
  export declare class FileCheckpointStore implements CheckpointStore {
7
- private readonly dir;
8
- private readonly tmpDir;
9
- private readonly ledgerPath;
10
- private readonly snapshotPath;
11
7
  private readonly fsyncEnabled;
12
8
  private readonly compactEvery;
13
- private readonly shared;
14
- private readonly sharedKey;
9
+ private readonly ledger;
15
10
  private closed;
16
11
  private get cps();
17
- private get locks();
18
- private get ledgerEvents();
19
- private set ledgerEvents(value);
20
- private get log();
21
- private set log(value);
22
12
  private fault;
23
13
  constructor(root: string, opts?: FileCheckpointStoreOptions);
24
- private replay;
25
- private applyEvent;
26
14
  private withLock;
27
15
  private commit;
28
16
  put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
@@ -37,7 +25,6 @@ export declare class FileCheckpointStore implements CheckpointStore {
37
25
  reap(scope: string, cutoff: number): Promise<number>;
38
26
  listByScope(scope: string): Promise<CheckpointSummary[]>;
39
27
  listScopes(): Promise<string[]>;
40
- private compact;
41
28
  compactNow(): void;
42
29
  testInjectFault(mode: CheckpointFaultMode | null): void;
43
30
  get size(): number;
@@ -1,124 +1,64 @@
1
1
  import { join } from "node:path";
2
2
  import { CheckpointError, summarizeCheckpoint, validatePendingSteer, winnerFromOutcome, } from "../../core/checkpoint-store.js";
3
- import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords } from "./fs-atomic.js";
4
- const sharedCheckpointDirs = new Map();
3
+ import { SharedLedgerTable } from "./shared-ledger.js";
4
+ function applyCheckpointEvent(cps, ev) {
5
+ if (ev.t === "put") {
6
+ cps.set(ev.token, ev.cp);
7
+ return;
8
+ }
9
+ const cp = cps.get(ev.token);
10
+ if (!cp)
11
+ return;
12
+ switch (ev.t) {
13
+ case "resolve":
14
+ cp.status = "resolved";
15
+ cp.rev = ev.rev;
16
+ if (ev.resolvedOutcome !== undefined)
17
+ cp.resolvedOutcome = ev.resolvedOutcome;
18
+ cp.reopenReason = undefined;
19
+ break;
20
+ case "reopen":
21
+ cp.status = "pending";
22
+ cp.rev = ev.rev;
23
+ cp.reopenReason = ev.reason;
24
+ break;
25
+ case "expire":
26
+ cp.status = "expired";
27
+ break;
28
+ case "steer":
29
+ cp.state.pendingSteer = ev.steer;
30
+ break;
31
+ }
32
+ }
33
+ const checkpointLedgers = new SharedLedgerTable({
34
+ keyOf: (cp) => cp.token,
35
+ apply: applyCheckpointEvent,
36
+ });
5
37
  export class FileCheckpointStore {
6
- dir;
7
- tmpDir;
8
- ledgerPath;
9
- snapshotPath;
10
38
  fsyncEnabled;
11
39
  compactEvery;
12
- shared;
13
- sharedKey;
40
+ ledger;
14
41
  closed = false;
15
42
  get cps() {
16
- return this.shared.cps;
17
- }
18
- get locks() {
19
- return this.shared.locks;
20
- }
21
- get ledgerEvents() {
22
- return this.shared.ledgerEvents;
23
- }
24
- set ledgerEvents(v) {
25
- this.shared.ledgerEvents = v;
26
- }
27
- get log() {
28
- return this.shared.log;
29
- }
30
- set log(v) {
31
- this.shared.log = v;
43
+ return this.ledger.rows;
32
44
  }
33
45
  fault = null;
34
46
  constructor(root, opts = {}) {
35
- this.dir = join(root, "checkpoints");
36
- this.tmpDir = join(root, "tmp");
37
- this.ledgerPath = join(this.dir, "checkpoints.jsonl");
38
- this.snapshotPath = join(this.dir, "checkpoints.snapshot.jsonl");
39
47
  this.fsyncEnabled = opts.fsync !== false;
40
48
  this.compactEvery = opts.compactEvery ?? 1000;
41
- ensureDir(this.dir);
42
- ensureDir(this.tmpDir);
43
- const key = canonicalStoreKey(this.dir);
44
- this.sharedKey = key;
45
- const live = sharedCheckpointDirs.get(key);
46
- if (live !== undefined) {
47
- live.refs += 1;
48
- this.shared = live;
49
- return;
50
- }
51
- this.shared = {
52
- cps: new Map(),
53
- locks: new Map(),
54
- ledgerEvents: 0,
55
- log: undefined,
56
- refs: 1,
57
- };
58
- sharedCheckpointDirs.set(key, this.shared);
59
- this.replay();
60
- this.log = new AppendLog(this.ledgerPath);
61
- }
62
- replay() {
63
- for (const snap of readJsonlRecords(this.snapshotPath)) {
64
- this.cps.set(snap.token, snap);
65
- }
66
- const events = readJsonlRecords(this.ledgerPath);
67
- this.ledgerEvents = events.length;
68
- for (const ev of events)
69
- this.applyEvent(ev);
70
- }
71
- applyEvent(ev) {
72
- if (ev.t === "put") {
73
- this.cps.set(ev.token, ev.cp);
74
- return;
75
- }
76
- const cp = this.cps.get(ev.token);
77
- if (!cp)
78
- return;
79
- switch (ev.t) {
80
- case "resolve":
81
- cp.status = "resolved";
82
- cp.rev = ev.rev;
83
- if (ev.resolvedOutcome !== undefined)
84
- cp.resolvedOutcome = ev.resolvedOutcome;
85
- cp.reopenReason = undefined;
86
- break;
87
- case "reopen":
88
- cp.status = "pending";
89
- cp.rev = ev.rev;
90
- cp.reopenReason = ev.reason;
91
- break;
92
- case "expire":
93
- cp.status = "expired";
94
- break;
95
- case "steer":
96
- cp.state.pendingSteer = ev.steer;
97
- break;
98
- }
49
+ const dir = join(root, "checkpoints");
50
+ this.ledger = checkpointLedgers.acquire({
51
+ dir,
52
+ tmpDir: join(root, "tmp"),
53
+ ledgerPath: join(dir, "checkpoints.jsonl"),
54
+ snapshotPath: join(dir, "checkpoints.snapshot.jsonl"),
55
+ });
99
56
  }
100
57
  withLock(token, fn) {
101
- const prev = this.locks.get(token) ?? Promise.resolve();
102
- const run = prev.then(() => fn(), () => fn());
103
- const tail = run.then(() => { }, () => { });
104
- this.locks.set(token, tail);
105
- void tail.then(() => {
106
- if (this.locks.get(token) === tail)
107
- this.locks.delete(token);
108
- });
109
- return run;
58
+ return this.ledger.withLock(token, fn);
110
59
  }
111
60
  commit(ev, fsync) {
112
- this.log.append(ev, fsync);
113
- this.applyEvent(ev);
114
- this.ledgerEvents++;
115
- if (this.ledgerEvents >= this.compactEvery) {
116
- try {
117
- this.compact();
118
- }
119
- catch {
120
- }
121
- }
61
+ this.ledger.commit(ev, fsync, this.compactEvery);
122
62
  }
123
63
  async put(token, cp) {
124
64
  return this.withLock(token, () => {
@@ -222,15 +162,8 @@ export class FileCheckpointStore {
222
162
  out.add(cp.scope);
223
163
  return [...out];
224
164
  }
225
- compact() {
226
- const lines = [...this.cps.values()].map((cp) => JSON.stringify(cp)).join("\n");
227
- atomicWriteFile(this.tmpDir, this.snapshotPath, lines.length ? `${lines}\n` : "");
228
- this.log.closeForSwap();
229
- atomicWriteFile(this.tmpDir, this.ledgerPath, "");
230
- this.ledgerEvents = 0;
231
- }
232
165
  compactNow() {
233
- this.compact();
166
+ this.ledger.compact();
234
167
  }
235
168
  testInjectFault(mode) {
236
169
  this.fault = mode;
@@ -242,11 +175,6 @@ export class FileCheckpointStore {
242
175
  if (this.closed)
243
176
  return;
244
177
  this.closed = true;
245
- this.shared.refs -= 1;
246
- if (this.shared.refs > 0)
247
- return;
248
- this.log.close();
249
- if (sharedCheckpointDirs.get(this.sharedKey) === this.shared)
250
- sharedCheckpointDirs.delete(this.sharedKey);
178
+ this.ledger.release();
251
179
  }
252
180
  }
@@ -1,4 +1,4 @@
1
- import type { ExecutionEnv } from "../../internal/harness.js";
1
+ import type { ExecutionEnv } from "../../internal/harness-types.js";
2
2
  import { type FileSnapshotBounds, type FileSnapshotResult, type FileSnapshotStore } from "../../core/file-snapshot-store.js";
3
3
  export declare class FileFileSnapshotStore implements FileSnapshotStore {
4
4
  private readonly base;
@@ -0,0 +1,35 @@
1
+ export interface LedgerPaths {
2
+ readonly dir: string;
3
+ readonly tmpDir: string;
4
+ readonly ledgerPath: string;
5
+ readonly snapshotPath: string;
6
+ }
7
+ export interface LedgerModel<Row, Ev> {
8
+ keyOf(row: Row): string;
9
+ apply(rows: Map<string, Row>, ev: Ev): void;
10
+ }
11
+ export declare class LedgerCore<Row, Ev> {
12
+ private readonly key;
13
+ private readonly paths;
14
+ private readonly model;
15
+ private readonly table;
16
+ readonly rows: Map<string, Row>;
17
+ refs: number;
18
+ private readonly locks;
19
+ private events;
20
+ private handle;
21
+ constructor(key: string, paths: LedgerPaths, model: LedgerModel<Row, Ev>, table: Map<string, LedgerCore<Row, Ev>>);
22
+ bootstrap(): void;
23
+ withLock<T>(key: string, fn: () => Promise<T> | T): Promise<T>;
24
+ commit(ev: Ev, fsync: boolean, compactEvery: number): void;
25
+ compact(): void;
26
+ release(): void;
27
+ get pendingEvents(): number;
28
+ private get log();
29
+ }
30
+ export declare class SharedLedgerTable<Row, Ev> {
31
+ private readonly model;
32
+ private readonly live;
33
+ constructor(model: LedgerModel<Row, Ev>);
34
+ acquire(paths: LedgerPaths): LedgerCore<Row, Ev>;
35
+ }
@@ -0,0 +1,94 @@
1
+ import { AppendLog, atomicWriteFile, canonicalStoreKey, ensureDir, readJsonlRecords } from "./fs-atomic.js";
2
+ export class LedgerCore {
3
+ key;
4
+ paths;
5
+ model;
6
+ table;
7
+ rows = new Map();
8
+ refs = 1;
9
+ locks = new Map();
10
+ events = 0;
11
+ handle;
12
+ constructor(key, paths, model, table) {
13
+ this.key = key;
14
+ this.paths = paths;
15
+ this.model = model;
16
+ this.table = table;
17
+ }
18
+ bootstrap() {
19
+ for (const row of readJsonlRecords(this.paths.snapshotPath))
20
+ this.rows.set(this.model.keyOf(row), row);
21
+ const events = readJsonlRecords(this.paths.ledgerPath);
22
+ this.events = events.length;
23
+ for (const ev of events)
24
+ this.model.apply(this.rows, ev);
25
+ this.handle = new AppendLog(this.paths.ledgerPath);
26
+ }
27
+ withLock(key, fn) {
28
+ const prev = this.locks.get(key) ?? Promise.resolve();
29
+ const run = prev.then(() => fn(), () => fn());
30
+ const tail = run.then(() => { }, () => { });
31
+ this.locks.set(key, tail);
32
+ void tail.then(() => {
33
+ if (this.locks.get(key) === tail)
34
+ this.locks.delete(key);
35
+ });
36
+ return run;
37
+ }
38
+ commit(ev, fsync, compactEvery) {
39
+ this.log.append(ev, fsync);
40
+ this.model.apply(this.rows, ev);
41
+ this.events++;
42
+ if (this.events >= compactEvery) {
43
+ try {
44
+ this.compact();
45
+ }
46
+ catch {
47
+ }
48
+ }
49
+ }
50
+ compact() {
51
+ const lines = [...this.rows.values()].map((r) => JSON.stringify(r)).join("\n");
52
+ atomicWriteFile(this.paths.tmpDir, this.paths.snapshotPath, lines.length ? `${lines}\n` : "");
53
+ this.log.closeForSwap();
54
+ atomicWriteFile(this.paths.tmpDir, this.paths.ledgerPath, "");
55
+ this.events = 0;
56
+ }
57
+ release() {
58
+ this.refs -= 1;
59
+ if (this.refs > 0)
60
+ return;
61
+ if (this.table.get(this.key) === this)
62
+ this.table.delete(this.key);
63
+ this.handle?.close();
64
+ }
65
+ get pendingEvents() {
66
+ return this.events;
67
+ }
68
+ get log() {
69
+ if (this.handle === undefined)
70
+ throw new Error("file ledger: used before bootstrap");
71
+ return this.handle;
72
+ }
73
+ }
74
+ export class SharedLedgerTable {
75
+ model;
76
+ live = new Map();
77
+ constructor(model) {
78
+ this.model = model;
79
+ }
80
+ acquire(paths) {
81
+ ensureDir(paths.dir);
82
+ ensureDir(paths.tmpDir);
83
+ const key = canonicalStoreKey(paths.dir);
84
+ const existing = this.live.get(key);
85
+ if (existing !== undefined) {
86
+ existing.refs += 1;
87
+ return existing;
88
+ }
89
+ const core = new LedgerCore(key, paths, this.model, this.live);
90
+ this.live.set(key, core);
91
+ core.bootstrap();
92
+ return core;
93
+ }
94
+ }
@@ -5,24 +5,12 @@ export interface FileWorkflowRunStoreOptions {
5
5
  compactEvery?: number;
6
6
  }
7
7
  export declare class FileWorkflowRunStore implements WorkflowRunStore {
8
- private readonly dir;
9
- private readonly tmpDir;
10
- private readonly ledgerPath;
11
- private readonly snapshotPath;
12
8
  private readonly fsyncEnabled;
13
9
  private readonly compactEvery;
14
- private readonly shared;
10
+ private readonly ledger;
15
11
  private closed;
16
- private readonly canonicalDir;
17
12
  private get runs();
18
- private get locks();
19
- private get ledgerEvents();
20
- private set ledgerEvents(value);
21
- private get log();
22
- private set log(value);
23
13
  constructor(root: string, opts?: FileWorkflowRunStoreOptions);
24
- private replay;
25
- private applyEvent;
26
14
  private withLock;
27
15
  private commit;
28
16
  put(id: string, run: WorkflowRun): Promise<void>;
@@ -39,7 +27,6 @@ export declare class FileWorkflowRunStore implements WorkflowRunStore {
39
27
  maxAgeMs?: number;
40
28
  keep?: number;
41
29
  }): Promise<number>;
42
- private compact;
43
30
  compactNow(): void;
44
31
  get size(): number;
45
32
  close(): void;