@sema-agent/core 1.451.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 (191) 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/observer.js +3 -0
  5. package/dist/agents/repair-loop.d.ts +1 -1
  6. package/dist/agents/repair-loop.js +1 -1
  7. package/dist/agents/retain-ledger.d.ts +60 -0
  8. package/dist/agents/retain-ledger.js +225 -0
  9. package/dist/agents/roster-store.d.ts +1 -0
  10. package/dist/agents/roster-store.js +9 -0
  11. package/dist/agents/send-message-tool.d.ts +41 -0
  12. package/dist/agents/send-message-tool.js +469 -0
  13. package/dist/agents/subagent.d.ts +33 -108
  14. package/dist/agents/subagent.js +40 -813
  15. package/dist/agents/teacher.d.ts +0 -1
  16. package/dist/agents/teacher.js +0 -9
  17. package/dist/bin/sema-tb.js +3 -3
  18. package/dist/brain/anthropic.js +15 -40
  19. package/dist/brain/openai.js +15 -40
  20. package/dist/brain/reasoning.d.ts +1 -1
  21. package/dist/brain/stream-engine.js +2 -2
  22. package/dist/brain/stream-shared.d.ts +17 -0
  23. package/dist/brain/stream-shared.js +50 -0
  24. package/dist/brain/timeout.d.ts +1 -1
  25. package/dist/brain/timeout.js +1 -1
  26. package/dist/config/catalog.js +1 -3
  27. package/dist/config/defaults.d.ts +6 -0
  28. package/dist/config/defaults.js +6 -0
  29. package/dist/core/ask-question.d.ts +1 -1
  30. package/dist/core/ask-question.js +5 -5
  31. package/dist/core/background-agent-store.d.ts +2 -0
  32. package/dist/core/background-agent-store.js +5 -1
  33. package/dist/core/background-shell.d.ts +1 -1
  34. package/dist/core/checkpoint-store.d.ts +0 -1
  35. package/dist/core/exec-gate.d.ts +1 -1
  36. package/dist/core/exec-output-tail.d.ts +19 -1
  37. package/dist/core/exec-output-tail.js +44 -5
  38. package/dist/core/file-snapshot-store.d.ts +1 -1
  39. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  40. package/dist/core/git-worktree-env.d.ts +1 -1
  41. package/dist/core/hooks.d.ts +1 -1
  42. package/dist/core/image-downsample.d.ts +44 -0
  43. package/dist/core/image-downsample.js +97 -0
  44. package/dist/core/lsp-session.d.ts +1 -0
  45. package/dist/core/lsp-session.js +24 -6
  46. package/dist/core/lsp.d.ts +11 -1
  47. package/dist/core/lsp.js +63 -6
  48. package/dist/core/mailbox-store.d.ts +3 -2
  49. package/dist/core/mailbox-store.js +19 -4
  50. package/dist/core/mcp.d.ts +3 -45
  51. package/dist/core/mcp.js +2 -97
  52. package/dist/core/media-byte-cap.d.ts +1 -1
  53. package/dist/core/memory-recall.d.ts +0 -12
  54. package/dist/core/memory-recall.js +0 -222
  55. package/dist/core/memory.d.ts +0 -6
  56. package/dist/core/memory.js +0 -88
  57. package/dist/core/message-utils.d.ts +1 -1
  58. package/dist/core/oracle-isolation.d.ts +1 -1
  59. package/dist/core/remote-env.d.ts +1 -1
  60. package/dist/core/roles.d.ts +1 -1
  61. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  62. package/dist/core/runner/assemble-result.d.ts +0 -2
  63. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  64. package/dist/core/runner/prepare-memory.d.ts +16 -0
  65. package/dist/core/runner/prepare-memory.js +191 -0
  66. package/dist/core/runner/prepare-task.d.ts +4 -28
  67. package/dist/core/runner/prepare-task.js +29 -208
  68. package/dist/core/runner/prompt-suggestions.d.ts +1 -1
  69. package/dist/core/runner/runtask.d.ts +2 -3
  70. package/dist/core/runner/runtask.js +337 -529
  71. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  72. package/dist/core/runner/synthetic-tools.d.ts +5 -5
  73. package/dist/core/runner/synthetic-tools.js +4 -4
  74. package/dist/core/runner/tool-disclosure.d.ts +3 -3
  75. package/dist/core/runner/tool-disclosure.js +2 -2
  76. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  77. package/dist/core/runner/tool-output-projection.js +81 -0
  78. package/dist/core/runtime.d.ts +1 -1
  79. package/dist/core/scheduler.d.ts +1 -1
  80. package/dist/core/sensitive-path-policy.d.ts +1 -1
  81. package/dist/core/session-reconcile.d.ts +1 -1
  82. package/dist/core/session-reconcile.js +5 -2
  83. package/dist/core/session-store.d.ts +2 -1
  84. package/dist/core/session-store.js +2 -1
  85. package/dist/core/session.d.ts +0 -1
  86. package/dist/core/task-notification.d.ts +1 -0
  87. package/dist/core/task-registry-agent.d.ts +115 -0
  88. package/dist/core/task-registry-agent.js +1163 -0
  89. package/dist/core/task-registry-monitor.d.ts +13 -0
  90. package/dist/core/task-registry-monitor.js +347 -0
  91. package/dist/core/task-registry-shared.d.ts +294 -0
  92. package/dist/core/task-registry-shared.js +271 -0
  93. package/dist/core/task-registry-workflow.d.ts +5 -0
  94. package/dist/core/task-registry-workflow.js +143 -0
  95. package/dist/core/task-registry.d.ts +54 -213
  96. package/dist/core/task-registry.js +190 -1766
  97. package/dist/core/task-tool-shape.d.ts +32 -0
  98. package/dist/core/task-tool-shape.js +83 -0
  99. package/dist/core/tool-policy.d.ts +1 -2
  100. package/dist/core/tool-result-budget.d.ts +1 -1
  101. package/dist/core/tool-result-store.d.ts +2 -2
  102. package/dist/core/tool-result-store.js +3 -3
  103. package/dist/core/tools.d.ts +7 -3
  104. package/dist/core/tools.js +3 -8
  105. package/dist/core/trace.d.ts +2 -2
  106. package/dist/core/types.d.ts +1 -29
  107. package/dist/core/workflow-journal-store.d.ts +2 -0
  108. package/dist/core/workflow-journal-store.js +14 -0
  109. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  110. package/dist/core/workflow-run-store-contract.js +19 -19
  111. package/dist/engine/execution-env/node-execution-env.d.ts +14 -0
  112. package/dist/engine/execution-env/node-execution-env.js +184 -44
  113. package/dist/engine/lsp/frame-decoder.d.ts +1 -1
  114. package/dist/engine/lsp/frame-decoder.js +1 -1
  115. package/dist/engine/lsp/node-lsp-manager.d.ts +4 -2
  116. package/dist/engine/lsp/node-lsp-manager.js +22 -5
  117. package/dist/engine/lsp/stdio-lsp-transport.d.ts +1 -1
  118. package/dist/engine/lsp/stdio-lsp-transport.js +19 -8
  119. package/dist/index.d.ts +18 -16
  120. package/dist/index.js +15 -13
  121. package/dist/internal/harness-types.d.ts +7 -0
  122. package/dist/internal/harness-types.js +1 -0
  123. package/dist/internal/harness.d.ts +1 -7
  124. package/dist/internal/harness.js +1 -0
  125. package/dist/orchestration/goal.js +2 -2
  126. package/dist/orchestration/run-workflow-tool.d.ts +2 -1
  127. package/dist/orchestration/run-workflow-tool.js +29 -6
  128. package/dist/orchestration/workflow-types.d.ts +193 -0
  129. package/dist/orchestration/workflow-types.js +54 -0
  130. package/dist/orchestration/workflow.d.ts +3 -184
  131. package/dist/orchestration/workflow.js +87 -64
  132. package/dist/prompt-assembly/assemble.d.ts +3 -3
  133. package/dist/prompt-assembly/assemble.js +0 -16
  134. package/dist/prompt-assembly/explain.d.ts +1 -1
  135. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  136. package/dist/prompts/default.d.ts +1 -6
  137. package/dist/prompts/default.js +3 -11
  138. package/dist/scenarios/env.d.ts +1 -1
  139. package/dist/scenarios/full-body.d.ts +0 -3
  140. package/dist/scenarios/full-body.js +0 -2
  141. package/dist/stores/cc/mailbox-store.js +6 -1
  142. package/dist/stores/file/background-agent-store.d.ts +2 -15
  143. package/dist/stores/file/background-agent-store.js +33 -102
  144. package/dist/stores/file/checkpoint-store.d.ts +1 -14
  145. package/dist/stores/file/checkpoint-store.js +47 -119
  146. package/dist/stores/file/file-snapshot-store.d.ts +1 -1
  147. package/dist/stores/file/mailbox-store.d.ts +1 -1
  148. package/dist/stores/file/mailbox-store.js +9 -7
  149. package/dist/stores/file/shared-ledger.d.ts +35 -0
  150. package/dist/stores/file/shared-ledger.js +94 -0
  151. package/dist/stores/file/workflow-run-store.d.ts +1 -14
  152. package/dist/stores/file/workflow-run-store.js +24 -93
  153. package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
  154. package/dist/tools/fs/bash-readonly-classifier.js +166 -0
  155. package/dist/tools/fs/encoding.d.ts +5 -0
  156. package/dist/tools/fs/encoding.js +6 -0
  157. package/dist/tools/fs/fs-bash.d.ts +31 -0
  158. package/dist/tools/fs/fs-bash.js +672 -0
  159. package/dist/tools/fs/fs-pdf.d.ts +22 -0
  160. package/dist/tools/fs/fs-pdf.js +236 -0
  161. package/dist/tools/fs/fs-read.d.ts +8 -0
  162. package/dist/tools/fs/fs-read.js +286 -0
  163. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  164. package/dist/tools/fs/fs-search-tools.js +173 -0
  165. package/dist/tools/fs/fs-shared.d.ts +55 -0
  166. package/dist/tools/fs/fs-shared.js +144 -0
  167. package/dist/tools/fs/fs-write.d.ts +8 -0
  168. package/dist/tools/fs/fs-write.js +423 -0
  169. package/dist/tools/fs/index.d.ts +12 -65
  170. package/dist/tools/fs/index.js +26 -2026
  171. package/dist/tools/fs/notebook.d.ts +43 -0
  172. package/dist/tools/fs/notebook.js +141 -0
  173. package/dist/tools/fs/pdf.d.ts +1 -1
  174. package/dist/tools/fs/repo-map.d.ts +3 -3
  175. package/dist/tools/fs/repo-map.js +3 -3
  176. package/dist/tools/fs/safety.d.ts +1 -1
  177. package/dist/tools/fs/search.d.ts +1 -1
  178. package/dist/tools/fs/search.js +141 -12
  179. package/dist/tools/gitea-issue.js +4 -2
  180. package/dist/tools/monitor.d.ts +1 -1
  181. package/dist/tools/monitor.js +12 -8
  182. package/dist/tools/scheduler-tools.d.ts +1 -1
  183. package/dist/tools/scheduler-tools.js +16 -16
  184. package/dist/tools/task-list.js +34 -12
  185. package/dist/tools/web.d.ts +4 -2
  186. package/dist/tools/web.js +106 -20
  187. package/dist/tools/worktree.d.ts +1 -1
  188. package/dist/tools/worktree.js +14 -14
  189. package/package.json +1 -1
  190. package/dist/core/dynamic-recall.d.ts +0 -32
  191. package/dist/core/dynamic-recall.js +0 -76
