@sema-agent/core 5.19.0 → 5.21.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/CHANGELOG.md +102 -4
- package/dist/agents/agent-definition.d.ts +7 -0
- package/dist/agents/agent-transcript-tool.d.ts +30 -0
- package/dist/agents/builtin-agents.d.ts +60 -0
- package/dist/agents/cascade.d.ts +107 -0
- package/dist/agents/cumulative-stats.d.ts +56 -0
- package/dist/agents/observer.d.ts +162 -0
- package/dist/agents/peer-admission.d.ts +95 -0
- package/dist/agents/repair-loop.d.ts +226 -0
- package/dist/agents/retain-ledger.d.ts +141 -0
- package/dist/agents/roster-store.d.ts +105 -0
- package/dist/agents/roster-store.js +3 -0
- package/dist/agents/send-message-tool.d.ts +92 -0
- package/dist/agents/session-util.d.ts +5 -0
- package/dist/agents/subagent-steps.d.ts +66 -0
- package/dist/agents/subagent.d.ts +600 -0
- package/dist/agents/suspend-guard.d.ts +29 -0
- package/dist/agents/teacher.d.ts +75 -0
- package/dist/agents/team.d.ts +120 -1
- package/dist/agents/tool-filter.d.ts +34 -0
- package/dist/agents/verify.d.ts +198 -0
- package/dist/bench/metrics.d.ts +455 -0
- package/dist/brain/anthropic.d.ts +30 -0
- package/dist/brain/circuit-breaker.d.ts +33 -0
- package/dist/brain/circuit-breaker.js +14 -3
- package/dist/brain/context-overflow.d.ts +60 -3
- package/dist/brain/degrading.d.ts +67 -0
- package/dist/brain/errors.d.ts +42 -0
- package/dist/brain/failover.d.ts +15 -0
- package/dist/brain/media-degrade.d.ts +39 -0
- package/dist/brain/model-presets.d.ts +31 -0
- package/dist/brain/open-responses.d.ts +19 -0
- package/dist/brain/openai.d.ts +46 -0
- package/dist/brain/reasoning.d.ts +106 -1
- package/dist/brain/repetition.d.ts +83 -0
- package/dist/brain/request-params.d.ts +56 -0
- package/dist/brain/retry.d.ts +46 -0
- package/dist/brain/routing.d.ts +12 -0
- package/dist/brain/status-sink.d.ts +30 -1
- package/dist/brain/stream-engine.d.ts +147 -0
- package/dist/brain/stream-shared.d.ts +34 -0
- package/dist/brain/terminal-cause.d.ts +31 -0
- package/dist/brain/timeout.d.ts +108 -0
- package/dist/brain/timeout.js +11 -0
- package/dist/brain/tool-call-id.d.ts +20 -0
- package/dist/brain/tool-call-repair.d.ts +13 -0
- package/dist/config/catalog.d.ts +47 -0
- package/dist/config/defaults.d.ts +33 -0
- package/dist/core/a2a-task-state.d.ts +53 -0
- package/dist/core/a2a.d.ts +51 -0
- package/dist/core/arg-summary.d.ts +62 -0
- package/dist/core/ask-question.d.ts +272 -2
- package/dist/core/auto-compaction.d.ts +467 -0
- package/dist/core/auto-compaction.js +20 -6
- package/dist/core/auto-mode-prompt-assets.d.ts +8 -0
- package/dist/core/auto-mode-prompt.d.ts +27 -0
- package/dist/core/auto-mode.d.ts +54 -3
- package/dist/core/auto-promote.d.ts +100 -0
- package/dist/core/background-agent-store.d.ts +293 -0
- package/dist/core/background-agent-store.js +5 -0
- package/dist/core/background-shell.d.ts +110 -0
- package/dist/core/cache-break-detector.d.ts +34 -0
- package/dist/core/canonical-json.d.ts +57 -0
- package/dist/core/checkpoint-store.d.ts +1574 -16
- package/dist/core/compliance.d.ts +30 -0
- package/dist/core/consolidate-scope.d.ts +75 -0
- package/dist/core/context-edit.d.ts +99 -0
- package/dist/core/context-guard.d.ts +46 -0
- package/dist/core/exec-gate.d.ts +44 -0
- package/dist/core/exec-output-tail.d.ts +61 -0
- package/dist/core/file-snapshot-store.d.ts +104 -0
- package/dist/core/fs-write-gate-policy.d.ts +36 -0
- package/dist/core/git-worktree-env.d.ts +31 -0
- package/dist/core/governance-codes.d.ts +19 -0
- package/dist/core/hooks.d.ts +609 -1
- package/dist/core/human-input-projection.d.ts +37 -0
- package/dist/core/human-input-projection.js +13 -0
- package/dist/core/image-downsample.d.ts +74 -0
- package/dist/core/locked-config.d.ts +37 -0
- package/dist/core/lsp-diagnostics.d.ts +77 -0
- package/dist/core/lsp-protocol.d.ts +29 -0
- package/dist/core/lsp-session.d.ts +60 -1
- package/dist/core/lsp.d.ts +150 -1
- package/dist/core/mailbox-store.d.ts +57 -0
- package/dist/core/mailbox-store.js +2 -0
- package/dist/core/mcp.d.ts +385 -0
- package/dist/core/mcp.js +58 -11
- package/dist/core/media-byte-cap.d.ts +21 -0
- package/dist/core/memory-admission.d.ts +71 -0
- package/dist/core/memory-engine/content-origin.d.ts +64 -0
- package/dist/core/memory-engine/data-plane.d.ts +43 -0
- package/dist/core/memory-engine/dual-root.d.ts +24 -0
- package/dist/core/memory-engine/engine.d.ts +230 -0
- package/dist/core/memory-engine/engine.js +103 -35
- package/dist/core/memory-engine/file-backend.d.ts +155 -0
- package/dist/core/memory-engine/frontmatter.d.ts +16 -0
- package/dist/core/memory-engine/layout.d.ts +217 -0
- package/dist/core/memory-engine/memory-backend-contract.d.ts +12 -0
- package/dist/core/memory-engine/migrate.d.ts +9 -0
- package/dist/core/memory-engine/scan.d.ts +12 -0
- package/dist/core/memory-engine/scope-contract.d.ts +61 -1
- package/dist/core/memory-engine/sync-client.d.ts +87 -0
- package/dist/core/memory-engine/sync.d.ts +60 -0
- package/dist/core/memory-engine/tools.d.ts +41 -0
- package/dist/core/memory-engine/types.d.ts +188 -0
- package/dist/core/memory-recall.d.ts +141 -0
- package/dist/core/memory-vector.d.ts +20 -0
- package/dist/core/memory.d.ts +458 -0
- package/dist/core/message-utils.d.ts +6 -0
- package/dist/core/oracle-isolation.d.ts +69 -0
- package/dist/core/permission-rule-consent.d.ts +138 -0
- package/dist/core/permission-rule-model.d.ts +122 -0
- package/dist/core/permission-rule-store.d.ts +119 -3
- package/dist/core/permission-rules.d.ts +87 -1
- package/dist/core/present-plan-tool.d.ts +20 -0
- package/dist/core/pricing.d.ts +26 -0
- package/dist/core/property-harness.d.ts +86 -0
- package/dist/core/protocol-naming.d.ts +38 -0
- package/dist/core/protocol-table.d.ts +61 -0
- package/dist/core/push-queue.d.ts +1 -0
- package/dist/core/remote-env.d.ts +383 -1
- package/dist/core/retention-policy.d.ts +40 -0
- package/dist/core/retention-policy.js +21 -0
- package/dist/core/retention.d.ts +51 -0
- package/dist/core/roles.d.ts +59 -0
- package/dist/core/runner/active-skill-scope.d.ts +34 -0
- package/dist/core/runner/assemble-result.d.ts +134 -0
- package/dist/core/runner/compaction-call-options.d.ts +97 -1
- package/dist/core/runner/grounding-signal.d.ts +10 -0
- package/dist/core/runner/image.d.ts +17 -0
- package/dist/core/runner/image.js +29 -15
- package/dist/core/runner/memory-consolidation.d.ts +60 -0
- package/dist/core/runner/prepare-memory.d.ts +59 -0
- package/dist/core/runner/prepare-task.d.ts +1011 -2
- package/dist/core/runner/prepare-task.js +51 -14
- package/dist/core/runner/prompt-suggestions.d.ts +7 -0
- package/dist/core/runner/runtask.d.ts +304 -3
- package/dist/core/runner/runtask.js +17 -2
- package/dist/core/runner/session-file-state-replay.d.ts +49 -0
- package/dist/core/runner/session-rule-policy.d.ts +58 -0
- package/dist/core/runner/strict-output-schema.d.ts +18 -3
- package/dist/core/runner/synthetic-tools.d.ts +113 -0
- package/dist/core/runner/teardown-bounded.d.ts +34 -0
- package/dist/core/runner/tool-disclosure.d.ts +227 -0
- package/dist/core/runner/tool-output-projection.d.ts +6 -0
- package/dist/core/runner/turn-attachments.d.ts +634 -0
- package/dist/core/runner/usage-accounting.d.ts +32 -0
- package/dist/core/runtime.d.ts +9 -0
- package/dist/core/safe-notify.d.ts +64 -0
- package/dist/core/safety-axis-vocab.d.ts +23 -0
- package/dist/core/safety-merge-corpus.d.ts +37 -0
- package/dist/core/scheduler.d.ts +121 -0
- package/dist/core/secret-env.d.ts +32 -0
- package/dist/core/select-model.d.ts +15 -0
- package/dist/core/sensitive-path-policy.d.ts +42 -0
- package/dist/core/session-policy-store.d.ts +94 -0
- package/dist/core/session-reconcile.d.ts +80 -0
- package/dist/core/session-store.d.ts +85 -0
- package/dist/core/session.d.ts +153 -0
- package/dist/core/shared-memory/contract.d.ts +22 -0
- package/dist/core/shared-memory/normalize.d.ts +123 -2
- package/dist/core/shared-memory/tools.d.ts +14 -0
- package/dist/core/shared-memory/types.d.ts +105 -0
- package/dist/core/shutdown-debug.d.ts +6 -0
- package/dist/core/side-query.d.ts +38 -0
- package/dist/core/side-query.js +6 -1
- package/dist/core/skill-tool-specifier.d.ts +72 -0
- package/dist/core/skills-directory.d.ts +100 -1
- package/dist/core/spec-contract.d.ts +89 -0
- package/dist/core/store-contracts/background-agent-store-contract.d.ts +31 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +26 -0
- package/dist/core/store-contracts/contract-harness.d.ts +20 -0
- package/dist/core/store-contracts/contract-kit-version.d.ts +27 -0
- package/dist/core/store-contracts/file-snapshot-store-contract.d.ts +8 -0
- package/dist/core/store-contracts/mailbox-store-contract.d.ts +42 -0
- package/dist/core/store-contracts/tool-result-store-contract.d.ts +13 -0
- package/dist/core/strategy-store.d.ts +37 -0
- package/dist/core/stub-env.d.ts +7 -0
- package/dist/core/surrogate-safe-slice.d.ts +35 -0
- package/dist/core/task-notification.d.ts +202 -0
- package/dist/core/task-outcome.d.ts +53 -0
- package/dist/core/task-registry-agent.d.ts +337 -1
- package/dist/core/task-registry-agent.js +2 -0
- package/dist/core/task-registry-monitor.d.ts +12 -0
- package/dist/core/task-registry-shared.d.ts +540 -0
- package/dist/core/task-registry.d.ts +343 -0
- package/dist/core/task-registry.js +13 -2
- package/dist/core/task-tool-shape.d.ts +44 -0
- package/dist/core/tighten-task-spec.d.ts +21 -0
- package/dist/core/tool-detach.d.ts +21 -0
- package/dist/core/tool-errors.d.ts +131 -0
- package/dist/core/tool-errors.js +4 -0
- package/dist/core/tool-name-aliases.d.ts +27 -0
- package/dist/core/tool-policy.d.ts +555 -0
- package/dist/core/tool-policy.js +3 -0
- package/dist/core/tool-result-budget.d.ts +32 -0
- package/dist/core/tool-result-store.d.ts +174 -1
- package/dist/core/tools.d.ts +45 -0
- package/dist/core/trace.d.ts +323 -0
- package/dist/core/types.d.ts +3859 -2
- package/dist/core/untrusted-egress.d.ts +8 -0
- package/dist/core/untrusted-text.d.ts +156 -0
- package/dist/core/usage-window-store.d.ts +95 -0
- package/dist/core/version.d.ts +1 -0
- package/dist/core/warm-resume.d.ts +17 -0
- package/dist/core/wiring-manifest.d.ts +169 -0
- package/dist/core/with-retry.d.ts +24 -0
- package/dist/core/workflow-journal-store.d.ts +160 -0
- package/dist/core/workflow-run-store-contract.d.ts +25 -0
- package/dist/core/workflow-run-store.d.ts +119 -0
- package/dist/core/workflow-run-store.js +2 -0
- package/dist/engine/compaction/compaction.d.ts +256 -1
- package/dist/engine/compaction/utils.d.ts +94 -0
- package/dist/engine/execution-env/kill-tree.d.ts +29 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +94 -0
- package/dist/engine/harness/agent-harness.d.ts +116 -0
- package/dist/engine/harness/agent-harness.js +3 -14
- package/dist/engine/harness/messages.d.ts +15 -0
- package/dist/engine/harness/types.d.ts +464 -2
- package/dist/engine/llm/diagnostics.d.ts +4 -0
- package/dist/engine/llm/event-stream.d.ts +3 -0
- package/dist/engine/llm/index.d.ts +7 -0
- package/dist/engine/llm/types.d.ts +500 -3
- package/dist/engine/llm/validation.d.ts +3 -0
- package/dist/engine/loop/agent-loop.d.ts +87 -2
- package/dist/engine/loop/runtime-deps.d.ts +7 -0
- package/dist/engine/loop/types.d.ts +424 -0
- package/dist/engine/lsp/frame-decoder.d.ts +13 -0
- package/dist/engine/lsp/node-lsp-manager.d.ts +107 -0
- package/dist/engine/lsp/stdio-lsp-transport.d.ts +7 -0
- package/dist/engine/session/import-validate.d.ts +27 -0
- package/dist/engine/session/log-digest.d.ts +93 -0
- package/dist/engine/session/memory-repo.d.ts +6 -0
- package/dist/engine/session/memory-storage.d.ts +2 -0
- package/dist/engine/session/session.d.ts +75 -0
- package/dist/engine/session/storage-base.d.ts +8 -0
- package/dist/fixtures/index.d.ts +36 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +1 -2
- package/dist/internal/harness-types.d.ts +6 -0
- package/dist/internal/harness.d.ts +11 -0
- package/dist/internal/llm.d.ts +6 -0
- package/dist/orchestration/builtin-workflows.d.ts +53 -0
- package/dist/orchestration/dev-vm-script-runner.d.ts +19 -0
- package/dist/orchestration/goal.d.ts +57 -0
- package/dist/orchestration/goal.js +3 -0
- package/dist/orchestration/run-spec.d.ts +42 -0
- package/dist/orchestration/run-spec.js +4 -0
- package/dist/orchestration/run-workflow-tool.d.ts +169 -0
- package/dist/orchestration/workflow-governance.d.ts +61 -0
- package/dist/orchestration/workflow-meta.d.ts +28 -0
- package/dist/orchestration/workflow-observe.d.ts +60 -0
- package/dist/orchestration/workflow-primitives.d.ts +23 -1
- package/dist/orchestration/workflow-sandbox-conformance.d.ts +38 -0
- package/dist/orchestration/workflow-script-runner.d.ts +88 -0
- package/dist/orchestration/workflow-script-store.d.ts +98 -0
- package/dist/orchestration/workflow-size-guideline.d.ts +67 -0
- package/dist/orchestration/workflow-types.d.ts +169 -2
- package/dist/orchestration/workflow.d.ts +358 -0
- package/dist/orchestration/workflow.js +13 -2
- package/dist/prompt-assembly/artifact-store.d.ts +33 -0
- package/dist/prompt-assembly/artifact.d.ts +25 -0
- package/dist/prompt-assembly/assemble.d.ts +20 -0
- package/dist/prompt-assembly/composer.d.ts +29 -0
- package/dist/prompt-assembly/epoch.d.ts +55 -1
- package/dist/prompt-assembly/event-registry.d.ts +35 -0
- package/dist/prompt-assembly/explain.d.ts +12 -0
- package/dist/prompt-assembly/tool-catalog.d.ts +38 -0
- package/dist/prompt-assembly/turn-snapshot.d.ts +40 -0
- package/dist/prompt-assembly/types.d.ts +115 -0
- package/dist/prompts/coordinator.d.ts +27 -0
- package/dist/prompts/default.d.ts +539 -0
- package/dist/prompts/simple-sections.d.ts +45 -0
- package/dist/prompts/supervisor.d.ts +66 -0
- package/dist/scenarios/env.d.ts +28 -0
- package/dist/scenarios/full-body.d.ts +50 -0
- package/dist/scenarios/scenario-registry.d.ts +60 -0
- package/dist/scenarios/teacher-quickstart.d.ts +27 -0
- package/dist/server/http.d.ts +17 -0
- package/dist/stores/cc/lockfile.d.ts +6 -0
- package/dist/stores/cc/mailbox-store.d.ts +8 -0
- package/dist/stores/cc/roster-adapter.d.ts +4 -0
- package/dist/stores/cc/sidecar-transcript.d.ts +66 -0
- package/dist/stores/file/background-agent-store.d.ts +24 -0
- package/dist/stores/file/background-agent-store.js +2 -1
- package/dist/stores/file/checkpoint-store.d.ts +38 -0
- package/dist/stores/file/file-snapshot-store.d.ts +25 -0
- package/dist/stores/file/fs-atomic.d.ts +155 -0
- package/dist/stores/file/index.d.ts +89 -0
- package/dist/stores/file/mailbox-store.d.ts +36 -0
- package/dist/stores/file/mailbox-store.js +2 -0
- package/dist/stores/file/memory-store.d.ts +82 -0
- package/dist/stores/file/permission-rule-store.d.ts +79 -0
- package/dist/stores/file/session-policy-store.d.ts +28 -0
- package/dist/stores/file/session-store.d.ts +40 -0
- package/dist/stores/file/shared-ledger.d.ts +83 -0
- package/dist/stores/file/tool-result-store.d.ts +11 -0
- package/dist/stores/file/usage-window-store.d.ts +18 -0
- package/dist/stores/file/workflow-journal-store.d.ts +100 -0
- package/dist/stores/file/workflow-run-store.d.ts +11 -0
- package/dist/stores/file/workflow-run-store.js +2 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +282 -0
- package/dist/tools/fs/encoding.d.ts +60 -0
- package/dist/tools/fs/fs-bash.d.ts +133 -0
- package/dist/tools/fs/fs-pdf.d.ts +28 -0
- package/dist/tools/fs/fs-search-tools.d.ts +5 -0
- package/dist/tools/fs/fs-shared.d.ts +360 -0
- package/dist/tools/fs/fs-write.d.ts +16 -0
- package/dist/tools/fs/gh-rate-limit.d.ts +22 -0
- package/dist/tools/fs/index.d.ts +79 -0
- package/dist/tools/fs/notebook.d.ts +56 -0
- package/dist/tools/fs/pdf.d.ts +70 -0
- package/dist/tools/fs/pdf.js +8 -2
- package/dist/tools/fs/repo-map.d.ts +10 -0
- package/dist/tools/fs/safety.d.ts +376 -0
- package/dist/tools/fs/search.d.ts +131 -0
- package/dist/tools/loop-tick.d.ts +24 -0
- package/dist/tools/monitor.d.ts +55 -0
- package/dist/tools/scheduler-tools.d.ts +51 -0
- package/dist/tools/sql-adapters.d.ts +18 -0
- package/dist/tools/sql.d.ts +25 -0
- package/dist/tools/task-list.d.ts +77 -0
- package/dist/tools/todo.d.ts +8 -0
- package/dist/tools/web.d.ts +184 -0
- package/dist/tools/web.js +32 -5
- package/dist/tools/worktree.d.ts +81 -0
- package/package.json +2 -2
- package/dist/tools/gitea-issue.d.ts +0 -13
- package/dist/tools/gitea-issue.js +0 -75
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { type BackgroundAgentReapOptions, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentStore, type BackgroundAgentUpdateIfExpect } from "../../core/background-agent-store.js";
|
|
2
2
|
export interface FileBackgroundAgentStoreOptions {
|
|
3
|
+
/** When false, a ledger append is NOT fsync'd. Default true. */
|
|
3
4
|
fsync?: boolean;
|
|
5
|
+
/** Compact the ledger into a snapshot once it exceeds this many events (then truncate). Default 1000. */
|
|
4
6
|
compactEvery?: number;
|
|
5
7
|
}
|
|
6
8
|
export declare class FileBackgroundAgentStore implements BackgroundAgentStore {
|
|
7
9
|
private readonly fsyncEnabled;
|
|
8
10
|
private readonly compactEvery;
|
|
11
|
+
/** RB-55: all mutable state is SHARED per canonical directory (see the note above the class). */
|
|
9
12
|
private readonly ledger;
|
|
13
|
+
/** RB-145: this instance's own close latch — the refcount belongs to the DIRECTORY, not to callers. */
|
|
10
14
|
private closed;
|
|
11
15
|
private get rows();
|
|
12
16
|
constructor(root: string, opts?: FileBackgroundAgentStoreOptions);
|
|
17
|
+
/**
|
|
18
|
+
* RB-60 (2026-07-25, red probe): the composite key must be INJECTIVE — `get`/`update` no longer re-check
|
|
19
|
+
* scope, so tenant isolation rests entirely on this function. A SPACE separator is not injective
|
|
20
|
+
* (`scope="a", handle="b c"` and `scope="a b", handle="c"` produce the same key), which let one tenant's
|
|
21
|
+
* row collide with another's. The in-memory reference implementation (`core/background-agent-store.ts`)
|
|
22
|
+
* uses `\u0000` for exactly this reason ("the composite key keeps the info-hiding property structural") —
|
|
23
|
+
* a NUL can never occur in a scope or handle, so the mapping is injective. Matching it also restores this
|
|
24
|
+
* backend's stated "byte-for-byte the InMemory reference implementation's" claim.
|
|
25
|
+
*
|
|
26
|
+
* MIGRATION: the key is IN-MEMORY only (the ledger stores whole records with their own `scope`/`handle`
|
|
27
|
+
* fields, and replay rebuilds keys through this function), so changing the separator needs no on-disk
|
|
28
|
+
* migration — an existing directory replays into the new key space on next open.
|
|
29
|
+
*/
|
|
13
30
|
static key(handle: string, scope: string): string;
|
|
14
31
|
private withLock;
|
|
32
|
+
/** Durable append FIRST, in-memory flip AFTER, then best-effort compaction — all in the base
|
|
33
|
+
* (RB-150/RB-167: a failing housekeeping compaction must never be reported as this CAS failing). */
|
|
15
34
|
private commit;
|
|
16
35
|
put(record: BackgroundAgentRecord): Promise<void>;
|
|
17
36
|
get(handle: string, scope: string): Promise<BackgroundAgentRecord | null>;
|
|
@@ -32,7 +51,12 @@ export declare class FileBackgroundAgentStore implements BackgroundAgentStore {
|
|
|
32
51
|
rev: number;
|
|
33
52
|
}): Promise<boolean>;
|
|
34
53
|
reap(scope: string, now: number, opts?: BackgroundAgentReapOptions): Promise<number>;
|
|
54
|
+
/** Force a compaction now (test/inspection). RB-167's closeForSwap recipe lives in the base. */
|
|
35
55
|
compactNow(): void;
|
|
56
|
+
/** Test/inspection helper: number of stored rows. */
|
|
36
57
|
get size(): number;
|
|
58
|
+
/** Release the append handle (best-effort). RB-55: refcounted — the directory's shared authority
|
|
59
|
+
* (rows/locks/log) is torn down only when the LAST instance over it closes, so a sibling instance
|
|
60
|
+
* is never left holding a closed fd or a half-dropped view. */
|
|
37
61
|
close(): void;
|
|
38
62
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, queryBackgroundAgents, } from "../../core/background-agent-store.js";
|
|
2
|
+
import { BackgroundAgentStoreError, STALE_RUNNING_REAP_ATTRIBUTION, assertBackgroundAgentReapOptions, queryBackgroundAgents, } from "../../core/background-agent-store.js";
|
|
3
3
|
import { SharedLedgerTable } from "./shared-ledger.js";
|
|
4
4
|
const agentLedgers = new SharedLedgerTable({
|
|
5
5
|
keyOf: (r) => FileBackgroundAgentStore.key(r.handle, r.scope),
|
|
@@ -113,6 +113,7 @@ export class FileBackgroundAgentStore {
|
|
|
113
113
|
});
|
|
114
114
|
}
|
|
115
115
|
async reap(scope, now, opts) {
|
|
116
|
+
assertBackgroundAgentReapOptions(opts);
|
|
116
117
|
if (!opts || (opts.maxAgeMs === undefined && opts.keep === undefined && opts.staleRunningMaxAgeMs === undefined))
|
|
117
118
|
return 0;
|
|
118
119
|
const changed = new Set();
|
|
@@ -1,19 +1,44 @@
|
|
|
1
1
|
import { type Checkpoint, type PendingSteerInput, type CheckpointFaultMode, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type ReopenReason, type ResolveExpectation, type ResumeOutcome } from "../../core/checkpoint-store.js";
|
|
2
2
|
export interface FileCheckpointStoreOptions {
|
|
3
|
+
/** When false, an `appendLine` for a state transition is NOT fsync'd. The checkpoint COMMIT POINT always
|
|
4
|
+
* fsyncs regardless (its crash-safety depends on it); this only affects whether `put` fsyncs. Default true. */
|
|
3
5
|
fsync?: boolean;
|
|
6
|
+
/** Compact the ledger into a snapshot once it exceeds this many events (then truncate). Default 1000. */
|
|
4
7
|
compactEvery?: number;
|
|
5
8
|
}
|
|
6
9
|
export declare class FileCheckpointStore implements CheckpointStore {
|
|
10
|
+
/** design/173 §2.3 — honest declaration on the restart-survival axis the vocabulary claims: rows
|
|
11
|
+
* live on disk (fsync'd append log) and survive a process restart. Multi-replica coordination is
|
|
12
|
+
* NOT claimed by this axis (see {@link StoreDurability}) — this backend is deliberately
|
|
13
|
+
* single-instance-per-data-dir (the boot lock refuses a second process); Pg/TiDB own that. */
|
|
7
14
|
readonly durability: "durable";
|
|
15
|
+
/** Honest declaration on the fidelity axis: the ledger is JSONL, so what survives the restart this
|
|
16
|
+
* backend promises is the JSON PROJECTION of the row — a `Date` replays as its ISO string, a
|
|
17
|
+
* `Map`/`Set` as `{}`, `NaN` as `null`. The in-process map holds the structured clone, which is why
|
|
18
|
+
* the loss is invisible until something reads the disk; the declaration is what makes it visible to
|
|
19
|
+
* the mint BEFORE the row is filed. */
|
|
8
20
|
readonly fidelity: "json";
|
|
9
21
|
private readonly fsyncEnabled;
|
|
10
22
|
private readonly compactEvery;
|
|
23
|
+
/** RB-134: the directory's ONE authority (map + token mutex + append log), joined not rebuilt. */
|
|
11
24
|
private readonly ledger;
|
|
12
25
|
private closed;
|
|
13
26
|
private get cps();
|
|
27
|
+
/** One-shot crash fault (mirrors `InMemoryCheckpointStore.testInjectFault`). */
|
|
14
28
|
private fault;
|
|
15
29
|
constructor(root: string, opts?: FileCheckpointStoreOptions);
|
|
30
|
+
/** Serialize an op behind any in-flight op on the same token (the in-process per-token async mutex). */
|
|
16
31
|
private withLock;
|
|
32
|
+
/**
|
|
33
|
+
* Commit one ledger event (append + fsync) THEN flip the in-memory map (crash-safe ordering, §2.4).
|
|
34
|
+
*
|
|
35
|
+
* RB-150 (2026-07-25, cross-backend differential): the compaction that may follow is HOUSEKEEPING and
|
|
36
|
+
* runs AFTER the operation is durable and the memory flip has happened — so its failure must never be
|
|
37
|
+
* reported as the operation's failure. It was: a snapshot write hitting EACCES made `resolve()` REJECT
|
|
38
|
+
* while the row was already `resolved` and the winner recorded, i.e. a consumed approval presented to
|
|
39
|
+
* the caller as "this did not happen" — the worst possible direction for a once-only gate, and something
|
|
40
|
+
* the in-memory reference cannot do because it has no I/O here. Both halves are now in the base.
|
|
41
|
+
*/
|
|
17
42
|
private commit;
|
|
18
43
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
19
44
|
get(token: CheckpointToken): Promise<Checkpoint | null>;
|
|
@@ -24,8 +49,21 @@ export declare class FileCheckpointStore implements CheckpointStore {
|
|
|
24
49
|
reap(scope: string, cutoff: number): Promise<number>;
|
|
25
50
|
listByScope(scope: string): Promise<CheckpointSummary[]>;
|
|
26
51
|
listScopes(): Promise<string[]>;
|
|
52
|
+
/** Force a compaction now (test/inspection). The snapshot rewrite + RB-167 closeForSwap swap that
|
|
53
|
+
* makes `replay` read the full state from the snapshot alone is the base's {@link LedgerCore.compact}. */
|
|
27
54
|
compactNow(): void;
|
|
55
|
+
/** Arm a one-shot fault on the next `resolve` (mirrors InMemory `testInjectFault`, council #5). */
|
|
28
56
|
testInjectFault(mode: CheckpointFaultMode | null): void;
|
|
57
|
+
/** Test/inspection helper: number of stored checkpoints. */
|
|
29
58
|
get size(): number;
|
|
59
|
+
/**
|
|
60
|
+
* Release the append handle (best-effort). The boot LOCK is released by the backend factory.
|
|
61
|
+
*
|
|
62
|
+
* RB-134: refcounted, and the LAST holder REVOKES the directory's authority. Adding the shared table
|
|
63
|
+
* without this would have repeated RB-73's mistake exactly — a cache with no invalidation: after a
|
|
64
|
+
* `close()` the next `new FileCheckpointStore(sameRoot)` joined a table whose log was already closed and
|
|
65
|
+
* every write threw `log_closed`, instead of replaying the on-disk truth into a fresh authority. Idempotent:
|
|
66
|
+
* a second `close()` on the same instance must not decrement someone else's share.
|
|
67
|
+
*/
|
|
30
68
|
close(): void;
|
|
31
69
|
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
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 interface FileFileSnapshotStoreOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Disclosure sink for a durable read this store treats as ABSENT (ruled 2026-08-03). Same name and
|
|
6
|
+
* same `{path, reason}` shape as the sibling file stores' seat — "unreadable read as missing" and
|
|
7
|
+
* "corrupt read as missing" are one failure grammar, so they get one seat name.
|
|
8
|
+
*
|
|
9
|
+
* Fires for: a corrupt/unreadable MANIFEST collapsed to "no snapshot" (the never-throw seam's
|
|
10
|
+
* documented fail-open — `has`/`restore`/`exportManifest`/`listKeys` all report absence), an
|
|
11
|
+
* unreadable manifest DIRECTORY that degrades an enumeration to "no keys", and a blob-GC pass that
|
|
12
|
+
* aborted because its live set could not be completed (blobs are kept — leaked, not lost).
|
|
13
|
+
*
|
|
14
|
+
* Never fires on plain ENOENT: a scope with no snapshots yet really is empty. Swallow-guarded.
|
|
15
|
+
*/
|
|
4
16
|
onCorruptRead?: (info: {
|
|
5
17
|
path: string;
|
|
6
18
|
reason: string;
|
|
@@ -10,14 +22,27 @@ export declare class FileFileSnapshotStore implements FileSnapshotStore {
|
|
|
10
22
|
private readonly base;
|
|
11
23
|
private readonly blobsDir;
|
|
12
24
|
private readonly manifestsDir;
|
|
25
|
+
/**
|
|
26
|
+
* RB-163 (2026-07-26): the in-flight blob set is authority over a DIRECTORY, so it lives with the
|
|
27
|
+
* directory. On the instance, a second store on the same base ran `reap()` while the first was midway
|
|
28
|
+
* through `importManifest` — its live set did not include the peer's in-flight blobs, so it collected
|
|
29
|
+
* blobs the peer had already published, and the import still reported `ok` with a manifest referencing
|
|
30
|
+
* files that no longer exist. Keyed by {@link canonicalStoreKey} so two spellings of one directory
|
|
31
|
+
* cannot each keep their own idea of what is in flight.
|
|
32
|
+
*/
|
|
13
33
|
private get inFlight();
|
|
14
34
|
private readonly inFlightKey;
|
|
15
35
|
private readonly bounds;
|
|
16
36
|
private readonly onCorruptRead;
|
|
17
37
|
constructor(root: string, bounds?: Partial<FileSnapshotBounds>, opts?: FileFileSnapshotStoreOptions);
|
|
38
|
+
/** The one delivery point for {@link FileFileSnapshotStoreOptions.onCorruptRead}; swallow-guarded
|
|
39
|
+
* here so no call site has to remember (and so the never-throw seam contract cannot be broken by a
|
|
40
|
+
* host's sink). */
|
|
18
41
|
private disclose;
|
|
19
42
|
private scopeDir;
|
|
20
43
|
private manifestPath;
|
|
44
|
+
/** manifestPath, but a key that fails the strict path-component guard → null (so the seam's NEVER-throw
|
|
45
|
+
* contract holds for a direct SDK caller; engine keys are uuidv7 so this never fires in practice). */
|
|
21
46
|
private tryManifestPath;
|
|
22
47
|
private blobPath;
|
|
23
48
|
private readManifest;
|
|
@@ -1,34 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate an engine-minted id (sessionId / tool-result ref) before using it as a path component. These are
|
|
3
|
+
* `uuidv7` / `tr_<sessionId>_<toolCallId>` — already filename-safe — but a strict guard is cheap
|
|
4
|
+
* defense-in-depth: reject `.`/`..`/empty/separators so nothing can traverse out of the store dir.
|
|
5
|
+
*/
|
|
1
6
|
export declare function sanitizePathComponent(raw: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Map a model-influenced memory `scope` to a safe directory name (CC `sanitizePath` parity). Replace every
|
|
9
|
+
* non-alphanumeric run with `-`; if the result is over 200 chars, append a short content hash so two long
|
|
10
|
+
* scopes can't collide. The ORIGINAL scope is the partition key the engine passes — this only affects the
|
|
11
|
+
* on-disk dir name, never the stored data. Never `path.join` a raw scope.
|
|
12
|
+
*/
|
|
2
13
|
export declare function sanitizeScope(scope: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the data root (CC `getClaudeConfigHomeDir` analog): `$AGENT_DATA_DIR ?? ~/.ai-agent`,
|
|
16
|
+
* NFC-normalized, and `realpath`-canonicalized once the dir exists. Creates the dir (0o700) if absent.
|
|
17
|
+
*/
|
|
3
18
|
export declare function resolveDataRoot(explicit?: string): string;
|
|
19
|
+
/** Ensure a directory exists with 0o700 perms (idempotent). */
|
|
20
|
+
/**
|
|
21
|
+
* Atomically CREATE `target` carrying `content` — it appears in ONE step already fully written, no empty/partial
|
|
22
|
+
* window. Fixes the create-then-write race (codex review BUG-1/BUG-3): a reader/pruner can see a half-written
|
|
23
|
+
* file, and a crash mid-write leaves a zero-byte target a retry would silently keep. Writes a temp in `target`'s
|
|
24
|
+
* OWN dir (same FS → `link` is atomic) + fsync, then `linkSync`-publishes it; throws `EEXIST` if `target` exists.
|
|
25
|
+
*/
|
|
4
26
|
export declare function writeThenLink(target: string, content: string | Uint8Array): void;
|
|
5
27
|
export declare function ensureDir(dir: string): void;
|
|
28
|
+
/**
|
|
29
|
+
* The 5-step atomic whole-file replace (§2.1). Skipping any of steps 3/5/6 is the classic corruption bug:
|
|
30
|
+
* 1. open a temp under `root/tmp/` (same FS → rename is atomic),
|
|
31
|
+
* 2. write the bytes,
|
|
32
|
+
* 3. fsync the DATA (else ext4 "zero-length file after crash"),
|
|
33
|
+
* 4. close → rename(tmp, target) (atomic intra-FS namespace swap),
|
|
34
|
+
* 5. fsync the containing DIRECTORY (persists the rename itself).
|
|
35
|
+
* On any failure the temp is unlinked so a crashed write leaves no scratch behind.
|
|
36
|
+
*/
|
|
6
37
|
export declare function atomicWriteFile(tmpDir: string, target: string, bytes: string): void;
|
|
38
|
+
/**
|
|
39
|
+
* Read a JSONL file and return every COMPLETE, parseable record — the universal torn-tail recovery (§2.2):
|
|
40
|
+
* - a trailing line with no terminating `\n` (a crash mid-write) is discarded,
|
|
41
|
+
* - any line (interior or tail) that fails `JSON.parse` is skipped (never throws — `listRemoteAgentMetadata`
|
|
42
|
+
* parity), so a single corrupt interior line never poisons the whole replay.
|
|
43
|
+
* A missing file → `[]`. This is why JSONL beats one big JSON blob: a torn tail can never corrupt the store.
|
|
44
|
+
*
|
|
45
|
+
* `onCorrupt` (optional, review-510 disclosure-seat family): fired once per skipped INTERIOR record so a
|
|
46
|
+
* caller-side seat can observe real corruption — a silently-skipped record is otherwise indistinguishable
|
|
47
|
+
* from "less data". The torn TAIL stays out of it by design: dying mid-write is the documented crash
|
|
48
|
+
* shape this format exists to absorb, not corruption. The callback is invoked as given — a seat that
|
|
49
|
+
* must not throw swallow-guards on ITS side (the stores do).
|
|
50
|
+
*/
|
|
7
51
|
export declare function readJsonlRecords<T>(path: string, onCorrupt?: (info: {
|
|
8
52
|
path: string;
|
|
9
53
|
reason: string;
|
|
10
54
|
}) => void): T[];
|
|
55
|
+
/**
|
|
56
|
+
* An append-only JSONL log opened once with `O_APPEND` and held for the file's lifetime. One record =
|
|
57
|
+
* one `JSON.stringify(...) + "\n"` written in a SINGLE `writeSync` call (never split across writes → the
|
|
58
|
+
* only torn line possible is a crash tail, which {@link readJsonlRecords} drops). `fsync` is opt-in per
|
|
59
|
+
* append (the checkpoint commit point ALWAYS fsyncs; session/memory cadence is the caller's `fsyncEvery`).
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* RB-144 (2026-07-25, 按面收口): THE canonical key for "one authority per physical location".
|
|
63
|
+
*
|
|
64
|
+
* Every file backend keeps a module-level table so that N instances over one directory collapse into one
|
|
65
|
+
* CAS authority. Getting the KEY wrong reopens the exact defect the table exists to close — and this repo
|
|
66
|
+
* has now paid for that four separate times: RB-62 taught the mailbox to realpath, RB-101 taught the
|
|
67
|
+
* session store, RB-119 added case folding there, and a defect hunt then found the agent store, the run store
|
|
68
|
+
* and the task-list still keying on a lexical `resolve()` while two of them cite the mailbox as the
|
|
69
|
+
* precedent they copied. Point fixes kept missing siblings, so the rule now lives in ONE place that all of
|
|
70
|
+
* them call.
|
|
71
|
+
*
|
|
72
|
+
* Two normalizations, each for a demonstrated failure:
|
|
73
|
+
* - REALPATH — a symlinked data dir (`/var` → `/private/var` on macOS, a container bind-mount, a linked
|
|
74
|
+
* `~/.ai-agent`) otherwise yields two authorities for one directory, and both writers win the CAS.
|
|
75
|
+
* - CASE FOLD — on a case-insensitive filesystem (macOS/Windows default) `Foo/` and `foo/` are the same
|
|
76
|
+
* directory; `realpath` does NOT fold case there, so realpath alone is not enough (measured).
|
|
77
|
+
*
|
|
78
|
+
* The key is used ONLY for table lookup — never for I/O, so the on-disk name stays verbatim. A path that
|
|
79
|
+
* does not exist yet resolves through its parent directory; if even that fails, the lexical form is used
|
|
80
|
+
* (a store must not fail to construct because canonicalization is unavailable).
|
|
81
|
+
*/
|
|
11
82
|
export declare function canonicalStoreKey(p: string): string;
|
|
12
83
|
export declare class AppendLog {
|
|
13
84
|
private fd;
|
|
85
|
+
/**
|
|
86
|
+
* RB-167 (2026-07-26): distinguishes "closed because the holder is done with it" from "closed for a
|
|
87
|
+
* compaction swap". Only the first is permanent.
|
|
88
|
+
*
|
|
89
|
+
* Every store that compacts does close → rewrite → reopen, and the rewrite can fail (EACCES, ENOSPC, a
|
|
90
|
+
* read-only mount, an immutable flag). The stores handled that in three different and individually
|
|
91
|
+
* insufficient ways: three of them left the log CLOSED FOREVER, so one transient I/O error turned into
|
|
92
|
+
* `log_closed` on every later write — and because these logs are now SHARED per directory, that bricks
|
|
93
|
+
* every instance on it, not just the one that compacted. The fourth reopened in a `finally`, which only
|
|
94
|
+
* covers the failure shapes where reopening still works and, worse, replaces the original error with the
|
|
95
|
+
* reopen's own when it does not.
|
|
96
|
+
*
|
|
97
|
+
* The reopen therefore belongs here, lazily: a swap-closed log reopens on its next write, so a fault that
|
|
98
|
+
* has cleared simply resolves itself, a fault that has not surfaces at the write (with its own error, the
|
|
99
|
+
* original having already propagated from the compaction), and `close()` keeps meaning exactly what
|
|
100
|
+
* RB-99 made it mean.
|
|
101
|
+
*/
|
|
14
102
|
private disposed;
|
|
15
103
|
private readonly path;
|
|
104
|
+
/**
|
|
105
|
+
* RB-438 — the file size as of the end of THIS log's last successful append (or of the last time it
|
|
106
|
+
* observed the file at a record boundary). `-1` = unknown, re-establish on the next append.
|
|
107
|
+
*
|
|
108
|
+
* It is the cheap arbiter for "did the file change under us since we last left it whole": equal ⇒
|
|
109
|
+
* nothing to check, different ⇒ read the last byte and repair if it is not a newline. See
|
|
110
|
+
* {@link repairTornTail}.
|
|
111
|
+
*/
|
|
16
112
|
private boundary;
|
|
17
113
|
constructor(path: string);
|
|
18
114
|
private sizeOrUnknown;
|
|
115
|
+
/**
|
|
116
|
+
* RB-438 (2026-07-31, hardening probe R2) — re-establish the last-complete-record boundary before a write.
|
|
117
|
+
*
|
|
118
|
+
* The constructor's {@link truncateTornTail} (and the RB-167 lazy reopen) only ever ran at OPEN time, and
|
|
119
|
+
* the RB-66 loop below only repairs a SHORT write — not a `writeSync` that THROWS mid-loop (ENOSPC/EDQUOT/
|
|
120
|
+
* EIO after a partial `write(2)`, the classic full-volume shape) and not the `n <= 0` guard. Both leave
|
|
121
|
+
* `written` bytes on disk with no terminating newline while the fd and this object stay live, and every
|
|
122
|
+
* caller retries through the SAME AppendLog (`FileSessionStorage.appendEntry` throws before `recordEntry`,
|
|
123
|
+
* so the in-memory leaf is unchanged and the CAS retry appends again). The retry's record was then GLUED
|
|
124
|
+
* onto the remnant, so replay dropped BOTH — after that second append had returned and fsync'd. Measured:
|
|
125
|
+
* records [1,2,3] + a torn remnant + appends 5,6 replayed as [1,2,3,6].
|
|
126
|
+
*
|
|
127
|
+
* Cost on the healthy path is ONE `fstat` per append (the sizes match and we return); the whole-file read
|
|
128
|
+
* only happens when the file actually moved off a boundary. A legitimate concurrent appender (another
|
|
129
|
+
* process under the boot-lock handoff) leaves the file newline-terminated, so it is adopted, not truncated.
|
|
130
|
+
*/
|
|
19
131
|
private repairTornTail;
|
|
132
|
+
/**
|
|
133
|
+
* Append one record (`json + "\n"`). `fsync:true` makes the record durable before return.
|
|
134
|
+
*
|
|
135
|
+
* RB-66 (2026-07-25, red probe): `writeSync(fd, string)` issues ONE write(2) and may SHORT-WRITE — the
|
|
136
|
+
* same defect class this file's own `writeThenLink` already documents and fixed ("silently SHORT-WRITES
|
|
137
|
+
* (codex/Opus review B1)"), never propagated to this shared primitive. A short write here leaves a torn
|
|
138
|
+
* line with no trailing newline, so the NEXT append concatenates onto it and replay drops BOTH records.
|
|
139
|
+
* Loop until every byte lands (write(2) returns how many it took) — the O_APPEND atomicity per call is
|
|
140
|
+
* unchanged, and a genuine I/O error still throws.
|
|
141
|
+
*/
|
|
20
142
|
append(record: unknown, fsync: boolean): void;
|
|
21
143
|
close(): void;
|
|
144
|
+
/**
|
|
145
|
+
* RB-167 — release the descriptor for a compaction SWAP. The next write reopens it lazily.
|
|
146
|
+
*
|
|
147
|
+
* Callers must use this rather than `close()` around a rewrite: `close()` means "this log is finished",
|
|
148
|
+
* and a store that used it for a swap and then failed to rewrite left the log permanently unusable —
|
|
149
|
+
* one transient I/O error becoming a permanent brick, shared by every instance on that directory.
|
|
150
|
+
*/
|
|
22
151
|
closeForSwap(): void;
|
|
23
152
|
private releaseFd;
|
|
24
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* The SINGLE coarse boot guard (§2.4): an `O_EXCL` PID file at `root/LOCK` that forbids two processes
|
|
156
|
+
* sharing a data dir. A second instance fails fast ("another instance owns this data dir"). A STALE lock
|
|
157
|
+
* (the writing PID is dead) is pruned and re-acquired — `proper-lockfile`/CC `concurrentSessions` parity.
|
|
158
|
+
*
|
|
159
|
+
* This is the ONLY legitimate file lock in the backend: the once-only CAS is in-process (one event loop +
|
|
160
|
+
* a per-token async mutex), so there is NO per-operation flock — this fence just guarantees the
|
|
161
|
+
* in-process model's premise (a single writer to the dir) holds. Cross-process CORRECT concurrency is the
|
|
162
|
+
* Pg/TiDB backend's job, by design.
|
|
163
|
+
*/
|
|
25
164
|
export declare class BootLock {
|
|
26
165
|
private readonly lockPath;
|
|
27
166
|
private held;
|
|
28
167
|
constructor(lockPath: string);
|
|
168
|
+
/** Acquire the lock or throw. Prunes a stale lock whose recorded PID is not running. */
|
|
29
169
|
acquire(): void;
|
|
30
170
|
private writeLock;
|
|
31
171
|
private readLockPid;
|
|
172
|
+
/** Release the lock (only if we hold it). Best-effort; never throws. */
|
|
32
173
|
release(): void;
|
|
33
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* design/84 Seam B (TOC profile) — a per-scope CONSOLIDATION lock factory for the file backend's
|
|
177
|
+
* {@link import("../../core/consolidate-scope.js").ConsolidateScopeDeps.acquire} injection point. A single
|
|
178
|
+
* machine may run a periodic-consolidation timer AND a task-end inline pass concurrently; this advisory lock
|
|
179
|
+
* stops two processes consolidating the SAME scope at once (the in-process model the file backend assumes —
|
|
180
|
+
* see {@link BootLock} — does not cover a second OS process with its own timer).
|
|
181
|
+
*
|
|
182
|
+
* `acquire(scope)` writes `lockDir/<sanitizedScope>.consolidate.lock` carrying THIS pid (atomic
|
|
183
|
+
* {@link writeThenLink}, so the lock appears with its pid already in it — no empty-window prune race). It
|
|
184
|
+
* returns a `release` callback on success, or `undefined` when a LIVE owner already holds it (the caller
|
|
185
|
+
* treats `undefined` as "busy → skip this pass", a no-op). A STALE lock (recorded pid is dead) is pruned and
|
|
186
|
+
* re-acquired once. The implementation lives HERE (the deployment shell's persist/exec axis), NOT core — core
|
|
187
|
+
* only DEFINES the injection point (the constitutional split: gate-presence in core, mechanism in the profile).
|
|
188
|
+
*/
|
|
34
189
|
export declare function createFileConsolidationLock(lockDir: string): (scope: string) => (() => void) | undefined;
|
|
@@ -18,38 +18,127 @@ export { FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResu
|
|
|
18
18
|
export { FileUsageWindowStore } from "./usage-window-store.js";
|
|
19
19
|
export { resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock } from "./fs-atomic.js";
|
|
20
20
|
export { atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog } from "./fs-atomic.js";
|
|
21
|
+
/**
|
|
22
|
+
* design/80 — the file-backed `StorageBackend` for the local (TOC) single-user binary: the 3rd store impl
|
|
23
|
+
* alongside `InMemory*` and `Pg*`, selected purely by what the embedder injects into `RunnerDeps` (no engine
|
|
24
|
+
* change). Construct one over a single data root and pass its four members straight into `RunnerDeps`:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const backend = new FileStorageBackend(); // ~/.ai-agent (or $AGENT_DATA_DIR)
|
|
28
|
+
* const runner = new Runner({
|
|
29
|
+
* brain,
|
|
30
|
+
* sessionStore: backend.sessionStore,
|
|
31
|
+
* checkpointStore: backend.checkpointStore,
|
|
32
|
+
* memoryStore: backend.memoryStore,
|
|
33
|
+
* toolResultStore: backend.toolResultStore,
|
|
34
|
+
* });
|
|
35
|
+
* // …on shutdown:
|
|
36
|
+
* await backend.dispose();
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* **Single-instance-per-data-dir (§2.4 / §7 decision 4):** the constructor takes a coarse boot `flock` on
|
|
40
|
+
* `root/LOCK`. A second `FileStorageBackend` over the same dir FAILS FAST ("another instance owns this data
|
|
41
|
+
* dir"); a stale lock from a dead PID is pruned. This is the ONLY file lock here — the once-only checkpoint
|
|
42
|
+
* CAS is in-process (one event loop), so there is no per-operation lock. Cross-process correct concurrency
|
|
43
|
+
* is the Pg/TiDB backend's job, by design.
|
|
44
|
+
*/
|
|
21
45
|
export interface FileStorageBackendOptions {
|
|
46
|
+
/** Data root. Default `$AGENT_DATA_DIR ?? ~/.ai-agent`, NFC-normalized + realpath-canonicalized. */
|
|
22
47
|
root?: string;
|
|
48
|
+
/** Profile-injected promotable-write gate (design/77 §2). Threaded into `guardedMemoryStore`. */
|
|
23
49
|
utilityGate?: UtilityGate;
|
|
50
|
+
/** `TtlSessionStore` idle-eviction policy. Default `"forget"` (§7 decision 5 — durable history is never
|
|
51
|
+
* deleted by an idle timer). */
|
|
24
52
|
evict?: EvictPolicy;
|
|
53
|
+
/** Checkpoint store tuning (fsync cadence for `put`, ledger compaction threshold). */
|
|
25
54
|
checkpoint?: FileCheckpointStoreOptions;
|
|
55
|
+
/** design/101 §E19 — file-snapshot enumerator bounds (maxFiles/maxBytes/ignoreDirs). Default
|
|
56
|
+
* {@link DEFAULT_SNAPSHOT_BOUNDS}. */
|
|
26
57
|
snapshotBounds?: Partial<import("../../core/file-snapshot-store.js").FileSnapshotBounds>;
|
|
58
|
+
/** design/81 Slice 5 — an optional {@link import("../../core/memory.js").Embedder}. Injected ⇒ the memory
|
|
59
|
+
* store does portable (in-process) vector recall over a rebuildable sidecar index (`vectorMode:"portable"`);
|
|
60
|
+
* absent ⇒ the lexical floor, byte-identical to before. Config-driven (the deployment injects it); OFF by
|
|
61
|
+
* default. 🔴 the consolidation band `{0.05,0.3}` is tuned for the lexical distance — a real embedding model
|
|
62
|
+
* needs it re-tuned + a no-mis-merge live check BEFORE production-enable (design/81 Slice 5 release gate). */
|
|
27
63
|
embedder?: import("../../core/memory.js").Embedder;
|
|
64
|
+
/**
|
|
65
|
+
* Disclosure sink for a durable read this backend's stores treat as ABSENT because the bytes were
|
|
66
|
+
* corrupt or unreadable (never fires on plain ENOENT — real absence).
|
|
67
|
+
*
|
|
68
|
+
* Why it lives on the BACKEND options and not only on the individual store: the fail-open it discloses
|
|
69
|
+
* exists for the DEFAULT deployment (a corrupt policy file yields an apparently normal, UNCONSTRAINED
|
|
70
|
+
* task), and the default deployment constructs its stores through this one-argument constructor — the
|
|
71
|
+
* per-store constructor parameter was unreachable from here, so the disclosure was wired exactly
|
|
72
|
+
* nowhere for the deployments that need it most.
|
|
73
|
+
*
|
|
74
|
+
* Reach TODAY: {@link FileStorageBackend.sessionPolicyStore} (rules read as ABSENT ⇒ an unconstrained
|
|
75
|
+
* task), {@link FileStorageBackend.sessionStore}'s repo (a session listing that silently omits what it
|
|
76
|
+
* could not read) and {@link FileStorageBackend.fileSnapshotStore} (a snapshot/scope read as missing,
|
|
77
|
+
* and a blob GC that aborted). The roster and CC-mailbox stores also carry the seat, but a deployment
|
|
78
|
+
* builds those itself and passes the sink directly. A store that grows the seat later is wired here in
|
|
79
|
+
* the same constructor.
|
|
80
|
+
*
|
|
81
|
+
* The payload is the UNION shape ({@link FileStorageCorruptReadInfo}): `path` + `reason` always,
|
|
82
|
+
* with the session-policy face's `sessionId`/`principal` present only when the reading store keys by
|
|
83
|
+
* them. One sink for one failure grammar — a host does not register three.
|
|
84
|
+
*/
|
|
28
85
|
onCorruptRead?: (info: FileStorageCorruptReadInfo) => void;
|
|
29
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* The payload of {@link FileStorageBackendOptions.onCorruptRead} — the widest shape any store this
|
|
89
|
+
* backend constructs can deliver, so ONE sink reaches all of them (a per-store payload type would make
|
|
90
|
+
* the backend's forwarding contravariance-illegal, which is what kept the seat single-store).
|
|
91
|
+
* `SessionPolicyCorruptReadInfo` is assignable to it; the two-field store seats are satisfied by it.
|
|
92
|
+
*/
|
|
30
93
|
export interface FileStorageCorruptReadInfo {
|
|
94
|
+
/** Absolute path of the offending file/directory. */
|
|
31
95
|
path: string;
|
|
96
|
+
/** What was observed and what the read degraded to. */
|
|
32
97
|
reason: string;
|
|
98
|
+
/** Present only when the reading store keys by session (the session-policy face). */
|
|
33
99
|
sessionId?: string;
|
|
100
|
+
/** Present only when the read was keyed by a principal. */
|
|
34
101
|
principal?: string;
|
|
35
102
|
}
|
|
36
103
|
export declare class FileStorageBackend {
|
|
104
|
+
/** The resolved, canonical data root all four stores live under. */
|
|
37
105
|
readonly root: string;
|
|
38
106
|
readonly sessionStore: SessionStore;
|
|
39
107
|
readonly checkpointStore: CheckpointStore;
|
|
108
|
+
/** Wrapped in `guardedMemoryStore` (Gate-2 chokepoint). design/81: implements the id-addressable trio on
|
|
109
|
+
* a lexical cosine-distance → `supportsConsolidation` is true → end-of-task consolidation lights up. */
|
|
40
110
|
readonly memoryStore: MemoryStore;
|
|
41
111
|
readonly toolResultStore: ToolResultStore;
|
|
112
|
+
/** design/99 §E6 — file-backed per-session permission rules (E6). Wire into `RunnerDeps.sessionPolicyStore`
|
|
113
|
+
* for a local deployment that needs session rules to survive a restart. */
|
|
42
114
|
readonly sessionPolicyStore: SessionPolicyStore;
|
|
115
|
+
/** design/101 §E19 — file-backed working-tree snapshots (rewind-files). Wire into
|
|
116
|
+
* `RunnerDeps.fileSnapshotStore`. Content-addressed blobs persist across restarts (local-only; a remote env
|
|
117
|
+
* defers rewind to its own VM-snapshot backend). */
|
|
43
118
|
readonly fileSnapshotStore: FileSnapshotStore;
|
|
119
|
+
/** SVC-2 / [381]⑥ (onboarded per [375]) — file-backed durable workflow resume journal. Wire into
|
|
120
|
+
* `RunnerDeps.workflowJournalStore` so `resumeFromRunId` survives a process restart on a TOC box. */
|
|
44
121
|
readonly workflowJournalStore: WorkflowJournalStore;
|
|
122
|
+
/** design/164 件五 — file-backed cross-task usage-governance ledger. Wire into
|
|
123
|
+
* `RunnerDeps.usageWindowStore` so a deployment's `usageWindows` survive a restart (an in-memory
|
|
124
|
+
* ledger silently re-grants every allowance on every boot). */
|
|
45
125
|
readonly usageWindowStore: UsageWindowStore;
|
|
126
|
+
/**
|
|
127
|
+
* design/84 Seam B (TOC) — the per-scope consolidation lock for `consolidateScope`'s `acquire` injection
|
|
128
|
+
* point: `consolidateScope(scope, { store: backend.memoryStore, llm, acquire: backend.consolidationLock })`.
|
|
129
|
+
* Stops two OS processes sharing this data dir from consolidating the SAME scope at once (a busy scope ⇒
|
|
130
|
+
* `undefined` ⇒ the pass is a no-op). The locks live under `root/consolidation-locks/`.
|
|
131
|
+
*/
|
|
46
132
|
readonly consolidationLock: (scope: string) => (() => void) | undefined;
|
|
47
133
|
private readonly lock;
|
|
48
134
|
private readonly fileCheckpoints;
|
|
49
135
|
private readonly fileMemory;
|
|
50
136
|
private readonly ttl;
|
|
137
|
+
/** RB-437 — kept so {@link dispose} can release the shared per-session authorities (and their append
|
|
138
|
+
* descriptors) this backend joined; `TtlSessionStore.dispose()` only clears the sweep timer. */
|
|
51
139
|
private readonly fileSessions;
|
|
52
140
|
private readonly fileWorkflowJournal;
|
|
53
141
|
constructor(opts?: FileStorageBackendOptions);
|
|
142
|
+
/** Release file handles + the boot lock (best-effort). Call on shutdown so a successor can acquire. */
|
|
54
143
|
dispose(): Promise<void>;
|
|
55
144
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { type MailboxAppendMessage, type MailboxLease, type MailboxStore } from "../../core/mailbox-store.js";
|
|
2
2
|
export interface FileMailboxStoreOptions {
|
|
3
|
+
/** When false, ledger appends are NOT fsync'd. Default true. */
|
|
3
4
|
fsync?: boolean;
|
|
5
|
+
/** Compact a box's event log into a snapshot-form log once it exceeds this many events. Default 500. */
|
|
4
6
|
compactEvery?: number;
|
|
7
|
+
/** Disclosure seat (review-510 family; server pickup receipt named this store's gap): a corrupt
|
|
8
|
+
* INTERIOR record dropped by the JSONL replay is disclosed here instead of silently reading as
|
|
9
|
+
* "less mail". The torn tail stays silent by design — that is the append-log's documented
|
|
10
|
+
* crash-recovery shape, not corruption. Swallow-guarded; absent = the old silent tolerance. */
|
|
5
11
|
onCorruptRead?: (info: {
|
|
6
12
|
path: string;
|
|
7
13
|
reason: string;
|
|
@@ -12,10 +18,39 @@ export declare class FileMailboxStore implements MailboxStore {
|
|
|
12
18
|
private readonly tmpDir;
|
|
13
19
|
private readonly fsyncEnabled;
|
|
14
20
|
private readonly compactEvery;
|
|
21
|
+
/** Paths THIS instance has touched → the EXACT BoxState it took a ref on (server[1523] 对等复审
|
|
22
|
+
* 发现1: after a drop/reap deletes the shared box, another instance's stale close() must not
|
|
23
|
+
* decrement a REBUILT box's refs — identity is checked, not just the path). */
|
|
15
24
|
private readonly touched;
|
|
25
|
+
/** The swallow-guarded corrupt-read seat (undefined = the old silent tolerance). */
|
|
16
26
|
private readonly discloseCorrupt;
|
|
17
27
|
constructor(root: string, opts?: FileMailboxStoreOptions);
|
|
28
|
+
/**
|
|
29
|
+
* RB-67 (2026-07-25, red probe): the on-disk name is CASE-FOLDED, so the in-memory key and the physical
|
|
30
|
+
* file can never disagree. Before this, `AgentOne` and `agentone` produced two DIFFERENT shared-state
|
|
31
|
+
* keys but the SAME file on a case-insensitive filesystem (macOS/Windows default) — one recipient read
|
|
32
|
+
* the other's messages after a restart. Folding is also the semantically correct answer, not just the
|
|
33
|
+
* safe one: the addressing layer's `normalizeAgentName` (task-registry.ts:621) already lowercases, so
|
|
34
|
+
* two case-variant handles ARE the same agent; this makes the storage layer say so explicitly instead of
|
|
35
|
+
* depending on an upstream coincidence, and behave identically on case-SENSITIVE filesystems (Linux),
|
|
36
|
+
* where the old code silently split one logical agent's mail across two boxes.
|
|
37
|
+
*/
|
|
18
38
|
private boxPath;
|
|
39
|
+
/**
|
|
40
|
+
* The canonical mutex / shared-state key — instance-independent (X-5).
|
|
41
|
+
*
|
|
42
|
+
* RB-162 (2026-07-26): this went through `realpath` (RB-62) but NOT the case fold (RB-67/RB-119), even
|
|
43
|
+
* though the module header above claims "the shared-state KEY goes through `canonicalStoreKey`, which
|
|
44
|
+
* also folds case". It did not — that name appeared exactly once in this file, inside that sentence.
|
|
45
|
+
* The handle component was folded by `boxPath`, but the ROOT was not: two spellings of the same
|
|
46
|
+
* directory on a case-insensitive filesystem produced two shared-state slots over one physical file, and
|
|
47
|
+
* each allocated its own `seq` — the very contract violation the realpath comment above describes,
|
|
48
|
+
* reached by the other half of the same canonicalization.
|
|
49
|
+
*
|
|
50
|
+
* Worse for the record: the enumerative guard written for this exact family tested
|
|
51
|
+
* `src.includes("canonicalStoreKey")`, which that one comment satisfied. The guard was green because the
|
|
52
|
+
* file talked about the rule.
|
|
53
|
+
*/
|
|
19
54
|
private lockKey;
|
|
20
55
|
private load;
|
|
21
56
|
private commit;
|
|
@@ -29,5 +64,6 @@ export declare class FileMailboxStore implements MailboxStore {
|
|
|
29
64
|
reap(scope: string, now: number, opts?: {
|
|
30
65
|
maxAgeMs?: number;
|
|
31
66
|
}): Promise<number>;
|
|
67
|
+
/** Release THIS instance's share of the box handles (fd closes when the last instance leaves). */
|
|
32
68
|
close(): void;
|
|
33
69
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join, resolve, sep } from "node:path";
|
|
2
|
+
import { assertRetentionPolicy } from "../../core/retention-policy.js";
|
|
2
3
|
import { existsSync, readdirSync, realpathSync } from "node:fs";
|
|
3
4
|
import { newestSentAt, } from "../../core/mailbox-store.js";
|
|
4
5
|
import { AppendLog, atomicWriteFile, canonicalStoreKey, ensureDir, readJsonlRecords, sanitizeScope, sanitizePathComponent } from "./fs-atomic.js";
|
|
@@ -176,6 +177,7 @@ export class FileMailboxStore {
|
|
|
176
177
|
});
|
|
177
178
|
}
|
|
178
179
|
async reap(scope, now, opts) {
|
|
180
|
+
assertRetentionPolicy("MailboxStore.reap", opts);
|
|
179
181
|
if (opts?.maxAgeMs === undefined)
|
|
180
182
|
return 0;
|
|
181
183
|
let dropped = 0;
|