@@ -351,7 +351,6 @@ export const TOOL_PARAM_JSON = `
351
351
  ## Tool-call arguments
352
352
  An object or array parameter value must be a single JSON value — never write parameter-tag markup (XML-ish <param> tags) inside a JSON value. Pass the structured value directly as JSON.`;
353
353
  export const CODE_SYSTEM_PROMPT = CODE_AGENT_PROMPT + AUTONOMY_SELF_AUDIT + ANTI_VERBOSITY + TOOL_PARAM_JSON;
354
- export const FULL_BODY_SYSTEM_PROMPT = CODE_SYSTEM_PROMPT;
355
354
  function joinParts(parts) {
356
355
  return parts.filter((p) => Boolean(p && p.trim())).join("\n\n");
357
356
  }
@@ -389,9 +388,6 @@ function defaultStableSystem(ctx) {
389
388
  }
390
389
  export const defaultPromptProvider = {
391
390
  stableSystem: defaultStableSystem,
392
- system(ctx) {
393
- return composeSystemPrompt(defaultStableSystem(ctx), ctx.memoryBlock);
394
- },
395
391
  };
396
392
  const PROBE_MEMORY_A = "<user_memory>\nAAAA durable fact alpha (2026-01-01 00:00 UTC)\n</user_memory>";
397
393
  const PROBE_MEMORY_B = "<user_memory>\nBBBB durable fact bravo (2099-12-31 23:59 UTC)\n</user_memory>";
@@ -399,13 +395,9 @@ export function analyzePromptCacheFriendliness(provider, opts = {}) {
399
395
  const tools = opts.tools ?? [];
400
396
  const minRatio = opts.minStablePrefixRatio ?? 0.5;
401
397
  const build = (memoryBlock) => {
402
- if (provider.stableSystem) {
403
- return composeSystemPrompt(provider.stableSystem({ userSystemPrompt: opts.userSystemPrompt, tools, memoryEnabled: true }), memoryBlock);
404
- }
405
- if (provider.system) {
406
- return provider.system({ userSystemPrompt: opts.userSystemPrompt, memoryBlock, tools, memoryEnabled: true });
407
- }
408
- throw new Error("PromptProvider implements neither stableSystem nor system");
398
+ if (!provider.stableSystem)
399
+ throw new Error("PromptProvider does not implement stableSystem");
400
+ return composeSystemPrompt(provider.stableSystem({ userSystemPrompt: opts.userSystemPrompt, tools, memoryEnabled: true }), memoryBlock);
409
401
  };
410
402
  const a = build(PROBE_MEMORY_A);
411
403
  const b = build(PROBE_MEMORY_B);
@@ -1,4 +1,4 @@
1
- import type { ThinkingLevel } from "../internal/harness.js";
1
+ import type { ThinkingLevel } from "../internal/harness-types.js";
2
2
  import { type ScenarioId } from "./scenario-registry.js";
3
3
  export type OrchestrationMode = "solo" | "teacher";
4
4
  export declare const DEFAULT_REASONING_INTENSITY: ThinkingLevel;
@@ -10,8 +10,5 @@ export interface CodeToolsConfig {
10
10
  taskList?: boolean;
11
11
  taskListStore?: TaskListStore;
12
12
  }
13
- export type FullBodyToolsConfig = CodeToolsConfig;
14
13
  export declare function assembleCodeTools(cfg?: CodeToolsConfig): ToolSpec[];
15
- export declare const assembleFullBodyTools: typeof assembleCodeTools;
16
14
  export declare const CODE_ROLE: RoleSpec;
17
- export declare const FULL_BODY_ROLE: RoleSpec;
@@ -17,6 +17,4 @@ export function assembleCodeTools(cfg = {}) {
17
17
  tools.push(...createTaskListTools(cfg.taskListStore));
18
18
  return tools;
19
19
  }
20
- export const assembleFullBodyTools = assembleCodeTools;
21
20
  export const CODE_ROLE = { systemPrompt: CODE_SYSTEM_PROMPT, thinking: "high" };
22
- export const FULL_BODY_ROLE = CODE_ROLE;
@@ -89,9 +89,12 @@ export function createCcFileMailboxStore(opts) {
89
89
  leases.set(key, { owner, expiresAt: t + ttlMs, maxSeq });
90
90
  return Promise.resolve({ maxSeq, messages: pending });
91
91
  },
92
- ack: (scope, handle, upToSeq) => {
92
+ ack: (scope, handle, owner, upToSeq) => {
93
93
  requireDefaultScope(scope);
94
94
  const path = inboxPath(handle);
95
+ const held = leases.get(path);
96
+ if (held === undefined || held.owner !== owner)
97
+ return Promise.resolve();
95
98
  if (!existsSync(path))
96
99
  return Promise.resolve();
97
100
  return withCcLock(path, () => {
@@ -105,6 +108,8 @@ export function createCcFileMailboxStore(opts) {
105
108
  }
106
109
  if (changed)
107
110
  saveBox(path, box);
111
+ if (held.maxSeq <= upToSeq)
112
+ leases.delete(path);
108
113
  });
109
114
  },
110
115
  releaseLease: (scope, handle, owner) => {
@@ -4,25 +4,13 @@ export interface FileBackgroundAgentStoreOptions {
4
4
  compactEvery?: number;
5
5
  }
6
6
  export declare class FileBackgroundAgentStore implements BackgroundAgentStore {
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;
9
+ private readonly ledger;
14
10
  private closed;
15
11
  private get rows();
16
- private get locks();
17
- private get ledgerEvents();
18
- private set ledgerEvents(value);
19
- private get log();
20
- private set log(value);
21
12
  constructor(root: string, opts?: FileBackgroundAgentStoreOptions);
22
- private readonly canonicalDir;
23
- private key;
24
- private replay;
25
- private applyEvent;
13
+ static key(handle: string, scope: string): string;
26
14
  private withLock;
27
15
  private commit;
28
16
  put(record: BackgroundAgentRecord): Promise<void>;
@@ -53,7 +41,6 @@ export declare class FileBackgroundAgentStore implements BackgroundAgentStore {
53
41
  keep?: number;
54
42
  staleRunningMaxAgeMs?: number;
55
43
  }): Promise<number>;
56
- private compact;
57
44
  compactNow(): void;
58
45
  get size(): number;
59
46
  close(): void;
@@ -1,105 +1,49 @@
1
1
  import { join } from "node:path";
2
- import { BackgroundAgentStoreError, } from "../../core/background-agent-store.js";
3
- import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords } from "./fs-atomic.js";
4
- const sharedAgentDirs = new Map();
2
+ import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, } from "../../core/background-agent-store.js";
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)
@@ -205,7 +149,8 @@ export class FileBackgroundAgentStore {
205
149
  const next = structuredClone(live);
206
150
  next.status = "failed";
207
151
  next.stoppedBy = "system";
208
- next.summary = next.summary ?? "host process interrupted (stale running row reaped)";
152
+ next.summary = next.summary ?? STALE_RUNNING_REAP_ATTRIBUTION;
153
+ next.error = next.error ?? STALE_RUNNING_REAP_ATTRIBUTION;
209
154
  next.settledAt = now;
210
155
  next.updatedAt = now;
211
156
  next.rev = live.rev + 1;
@@ -247,15 +192,8 @@ export class FileBackgroundAgentStore {
247
192
  }
248
193
  return changed.size;
249
194
  }
250
- compact() {
251
- const lines = [...this.rows.values()].map((r) => JSON.stringify(r)).join("\n");
252
- atomicWriteFile(this.tmpDir, this.snapshotPath, lines.length ? `${lines}\n` : "");
253
- this.log.closeForSwap();
254
- atomicWriteFile(this.tmpDir, this.ledgerPath, "");
255
- this.ledgerEvents = 0;
256
- }
257
195
  compactNow() {
258
- this.compact();
196
+ this.ledger.compact();
259
197
  }
260
198
  get size() {
261
199
  return this.rows.size;
@@ -264,14 +202,7 @@ export class FileBackgroundAgentStore {
264
202
  if (this.closed)
265
203
  return;
266
204
  this.closed = true;
267
- const st = sharedAgentDirs.get(this.canonicalDir);
268
- if (st !== undefined && st === this.shared) {
269
- st.refs -= 1;
270
- if (st.refs > 0)
271
- return;
272
- sharedAgentDirs.delete(this.canonicalDir);
273
- }
274
- this.log.close();
205
+ this.ledger.release();
275
206
  }
276
207
  }
277
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;
@@ -21,7 +21,7 @@ export declare class FileMailboxStore implements MailboxStore {
21
21
  sentAt: number;
22
22
  }): Promise<number>;
23
23
  claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
24
- ack(scope: string, handle: string, upToSeq: number): Promise<void>;
24
+ ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
25
25
  releaseLease(scope: string, handle: string, owner: string): Promise<void>;
26
26
  peekCount(scope: string, handle: string): Promise<number>;
27
27
  drop(scope: string, handle: string): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { join, resolve, sep } from "node:path";
2
2
  import { existsSync, realpathSync } from "node:fs";
3
- import {} from "../../core/mailbox-store.js";
3
+ import { newestSentAt, } from "../../core/mailbox-store.js";
4
4
  import { AppendLog, atomicWriteFile, canonicalStoreKey, ensureDir, readJsonlRecords, sanitizeScope, sanitizePathComponent } from "./fs-atomic.js";
5
5
  function realpathSyncSafe(p) {
6
6
  try {
@@ -86,7 +86,7 @@ export class FileMailboxStore {
86
86
  lines.push(JSON.stringify({ t: "lease", ...b.lease }));
87
87
  b.log.closeForSwap();
88
88
  atomicWriteFile(this.tmpDir, path, lines.length ? `${lines.join("\n")}\n` : "");
89
- b.events = lines.length;
89
+ b.events = 0;
90
90
  }
91
91
  async append(scope, handle, msg) {
92
92
  if (scope === undefined || scope === "")
@@ -111,9 +111,11 @@ export class FileMailboxStore {
111
111
  return { messages: b.messages.map((m) => ({ ...m })), maxSeq };
112
112
  });
113
113
  }
114
- async ack(scope, handle, upToSeq) {
114
+ async ack(scope, handle, owner, upToSeq) {
115
115
  return withPathLock(this.lockKey(scope, handle), () => {
116
116
  const b = this.load(scope, handle);
117
+ if (b.lease === undefined || b.lease.owner !== owner)
118
+ return;
117
119
  this.commit(b, this.boxPath(scope, handle), { t: "ack", upToSeq });
118
120
  });
119
121
  }
@@ -160,15 +162,15 @@ export class FileMailboxStore {
160
162
  for (const [key, b] of [...sharedBoxes]) {
161
163
  if (!b.path.startsWith(prefix))
162
164
  continue;
163
- const seen = b.messages[b.messages.length - 1];
164
- if (seen === undefined || seen.sentAt >= now - maxAgeMs)
165
+ const seen = newestSentAt(b.messages);
166
+ if (seen === undefined || seen >= now - maxAgeMs)
165
167
  continue;
166
168
  const swept = await withPathLock(key, () => {
167
169
  const live = sharedBoxes.get(key);
168
170
  if (live === undefined || live !== b)
169
171
  return false;
170
- const newest = b.messages[b.messages.length - 1];
171
- if (newest === undefined || newest.sentAt >= now - maxAgeMs)
172
+ const newest = newestSentAt(b.messages);
173
+ if (newest === undefined || newest >= now - maxAgeMs)
172
174
  return false;
173
175
  b.messages = [];
174
176
  delete b.lease;
@@ -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
+ }