@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,56 +1,211 @@
|
|
|
1
1
|
import { scopeDirName, type MemoryPartitionIncidentSink } from "./layout.js";
|
|
2
2
|
import type { HarvestRejection, MemoryBackend, MemoryEntry, MemoryEntryHeader, NotePatch, PatchReport, ScoredMemoryEntry } from "./types.js";
|
|
3
|
+
/** Files/dirs the entry scan never treats as entries: the derived index, dotfiles (`.hydrate`,
|
|
4
|
+
* transaction staging files), and anything not `.md`. */
|
|
3
5
|
export declare const MEMORY_INDEX_FILENAME = "MEMORY.md";
|
|
6
|
+
/** Default nesting depth cap under a scope dir (CC memory is flat; a small allowance for topic dirs). */
|
|
4
7
|
export declare const DEFAULT_MAX_ENTRY_DEPTH = 3;
|
|
8
|
+
/** One on-disk entry file the scan found. */
|
|
5
9
|
export interface ScannedEntryFile {
|
|
10
|
+
/** Absolute path. */
|
|
6
11
|
path: string;
|
|
12
|
+
/** Slug = path relative to the scope dir, without `.md` (may contain `/` for nested topics). */
|
|
7
13
|
slug: string;
|
|
8
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Enumerate a scope dir's entry files (depth-capped, symlinks skipped, dotfiles/index/non-`.md`
|
|
17
|
+
* ignored, `exclude` dirs — sibling scope homes under the root — skipped). Shared by the backend
|
|
18
|
+
* and the engine's harvest scan so the two can never disagree on what counts as an entry file.
|
|
19
|
+
*/
|
|
9
20
|
export declare function scanEntryFiles(dir: string, opts?: {
|
|
10
21
|
maxDepth?: number;
|
|
11
22
|
exclude?: ReadonlySet<string>;
|
|
23
|
+
/** Harvest gate hook: called for every path the scan SKIPS, with why (backend scans ignore it). */
|
|
12
24
|
onSkip?: (path: string, kind: "symlink" | "depth" | "nonmd" | "dotfile" | "unreadable") => void;
|
|
13
25
|
}): ScannedEntryFile[];
|
|
26
|
+
/** Construction options for {@link FileMemoryEngineBackend}. */
|
|
14
27
|
export interface FileMemoryEngineBackendOptions {
|
|
28
|
+
/** ⚠️ 分家坑(service 报告 2026-07-09):不显式传时,控制平面按无参 `resolveMemoryEngineRoot()`
|
|
29
|
+
* 派生(env/默认 config 根)——若你的记忆目录带自定义根,控制面会与记忆目录分家。自建 backend 的
|
|
30
|
+
* consumer 应显式钉本字段(engine 同参)。 */
|
|
15
31
|
controlDir?: string;
|
|
32
|
+
/** 注入时钟(determinism,engine 同款);默认 `Date.now`。 */
|
|
16
33
|
now?: () => number;
|
|
34
|
+
/**
|
|
35
|
+
* Where mount-time partition-key incidents go. The migration probes promise "never silent", and a
|
|
36
|
+
* self-constructed backend has no other channel to keep that promise on — the runner-assembled path
|
|
37
|
+
* bridges its own sink into `RunnerDeps.onError`, and without this seat the identical incident was
|
|
38
|
+
* unobservable here. Absent ⇒ the probe stays silent (it is advisory; a mount never fails over it).
|
|
39
|
+
*/
|
|
17
40
|
onIncident?: MemoryPartitionIncidentSink;
|
|
18
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* ⚠️ DEPLOYMENT BOUNDARY (N0 定谳,channel 给service-AI.md [412],2026-07-09):this backend — and the
|
|
44
|
+
* file memory engine's materialize/harvest — operates on the WORKER-HOST fs plane (node:fs), matching
|
|
45
|
+
* CC's `~/.claude` host-plane memory (the only behavior anchor). It is NOT ExecutionEnv-aware: when a
|
|
46
|
+
* deployment routes the model's file tools to a REMOTE sandbox (e2b/k8s/ssh/adb/local-docker), the two
|
|
47
|
+
* planes do not intersect — the model cannot see the memory dir and harvest cannot see sandbox writes.
|
|
48
|
+
* Remote-hands deployments MUST gate the engine off for non-host lanes (reference: service
|
|
49
|
+
* `memoryEngineBackendFor` lane gate, fail-closed + `MEMORY_ENGINE_REMOTE_LANE=allow` override for
|
|
50
|
+
* lanes that genuinely share the worker fs, e.g. ssh+NFS). A future "remote-memory-materialize" design
|
|
51
|
+
* (ExecutionEnv-seam placement/harvest with sandbox-lifecycle contracts) is demand-triggered — do not
|
|
52
|
+
* bolt it on here.
|
|
53
|
+
*/
|
|
19
54
|
export declare class FileMemoryEngineBackend implements MemoryBackend {
|
|
55
|
+
/** The repo-level memory dir this backend is the authority for (engine reads it for zero-copy). */
|
|
20
56
|
readonly directoryRoot: string;
|
|
57
|
+
/** B3 — the engine control plane home (ledger/scopes/shadow/quarantine/journal). Model-invisible:
|
|
58
|
+
* NEVER under {@link directoryRoot}, always on the config-root side even for an in-repo memory dir.
|
|
59
|
+
* The engine picks this up (like `directoryRoot`) so both sides share ONE control plane. */
|
|
21
60
|
readonly controlPlaneRoot: string;
|
|
61
|
+
/** Injected clock (opus 1.258 LOW: match the engine's `this.now` determinism discipline). */
|
|
22
62
|
private readonly now;
|
|
63
|
+
/**
|
|
64
|
+
* The COMMITTED-rev ledger (`revs.json`, id → rev). Zero-copy makes it load-bearing for CAS: the
|
|
65
|
+
* entry files are the SAME files the session edits, so "read the current rev from the file" would
|
|
66
|
+
* make every legitimate update self-conflict (the file already holds the new content). The ledger
|
|
67
|
+
* records the rev as of the last COMMIT (applyPatches) — a CAS compares against IT, so an in-session
|
|
68
|
+
* edit passes (baseRev == ledger) while a concurrently-committed change conflicts (ledger advanced).
|
|
69
|
+
* Read-side (`listHeaders`/`getByIds`/`search`) SYNCS the ledger to disk THROUGH the inbound gate
|
|
70
|
+
* (B2 — out-of-session edits, e.g. a git pull into an in-repo memory dir, are secret/cap-scanned;
|
|
71
|
+
* rejects are quarantined + shadow-restored, never adopted); the applyPatches lookup does NOT sync
|
|
72
|
+
* (that would erase the very baseline CAS needs). B3: corrupt ⇒ {@link ControlPlaneCorruptError}
|
|
73
|
+
* (fail-closed), never a silent empty-ledger downgrade.
|
|
74
|
+
*/
|
|
23
75
|
private ledger;
|
|
76
|
+
/** B2 — inbound-gate findings accumulated by read-side syncs; the engine drains them into
|
|
77
|
+
* `HarvestReport.inboundFindings` at harvest. */
|
|
24
78
|
private inboundFindings;
|
|
79
|
+
/** Batch-planning scan memo (applyPatchesLocked only): planning reads pre-batch disk exclusively,
|
|
80
|
+
* so one `readScope(scope, false)` per scope serves every patch of the batch. Set for the planning
|
|
81
|
+
* loop, cleared before EXECUTE mutates the disk. Never caches `sync:true` reads. */
|
|
25
82
|
private batchScan;
|
|
26
83
|
constructor(dir: string, opts?: FileMemoryEngineBackendOptions);
|
|
84
|
+
/** B3 fail-closed preflight (engine harvest duck-types this): force-reload every load-bearing
|
|
85
|
+
* sidecar from disk; throws {@link ControlPlaneCorruptError} when one is corrupt. Also completes
|
|
86
|
+
* any interrupted transaction (H4 recovery). */
|
|
27
87
|
checkControlPlane(): void;
|
|
88
|
+
/** B2 — drain the accumulated inbound-gate findings (engine harvest reporting). */
|
|
28
89
|
drainInboundFindings(): HarvestRejection[];
|
|
90
|
+
/** B1 — read an entry's COMMITTED content copy from the control-plane shadow (undefined = none).
|
|
91
|
+
* The engine's missing-file recovery reads this (zero-copy: a deleted file is gone from the
|
|
92
|
+
* directory scan, so the shadow is the only committed copy left). */
|
|
29
93
|
readCommittedShadow(id: string): string | undefined;
|
|
30
94
|
private shadowPath;
|
|
31
95
|
private loadLedger;
|
|
96
|
+
/** B3: atomic + fail-closed — a ledger that cannot be persisted throws (the commit is incomplete
|
|
97
|
+
* and the caller must know), never a silent swallow. */
|
|
32
98
|
private saveLedger;
|
|
99
|
+
/**
|
|
100
|
+
* @param opts.unlocked RB-447 (2026-07-31, hardening review) — set by the two callers that run
|
|
101
|
+
* WITHOUT the txn mutex (the constructor and {@link checkControlPlane}). `applyPatches` serializes
|
|
102
|
+
* reload→plan→journal→execute→ledger behind a cross-process lock precisely because two writers over
|
|
103
|
+
* one root would otherwise interleave; these two ran the recovery and the staged sweep with no lock
|
|
104
|
+
* at all, and both are DESTRUCTIVE. A second process merely CONSTRUCTING a backend on the same
|
|
105
|
+
* memory dir — which happens at every task start — while process A sat between STAGE and EXECUTE
|
|
106
|
+
* removed A's `.stage-*` tmps, so A's `renameSync(op.staged, op.target)` threw ENOENT and the engine
|
|
107
|
+
* converted a healthy transaction into a `sidecar_corrupt` incident. Dropping A's journal has the
|
|
108
|
+
* worse shape: if A then crashed, its roll-forward record is gone.
|
|
109
|
+
*
|
|
110
|
+
* The gate is the txn mutex ITSELF, not a heuristic: an unlocked caller stands down only while
|
|
111
|
+
* someone else is holding a LIVE lock (present and not past its stale line), because that is
|
|
112
|
+
* precisely the window in which the journal and the `.stage-*` files belong to a transaction in
|
|
113
|
+
* flight. With no live holder — a genuine crash, or the ordinary cold start these two callers were
|
|
114
|
+
* written for — recovery and the sweep run exactly as before, so the documented "a crash is rolled
|
|
115
|
+
* forward/back on the next construction" behavior is unchanged. A live holder's own transaction
|
|
116
|
+
* finishes its journal itself; if that holder dies, the lock goes stale and the next construction or
|
|
117
|
+
* the next locked `applyPatches` recovers it. Nothing is deferred indefinitely.
|
|
118
|
+
*/
|
|
33
119
|
private recoverJournal;
|
|
120
|
+
/** RB-447 — is another writer mid-transaction right now? True while the txn lock dir exists and is
|
|
121
|
+
* not past its stale line (a stale lock is a crashed holder: its artifacts ARE ours to recover).
|
|
122
|
+
* Only consulted by the two UNLOCKED callers — inside `applyPatches` the lock is ours by
|
|
123
|
+
* construction. Cheap: one `stat` on the lock dir, no lock acquisition, never blocks. */
|
|
34
124
|
private txnInFlight;
|
|
125
|
+
/** Remove orphaned `.stage-*` files (a crash BEFORE the journal rename = clean rollback: nothing
|
|
126
|
+
* was committed, the staged tmps are litter). Dotfiles are invisible to entry scans regardless. */
|
|
35
127
|
private sweepStagedFiles;
|
|
128
|
+
/** The names of sibling scope subdirs to exclude when scanning the ROOT scope's own nested content. */
|
|
36
129
|
private excludedSubdirNames;
|
|
37
130
|
private scopeDir;
|
|
131
|
+
/** B2 inbound gate for content that did NOT come through applyPatches (git pull / hand edits /
|
|
132
|
+
* a hand-seeded file): secret + cap + structure. Returns the rejection, or undefined (clean). */
|
|
38
133
|
private inboundGate;
|
|
134
|
+
/** Read every COMMITTED entry of `scope` — a file must carry a valid `id:` to count. An id-less
|
|
135
|
+
* file is UNCOMMITTED session material: it becomes an entry only through the engine's harvest
|
|
136
|
+
* gates (which mint the id on commit). Reading it as an entry here would let gate-rejected files
|
|
137
|
+
* (secret / cap / quarantine) leak into the authoritative set through the zero-copy disk scan.
|
|
138
|
+
* `sync` (read-side callers) adopts a committed file's CURRENT disk rev into the committed-rev
|
|
139
|
+
* ledger ONLY through the inbound gate (B2: out-of-session edits, e.g. git-pull, are scanned;
|
|
140
|
+
* a reject is moved to control-plane quarantine and the committed shadow content is restored to
|
|
141
|
+
* disk). The applyPatches lookup passes false (the ledger must keep the baseline for CAS). */
|
|
39
142
|
private readScope;
|
|
40
143
|
listHeaders(scopes: readonly string[]): Promise<MemoryEntryHeader[]>;
|
|
41
144
|
private listHeadersWith;
|
|
42
145
|
getByIds(ids: readonly string[]): Promise<MemoryEntry[]>;
|
|
43
146
|
private getByIdsWith;
|
|
147
|
+
/**
|
|
148
|
+
* design/178 §3 (ruled 2026-08-08) — a SIDE-EFFECT-FREE projection of this backend for the session's
|
|
149
|
+
* RETRIEVAL face (`memory_search` / `memory_get`).
|
|
150
|
+
*
|
|
151
|
+
* The ordinary read path carries the inbound-adoption channel: a disk revision that disagrees with
|
|
152
|
+
* the ledger is judged an out-of-session change, gated, and — when it passes — adopted as the new
|
|
153
|
+
* COMMITTED baseline (ledger + shadow rewritten). That is a deliberate lifecycle synchronization
|
|
154
|
+
* point, and it belongs to the engine's own moments (materialize / harvest), not to every model
|
|
155
|
+
* lookup: a retrieval landing between them would otherwise COMMIT the session's own in-flight edits
|
|
156
|
+
* with no harvest gate and no pollution discipline in the way. Reads through this view resolve
|
|
157
|
+
* entries from disk without touching the ledger, the shadow, or the quarantine.
|
|
158
|
+
*
|
|
159
|
+
* Not running the inbound gate on this path is the point, not a concession: the view reports what
|
|
160
|
+
* is on the plane, and admitting anything stays the harvest's job.
|
|
161
|
+
*
|
|
162
|
+
* WRITES ARE REFUSED, loudly — a view handed to a write path is a defect, not a fallback.
|
|
163
|
+
*/
|
|
44
164
|
retrievalView(): MemoryBackend;
|
|
165
|
+
/** Lexical floor (design/138 §2.1): `1 - Jaccard` distance over name+description+body, ascending. */
|
|
45
166
|
search(query: string, scopes: readonly string[], opts?: {
|
|
46
167
|
limit?: number;
|
|
47
168
|
}): Promise<ScoredMemoryEntry[]>;
|
|
48
169
|
private searchWith;
|
|
170
|
+
/**
|
|
171
|
+
* H4 — the entry transaction, staged and journaled:
|
|
172
|
+
* 1. RELOAD the ledger from disk (验收 M2: a concurrently-committed transaction from another
|
|
173
|
+
* process becomes a CAS conflict here, never a blind write over it) + finish any interrupted
|
|
174
|
+
* transaction (redo journal).
|
|
175
|
+
* 2. PLAN every patch against current state (CAS/conflicts decided now; no disk writes yet).
|
|
176
|
+
* 3. STAGE: write each new file content to a same-directory `.stage-*` tmp (fsync'd).
|
|
177
|
+
* 4. JOURNAL: atomically write the full redo log (ops with content + the post-txn ledger snapshot).
|
|
178
|
+
* ← the commit point: a crash after this rolls FORWARD (recovery redoes the journal); a crash
|
|
179
|
+
* before it rolls BACK (staged tmps are swept, nothing was committed).
|
|
180
|
+
* 5. EXECUTE: atomic renames / deletes / shadow updates.
|
|
181
|
+
* 6. Persist the ledger (atomic, fail-closed) and drop the journal.
|
|
182
|
+
*/
|
|
49
183
|
applyPatches(patches: readonly NotePatch[]): Promise<PatchReport>;
|
|
50
184
|
private txnLockDir;
|
|
185
|
+
/**
|
|
186
|
+
* 对抗复审 L1/L2/L3 — the mkdir mutex, hardened (same shape as layout.ts' acquireSidecarLock, but
|
|
187
|
+
* fail-closed timings and an async wait):
|
|
188
|
+
* - L2: every acquisition writes an OWNER TOKEN (`<lock>/owner`, crypto-random — never a
|
|
189
|
+
* timestamp) immediately after mkdir. Release only removes the lock while the token is still
|
|
190
|
+
* ours; {@link assertTxnLockOwnership} re-verifies it at the commit point.
|
|
191
|
+
* - L1: the stale steal is SINGLE-WINNER — atomic rename of the stale lock dir to a unique
|
|
192
|
+
* tombstone (exactly one racer's rename succeeds; losers ENOENT back into the wait loop), then
|
|
193
|
+
* the winner removes the tombstone and re-acquires through a NORMAL mkdir. Never a bare
|
|
194
|
+
* rmSync+mkdir (two same-window stealers would force-rm each other's FRESH lock = double hold).
|
|
195
|
+
* - L2 (steal side): only a lock that is BOTH past its stale line AND carries an owner token is
|
|
196
|
+
* stealable (a tokenless dir is a mid-acquisition, not a crashed holder).
|
|
197
|
+
* - L3: the wait deadline is `max(waitMs, observed lock's stale line + grace)` — a holder that
|
|
198
|
+
* crashed with `remaining stale > waitMs` no longer strands every waiter in an all-throw window.
|
|
199
|
+
*/
|
|
51
200
|
private acquireTxnLock;
|
|
201
|
+
/** 对抗复审 L2 — 提交点归属复验: called immediately before the JOURNAL write (the persistent commit
|
|
202
|
+
* point). A holder that stalled past the stale deadline (slow disk / GC pause / long txn) and was
|
|
203
|
+
* stolen from must ABORT — its staged tmps carry no committed state and the stealer's journal
|
|
204
|
+
* recovery is now authoritative; committing anyway would overwrite the stealer's transaction. */
|
|
52
205
|
private assertTxnLockOwnership;
|
|
53
206
|
private applyPatchesLocked;
|
|
207
|
+
/** Plan one patch: decide CAS/conflicts against CURRENT state, emit journal ops (no entry-file
|
|
208
|
+
* writes here — staging/execution happen in {@link applyPatches}). */
|
|
54
209
|
private planOne;
|
|
55
210
|
private locateById;
|
|
56
211
|
getConsolidationCursor(scope: string): Promise<string | undefined>;
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import type { MemoryEntry, MemoryEntryFrontmatter } from "./types.js";
|
|
2
|
+
/** A parsed entry file: frontmatter (may be absent — CC allows bare files; harvest mints them) + body. */
|
|
2
3
|
export interface ParsedEntryFile {
|
|
4
|
+
/** The hidden immutable id (`id:` frontmatter line), when present. */
|
|
3
5
|
id?: string;
|
|
4
6
|
frontmatter: MemoryEntryFrontmatter;
|
|
5
7
|
body: string;
|
|
8
|
+
/** True when the file had a well-formed `---` frontmatter block at all. */
|
|
6
9
|
hadFrontmatter: boolean;
|
|
7
10
|
}
|
|
11
|
+
/** The id-shape contract, shared with the WRITE path (KNOWN-ISSUES 幽灵行加固): the File backend's
|
|
12
|
+
* applyPatches refuses any patch whose id this rejects — such an entry would be applied + ledgered,
|
|
13
|
+
* yet THIS parser drops the id on read (a fresh one gets minted), leaving the file on disk but the
|
|
14
|
+
* id permanently invisible. Read gate and write gate must stay one predicate. */
|
|
8
15
|
export declare function isValidEntryId(value: string): boolean;
|
|
16
|
+
/** Parse one memory file's text into frontmatter + body. Never throws — an unfenced file is all-body. */
|
|
9
17
|
export declare function parseEntryFile(text: string): ParsedEntryFile;
|
|
18
|
+
/** Serialize an entry to its canonical file projection (id FIRST — the hidden immutable field). */
|
|
10
19
|
export declare function serializeEntryFile(entry: Pick<MemoryEntry, "id" | "frontmatter" | "body">): string;
|
|
20
|
+
/**
|
|
21
|
+
* Content-hash rev (the CAS baseline). Computed over the SEMANTIC content — id, structured
|
|
22
|
+
* frontmatter fields, preserved extra lines, and body — via a canonical JSON tuple, NOT raw file
|
|
23
|
+
* bytes: engine (parsing disk files) and backend (storing entries) must agree on the rev regardless
|
|
24
|
+
* of incidental serialization formatting, or every materialize would see a phantom diff.
|
|
25
|
+
*/
|
|
11
26
|
export declare function computeEntryRev(entry: Pick<MemoryEntry, "id" | "frontmatter" | "body">): string;
|
|
27
|
+
/** Parse a file's text directly into a full MemoryEntry (id must already be present/decided). */
|
|
12
28
|
export declare function entryFromFile(text: string, id: string, slug: string, scope: string): MemoryEntry;
|
|
@@ -1,79 +1,296 @@
|
|
|
1
1
|
import type { MemoryAnnouncement } from "./types.js";
|
|
2
|
+
/** Cursor sidecar for the design/84 Seam B pair on the FileBackend (`{ [scope]: cursor }`). B3: control plane. */
|
|
2
3
|
export declare const CURSORS_FILE = "cursors.json";
|
|
4
|
+
/** The control-plane subdir name under a key dir — ONE spelling for every derivation and for the
|
|
5
|
+
* migration probes that move a control plane between key dirs. Exported for the sibling planes
|
|
6
|
+
* (dual-root's personal plane) so no second literal exists; not part of the package surface. */
|
|
3
7
|
export declare const CONTROL_PLANE_DIR = ".engine";
|
|
8
|
+
/** B3 fail-closed: a control-plane sidecar exists but cannot be trusted (unparseable / wrong shape).
|
|
9
|
+
* Read paths propagate it (materialize fails ⇒ the runner falls open to a memory-less session with
|
|
10
|
+
* onError); harvest converts it into a refused report (incident `sidecar_corrupt`). It is NEVER
|
|
11
|
+
* silently degraded to an empty ledger / unclaimed root — that would let a corrupted (or truncated)
|
|
12
|
+
* sidecar erase CAS baselines and re-open the root claim. */
|
|
4
13
|
export declare class ControlPlaneCorruptError extends Error {
|
|
5
14
|
constructor(message: string, opts?: {
|
|
6
15
|
cause?: unknown;
|
|
7
16
|
});
|
|
8
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* CC-style repo PARTITION key: the repo root path, canonicalized, then slugged. Collisions across
|
|
20
|
+
* repos require pathological sibling names and only merge their memory dirs (never corrupt them).
|
|
21
|
+
*
|
|
22
|
+
* RB-472 (design/161, four-repo receipts on record): the key CANONICALIZES first — without
|
|
23
|
+
* realpath, the same repo reached through a symlink alias and through its physical path minted two
|
|
24
|
+
* different partition keys = two memory dirs, silently splitting memory. `canonicalize` degrades to
|
|
25
|
+
* plain resolve semantics for a not-yet-existing path (nearest existing ancestor + remainder).
|
|
26
|
+
* Its input is the HOST-declared repo root; a model-reachable directory is keyed by
|
|
27
|
+
* {@link deriveDirKey} instead, and that asymmetry is deliberate (see there).
|
|
28
|
+
*
|
|
29
|
+
* Known limit, registered rather than fixed (ruled 2026-08-03): on a case-INSENSITIVE volume two
|
|
30
|
+
* spellings that differ only in case still mint two keys — realpath does not case-fold, and folding
|
|
31
|
+
* here would MERGE two genuinely distinct repos on a case-SENSITIVE volume. This round closes the
|
|
32
|
+
* symlink axis only; the case axis predates it and is unchanged.
|
|
33
|
+
*/
|
|
9
34
|
export declare function deriveRepoKey(repoRoot: string): string;
|
|
35
|
+
/** Sink for the mount-time partition-key incidents (`memory.partition_split` /
|
|
36
|
+
* `memory.partition_adopt_failed`). ONE shape for every host that can receive them — the runner's
|
|
37
|
+
* onError bridge, the engine, and the file backend. */
|
|
10
38
|
export type MemoryPartitionIncidentSink = (err: Error & {
|
|
11
39
|
code?: string;
|
|
12
40
|
}) => void;
|
|
41
|
+
/** RB-472 adoption leg: a pre-5.x deployment keyed its repo partition by the resolve-only path, so
|
|
42
|
+
* when the two mints differ its `memory/` + `.engine/` + siblings live under {@link deriveDirKey}'s
|
|
43
|
+
* dir. Move the whole partition onto the canonical key (see {@link migrateKeyedPartition}). */
|
|
13
44
|
export declare function adoptLegacyRepoDirs(configRoot: string, repoRoot: string, onIncident?: MemoryPartitionIncidentSink): void;
|
|
45
|
+
/**
|
|
46
|
+
* The REVERSE one-shot migration (ruled 2026-08-03): for the width of ONE release the control-plane
|
|
47
|
+
* key canonicalized like the repo key does, which put a directory-pinned mount's `.engine` under the
|
|
48
|
+
* canonical key. {@link deriveDirKey} is the control-plane mint again (that ruling's security half),
|
|
49
|
+
* so such a control plane sits at an address this build no longer reads — move it back, once.
|
|
50
|
+
*
|
|
51
|
+
* Scoped to the `.engine` subtree on purpose: the canonical-key dir may host unrelated siblings.
|
|
52
|
+
*
|
|
53
|
+
* Known limit, registered rather than fixed: when the SAME physical directory is ALSO mounted under
|
|
54
|
+
* its physical spelling, that mount's own resolve-keyed plane IS this migration's source dir, and the
|
|
55
|
+
* probe cannot tell the two apart (both are `<key>/.engine`). Concurrently mounting one directory
|
|
56
|
+
* under two spellings is outside what this probe can discriminate; the window it recovers from is one
|
|
57
|
+
* release wide.
|
|
58
|
+
*/
|
|
14
59
|
export declare function adoptCanonicalKeyedControlDir(configRoot: string, dir: string, onIncident?: MemoryPartitionIncidentSink): void;
|
|
60
|
+
/**
|
|
61
|
+
* `<configRoot>/<repoKey>/memory` — the repo-level stable mount (§2.7: taskRoot 外, worktree-shared).
|
|
62
|
+
*
|
|
63
|
+
* Worktree note (验收 L5, documented behavior): the KEY must be the BASE repo root. The runner passes
|
|
64
|
+
* `deps.rootPath ?? taskRootPath` — when `rootPath` is configured, every derived worktree of that repo
|
|
65
|
+
* shares ONE memory dir (a worktree moves the task cwd, never the memory mount); when `rootPath` is
|
|
66
|
+
* NOT configured the taskRootPath fallback keys memory to the task's own checkout path, so an
|
|
67
|
+
* isolated-worktree task without `rootPath` gets a worktree-private memory dir. That fallback is the
|
|
68
|
+
* accepted degraded behavior (no silent cross-repo mixing; configure `rootPath` for worktree sharing).
|
|
69
|
+
*/
|
|
15
70
|
export declare function deriveRepoMemoryDir(configRoot: string, repoRoot: string): string;
|
|
71
|
+
/** design/142 §1.5 — the IDENTITY-keyed mount (v2 scope contract + a resolved project marker):
|
|
72
|
+
* `<configRoot>/proj-<projectId>/memory`. Unlike the path-derived {@link deriveRepoMemoryDir},
|
|
73
|
+
* this key survives rename/move/re-clone/container remounts — identity lives in the repo's marker,
|
|
74
|
+
* the mount stops referencing the path entirely. */
|
|
16
75
|
export declare function deriveProjectMemoryDir(configRoot: string, projectId: string): string;
|
|
76
|
+
/** Control-plane sibling of {@link deriveProjectMemoryDir} (B3: always on the config-root side). */
|
|
17
77
|
export declare function deriveProjectControlDir(configRoot: string, projectId: string): string;
|
|
78
|
+
/** design/142 §1.5 — the path→projectId HINT CACHE (`<configRoot>/project-id-hints.json`). NOT an
|
|
79
|
+
* identity source (the marker is the single truth source); purely the recovery breadcrumb for a
|
|
80
|
+
* deleted/lost marker: materialize can tell "this path used to carry marker X" and prompt a
|
|
81
|
+
* restore instead of silently minting a fresh identity. Best-effort on both faces (a corrupt hint
|
|
82
|
+
* file degrades to "no hints", never blocks a session). */
|
|
18
83
|
export declare const PROJECT_ID_HINTS_FILE = "project-id-hints.json";
|
|
19
84
|
export declare function recordProjectIdHint(configRoot: string, repoRoot: string, projectId: string): void;
|
|
20
85
|
export declare function lookupProjectIdHint(configRoot: string, repoRoot: string): string | undefined;
|
|
86
|
+
/** B3 — `<configRoot>/<dirKey>/.engine`: the engine CONTROL PLANE for a DIRECTORY-pinned mount (the
|
|
87
|
+
* memory dir itself is the key). ALWAYS on the config-root side — when the memory dir is configured
|
|
88
|
+
* in-repo, the control plane still lives here (model-invisible, not injected, not in-repo) — and
|
|
89
|
+
* keyed RESOLVE-ONLY, because the key input is a directory the model's file channel may be able to
|
|
90
|
+
* replace: see {@link deriveDirKey} for why canonicalizing it is a redirect handle.
|
|
91
|
+
* The repo-partition face has its own sibling mint, {@link deriveRepoControlPlaneDir}. */
|
|
21
92
|
export declare function deriveControlPlaneDir(configRoot: string, key: string): string;
|
|
93
|
+
/** B3 — the REPO-partition control plane: the sibling of {@link deriveRepoMemoryDir} under the SAME
|
|
94
|
+
* canonical repo key. Paired on purpose: {@link adoptLegacyRepoDirs} adopts the repo partition as ONE
|
|
95
|
+
* directory (`memory/` and `.engine/` inside it), so keying its control plane differently from its
|
|
96
|
+
* memory dir would strand one of the two on every alias-reached mount. Safe to canonicalize here —
|
|
97
|
+
* the key input is the host-declared repo root, not a directory the model can replace. */
|
|
22
98
|
export declare function deriveRepoControlPlaneDir(configRoot: string, repoRoot: string): string;
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the memory-engine config root: explicit (`RunnerDeps.memoryEngineDir`) → `AGENT_DATA_DIR`
|
|
101
|
+
* env → `~/.ai-agent` (the same default family as the file stores' `resolveDataRoot`; re-stated here
|
|
102
|
+
* so core/memory-engine does not import from src/stores).
|
|
103
|
+
*/
|
|
23
104
|
export declare function resolveMemoryEngineRoot(explicit?: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* Filesystem-safe scope dir label. 评审 H5 (唯一映射): the cleaned slug alone is LOSSY
|
|
107
|
+
* (`org:7` and `org/7` both fold to `org-7` — two tenants, one directory = cross-scope bleed), so any
|
|
108
|
+
* scope whose cleaned form differs from the original gets a short content-hash suffix
|
|
109
|
+
* (`org-7-a1b2c3`), making the mapping injective for all practical inputs; an already-safe scope maps
|
|
110
|
+
* to itself (stable, human-readable). {@link registerScope} additionally FAIL-CLOSES on a registry
|
|
111
|
+
* collision (two distinct scopes → one dir name), so even a hash collision cannot silently merge scopes.
|
|
112
|
+
*/
|
|
24
113
|
export declare function scopeDirName(scope: string): string;
|
|
114
|
+
/** Which scope owns the memory dir ROOT (undefined = unclaimed). */
|
|
25
115
|
export declare function rootScopeOf(controlDir: string): string | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Claim the memory dir root for `scope` iff unclaimed (idempotent, first-writer-wins, durable).
|
|
118
|
+
* Returns the (possibly pre-existing) root owner. Lock-serialized (独立轨 F5): two first-users of a
|
|
119
|
+
* shared mount race the claim under the sidecar lock — exactly one wins, the other SEES the winner.
|
|
120
|
+
*/
|
|
26
121
|
export declare function claimRootScope(controlDir: string, scope: string): string;
|
|
122
|
+
/** Register `scope` in the durable registry (idempotent) and return its home dir under `memoryDir`.
|
|
123
|
+
* H5 fail-closed: a dir-name collision with a DIFFERENT registered scope throws — two scopes must
|
|
124
|
+
* never share one directory (silent cross-scope merge). Lock-serialized (独立轨 F5). */
|
|
27
125
|
export declare function registerScope(memoryDir: string, controlDir: string, scope: string): string;
|
|
126
|
+
/** All registered scopes → home dir name (`""` = the root). */
|
|
28
127
|
export declare function registeredScopes(controlDir: string): Record<string, string>;
|
|
128
|
+
/** The stable home directory of `scope`'s entry files: the root when it owns the root, else the
|
|
129
|
+
* REGISTERED subdir (falls back to the {@link scopeDirName} formula for a never-registered scope). */
|
|
29
130
|
export declare function scopeDirFor(memoryDir: string, controlDir: string, scope: string): string;
|
|
131
|
+
/** Canonicalize a path for containment checks (resolves symlinks on the EXISTING prefix). */
|
|
30
132
|
export declare function canonicalize(p: string): string;
|
|
133
|
+
/** True iff `child` (canonicalized) is `root` or inside it. */
|
|
31
134
|
export declare function isContainedIn(root: string, child: string): boolean;
|
|
135
|
+
/** mkdir -p convenience used by the engine/backend (0o700 like the file stores). */
|
|
32
136
|
export declare function ensureDirExists(dir: string): void;
|
|
137
|
+
/** B2/L4 — the control-plane subdir holding gate-rejected originals. ONE name for all three writers
|
|
138
|
+
* (engine harvest, engine derived-index rung, backend inbound gate): they write the same directory,
|
|
139
|
+
* so its name and its permissions must not depend on which of them ran first. */
|
|
33
140
|
export declare const QUARANTINE_DIR = "quarantine";
|
|
141
|
+
/** REF-A9 — what {@link quarantineAndTombstone} reports back; both hosts escalate `detail` into
|
|
142
|
+
* their report channel (harvest `quarantineFailures` / inbound `quarantine_failed` findings). */
|
|
34
143
|
export interface QuarantineOutcome {
|
|
144
|
+
/** The captured copy under the quarantine dir. Absent ⇔ the capture itself failed. */
|
|
35
145
|
dest?: string;
|
|
146
|
+
/** True ⇔ the model-visible file no longer holds the suspect content (deleted, or tombstoned). */
|
|
36
147
|
removed: boolean;
|
|
148
|
+
/** Set on ANY deviation from the clean capture+delete path — never a silent best-effort downgrade. */
|
|
37
149
|
detail?: string;
|
|
38
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* B2/L4 + C-F9 (S2-0) — capture a gate-rejected file into control-plane quarantine FROM THE
|
|
153
|
+
* ALREADY-READ `content` (never re-reading `path`: the disk bytes may have been swapped since the
|
|
154
|
+
* gate scanned them — TOCTOU), then remove the suspect from the model-visible plane: delete, and if
|
|
155
|
+
* that fails overwrite in place with {@link DELETED_TOMBSTONE}.
|
|
156
|
+
*
|
|
157
|
+
* REF-A9: ONE implementation for the engine's harvest rung and the backend's inbound-gate rung.
|
|
158
|
+
* They were line-for-line twins carrying the same historical fix ("mirrors the engine-side fix",
|
|
159
|
+
* opus review 1.257.2) and had already drifted twice — the backend minted its quarantine filename
|
|
160
|
+
* off `Date.now()` instead of its own injected clock, and the two created the SHARED quarantine dir
|
|
161
|
+
* with different permissions (whoever ran first decided how readable the captured secrets were).
|
|
162
|
+
*
|
|
163
|
+
* NOT to be reused for the derived-index rung (`MemoryEngine.gateDerivedIndex`): that one CLEARS the
|
|
164
|
+
* file (the index is derived and is rebuilt from entry frontmatter), which is the opposite of a
|
|
165
|
+
* tombstone — folding them would launder that distinction away.
|
|
166
|
+
*/
|
|
167
|
+
/**
|
|
168
|
+
* Write `content` to `path`, refusing to follow a symlink at the FINAL component (`O_NOFOLLOW`).
|
|
169
|
+
*
|
|
170
|
+
* Every containment write in this domain is a check-then-use: the path was judged in-root when the
|
|
171
|
+
* scan read it, and the write happens later. A symlink dropped in that window turns a containment
|
|
172
|
+
* response — the thing whose job is removing suspect bytes — into a write through the link, i.e.
|
|
173
|
+
* this process putting its own committed memory content wherever the link points. Re-checking
|
|
174
|
+
* before writing cannot close a race; the kernel resolving and rejecting in one step can.
|
|
175
|
+
*
|
|
176
|
+
* SCOPE, stated exactly because the difference matters: `O_NOFOLLOW` constrains the LAST component
|
|
177
|
+
* only. An ANCESTOR directory swapped for a symlink in the same window still redirects the write,
|
|
178
|
+
* and closing that needs per-component `openat`-style traversal from a pinned root descriptor —
|
|
179
|
+
* which Node's `fs` does not expose (there is no relative-open API), so it is not reachable here
|
|
180
|
+
* without native bindings. What this buys is the leaf variant, which is the cheap one; the ancestor
|
|
181
|
+
* variant stays open and is recorded with the rest of the family rather than papered over.
|
|
182
|
+
*
|
|
183
|
+
* Throws like `writeFileSync`, including ELOOP when the final component IS a symlink. The write
|
|
184
|
+
* itself goes through `writeFileSync` on the open descriptor, which writes every byte or throws —
|
|
185
|
+
* a bare `writeSync` may short-write, and reporting a truncated restore as contained (after
|
|
186
|
+
* `O_TRUNC` already destroyed the destination) is the failure this guard exists to prevent.
|
|
187
|
+
*/
|
|
39
188
|
export declare function writeFileNoFollow(path: string, content: string): void;
|
|
40
189
|
export declare function quarantineAndTombstone(path: string, content: string, quarantineDir: string, now: () => number): QuarantineOutcome;
|
|
190
|
+
/** S2-B — the queued memory-change announcements (control plane; drained at session-first inject). */
|
|
41
191
|
export declare const ANNOUNCEMENTS_FILE = "announcements.json";
|
|
192
|
+
/** S2-B — bounded queue: overflow FOLDS (oldest dropped, counted) instead of growing unbounded. */
|
|
42
193
|
export declare const MEMORY_ANNOUNCEMENTS_MAX = 20;
|
|
194
|
+
/** S2-C — per-file write-rejection counts (the beforeWrite silent-reject fuse; O-F9/C-F7: scoped
|
|
195
|
+
* state in `.engine/`, so the count survives suspend/resume and backend re-construction). */
|
|
43
196
|
export declare const SCAN_FUSE_FILE = "scan-fuse.json";
|
|
197
|
+
/** Rejections at which the write-time feedback mutes (S2-C: 拒 ≥3 次熔断为静默拒). */
|
|
44
198
|
export declare const SCAN_FUSE_THRESHOLD = 3;
|
|
199
|
+
/** Options of {@link acquireSidecarLock}. `waitCapMs` exists FOR the wait-bound pins (same hook shape
|
|
200
|
+
* as the file backend's txn-lock timings) — production callers leave it at {@link LOCK_WAIT_CAP_MS}. */
|
|
45
201
|
export interface SidecarLockOptions {
|
|
46
202
|
onDeadline?: "proceed" | "throw";
|
|
47
203
|
waitCapMs?: number;
|
|
48
204
|
}
|
|
205
|
+
/** Cross-process mutex via mkdir (atomic on POSIX). A lock older than {@link LOCK_STALE_MS} is
|
|
206
|
+
* presumed crashed and stolen; after the wait cap we proceed anyway (fail-open — these sidecars are
|
|
207
|
+
* not CAS-load-bearing, and wedging a session start on a lock would be worse).
|
|
208
|
+
*
|
|
209
|
+
* opus 1.258 review (MED, steal TOCTOU): a bare rmSync+mkdir steal lets TWO stealers both win (B's
|
|
210
|
+
* force-rm deletes A's FRESH lock). The steal is made single-winner by an atomic `renameSync` of the
|
|
211
|
+
* stale lock dir to a unique tombstone first — rename is atomic, exactly one racer succeeds; the
|
|
212
|
+
* loser's rename throws ENOENT and it loops back to a normal mkdir wait. The deadline branch keeps
|
|
213
|
+
* the documented proceed-anyway semantics but steals through the same single-winner gate.
|
|
214
|
+
*
|
|
215
|
+
* Wait-bound (ruled 2026-08-04): EVERY round runs the same three steps — attempt, diagnose, then
|
|
216
|
+
* the deadline check and the pause. The two "retry immediately" shortcuts this loop used to take
|
|
217
|
+
* (holder released between mkdir and stat; steal attempted) skipped both of the last two, so a
|
|
218
|
+
* condition that keeps answering the same way — an unwritable control dir (read-only mount, full
|
|
219
|
+
* volume, revoked permission) makes mkdir fail forever while the lock dir it would stat never
|
|
220
|
+
* exists — spun this synchronous loop at full speed and never reached its own cap. */
|
|
49
221
|
export declare function acquireSidecarLock(lockDir: string, opts?: SidecarLockOptions): string;
|
|
50
222
|
interface AnnouncementsRecord {
|
|
223
|
+
/** How many announcements were dropped by the bounded-queue fold (disclosed at render). */
|
|
51
224
|
folded: number;
|
|
52
225
|
queue: MemoryAnnouncement[];
|
|
53
226
|
}
|
|
227
|
+
/** S2-B — enqueue one announcement (journaled + locked; bounded with overflow folding). */
|
|
54
228
|
export declare function enqueueMemoryAnnouncement(controlDir: string, ann: MemoryAnnouncement): void;
|
|
229
|
+
/** S2-B — drain the queue (session-first inject, 时机①). Returns what was queued and resets it. */
|
|
55
230
|
export declare function drainMemoryAnnouncements(controlDir: string): AnnouncementsRecord;
|
|
231
|
+
/** Test/observability peek (journal-aware, non-destructive). */
|
|
56
232
|
export declare function peekMemoryAnnouncements(controlDir: string): AnnouncementsRecord;
|
|
233
|
+
/** S2-C — record one write-time scan rejection for `key` (canonical file path); returns the count
|
|
234
|
+
* BEFORE this bump (callers mute the feedback when it is already ≥ {@link SCAN_FUSE_THRESHOLD}). */
|
|
57
235
|
export declare function bumpScanFuse(controlDir: string, key: string): number;
|
|
58
236
|
export declare function readIndexRevs(controlDir: string): Record<string, string>;
|
|
59
237
|
export declare function writeIndexRevs(controlDir: string, revs: Record<string, string>): void;
|
|
60
238
|
export declare function scanFuseCount(controlDir: string, key: string): number;
|
|
239
|
+
/** Directory (under the control plane) holding one marker file per polluted session. */
|
|
61
240
|
export declare const SESSION_POLLUTION_DIR = "session-pollution";
|
|
62
241
|
export interface SessionPollutionRecord {
|
|
63
242
|
at: number;
|
|
64
243
|
reason: string;
|
|
65
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Mark `sessionId` polluted (idempotent; the FIRST record wins — `wx` create refuses overwrite).
|
|
247
|
+
* Returns true when a durable marker exists after the call (created now or already present);
|
|
248
|
+
* false ⇔ the marker could not be persisted — the caller must keep its own in-memory mark and
|
|
249
|
+
* disclose (the session stays polluted for this process either way).
|
|
250
|
+
*/
|
|
66
251
|
export declare function markSessionPolluted(controlDir: string, sessionId: string, reason: string, now: () => number): boolean;
|
|
252
|
+
/** Read the durable pollution record for `sessionId` (undefined = no marker).
|
|
253
|
+
* A marker that EXISTS still reads as polluted whether or not its bytes can be read or parsed
|
|
254
|
+
* (fail-closed: neither corruption nor an unreadable node may launder the state) — a synthesized
|
|
255
|
+
* record stands in. Only ABSENCE is clean, so a control plane that cannot be stat'ed at all reads
|
|
256
|
+
* clean for every session rather than disabling memory deployment-wide. */
|
|
67
257
|
export declare function readSessionPollution(controlDir: string, sessionId: string): SessionPollutionRecord | undefined;
|
|
258
|
+
/** The retrieved-account sidecar (control plane): entry id → { count, lastAt }. */
|
|
68
259
|
export declare const USAGE_RETRIEVED_FILE = "usage-retrieved.json";
|
|
260
|
+
/** Bound on tracked ids — over the cap, the entries with the OLDEST `lastAt` are evicted first
|
|
261
|
+
* (a popularity account tolerates forgetting cold ids; unbounded growth does not tolerate itself). */
|
|
69
262
|
export declare const USAGE_RETRIEVED_MAX_IDS = 4096;
|
|
263
|
+
/** One retrieved-account row. */
|
|
70
264
|
export interface RetrievedAccountRow {
|
|
71
265
|
count: number;
|
|
72
266
|
lastAt: number;
|
|
73
267
|
}
|
|
268
|
+
/** Record one retrieval (a search/get hit actually RETURNED to the model) for each id. Journaled +
|
|
269
|
+
* locked like every sidecar; throws propagate to the caller, which treats them fail-open. */
|
|
74
270
|
export declare function recordRetrievedAccount(controlDir: string, ids: readonly string[], now: () => number): void;
|
|
271
|
+
/** Journal-aware read of the retrieved account (observability/tests; nothing in the engine's
|
|
272
|
+
* retention/ranking paths consumes it — see the section note). */
|
|
75
273
|
export declare function readRetrievedAccount(controlDir: string): Record<string, RetrievedAccountRow>;
|
|
274
|
+
/** S2-C — clear the fuse for files harvest just ACCEPTED (a committed write resets the strike count). */
|
|
76
275
|
export declare function clearScanFuse(controlDir: string, keys: Iterable<string>): void;
|
|
276
|
+
/**
|
|
277
|
+
* REF-C6 — write EVERY byte of `data` to `fd`, looping until the OS has taken all of them.
|
|
278
|
+
*
|
|
279
|
+
* A bare `writeSync(fd, data)` issues ONE write(2) and may legally SHORT-WRITE (POSIX): it returns
|
|
280
|
+
* how many bytes it took and silently drops the rest. `src/stores/file/fs-atomic.ts` already
|
|
281
|
+
* documents and fixes this defect class twice (`writeThenLink`'s "silently SHORT-WRITES" note and
|
|
282
|
+
* `AppendLog.append`'s RB-66 loop) — it was never propagated to the memory-engine control plane,
|
|
283
|
+
* where a short write would fsync+rename a TRUNCATED sidecar/entry into place while every
|
|
284
|
+
* fail-closed check upstream still reported success. Zero progress throws instead of spinning.
|
|
285
|
+
*/
|
|
77
286
|
export declare function writeAllSync(fd: number, data: string): void;
|
|
287
|
+
/** B3/H4 — TORN-WRITE-safe atomic replace (R5, ruling 2026-08-02: wording narrowed, no dirsync
|
|
288
|
+
* added): tmp file in the SAME directory (rename atomicity needs one fs), content fsync'd, then
|
|
289
|
+
* renamed over the target. A crash can leave the OLD content or the NEW content, never a torn
|
|
290
|
+
* file. What this does NOT promise: the rename itself surviving a power loss — the directory entry
|
|
291
|
+
* is not fsync'd (stores/file/fs-atomic.ts has the dir-fsync variant for stores that need it), so
|
|
292
|
+
* a crash in that window can resurface the old content. Sidecars tolerate that (they are rebuilt
|
|
293
|
+
* or re-derived); do not route data that can't through here. Throws on failure (fail-closed: a
|
|
294
|
+
* sidecar that cannot be written must not be silently skipped). */
|
|
78
295
|
export declare function atomicWriteFileSync(path: string, data: string): void;
|
|
79
296
|
export {};
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import type { MemoryBackend } from "./types.js";
|
|
2
2
|
export interface MemoryBackendContractHooks {
|
|
3
3
|
make: () => Promise<MemoryBackend> | MemoryBackend;
|
|
4
|
+
/** A second backend instance over the SAME storage as the given one (cross-instance CAS cases). */
|
|
4
5
|
makeSibling?: (of: MemoryBackend) => Promise<MemoryBackend> | MemoryBackend;
|
|
6
|
+
/**
|
|
7
|
+
* design/142 §6.3: the ONLY way to run the suite without `makeSibling`. State WHY this store has no
|
|
8
|
+
* shared-storage second instance (e.g. "process-exclusive single-writer file store; cross-process CAS
|
|
9
|
+
* covered by <suite>"). Runs as a named, visible case (a LOUD skip) — never a silent 16/17 pass.
|
|
10
|
+
* Authoritative-backend certification (§6.1 (a)) must provide `makeSibling` instead.
|
|
11
|
+
*/
|
|
5
12
|
skipCrossInstanceCas?: string;
|
|
13
|
+
/** Wrap each case (vitest `it`, sequential SQL chaining, …). Default: run sequentially. */
|
|
6
14
|
runAssertion?: (name: string, fn: () => Promise<void>) => void | Promise<void>;
|
|
7
15
|
}
|
|
16
|
+
/** Run the full conformance suite. Throws (assert) on the first violated contract clause. */
|
|
8
17
|
export declare function memoryBackendContract(hooks: MemoryBackendContractHooks): Promise<void>;
|
|
18
|
+
/** Cross-backend value equivalence: same fixture ⇒ IDENTICAL (id, slug, score) search projections on
|
|
19
|
+
* two backends (File vs Pg lexical floor must not drift — assertMemoryValueEquivalence precedent).
|
|
20
|
+
* mtimeMs/sizeBytes are backend-native (fs stat vs column) and deliberately not compared. */
|
|
9
21
|
export declare function assertMemoryBackendSearchEquivalence(a: MemoryBackend, b: MemoryBackend): Promise<void>;
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import type { MemoryBackend, PatchReport } from "./types.js";
|
|
2
2
|
export interface MigrateScopeReport {
|
|
3
|
+
/** Entries moved to the new scope (CAS-clean). */
|
|
3
4
|
moved: number;
|
|
5
|
+
/** Entries that hit a CAS conflict (concurrently edited during migration) — re-run to retry. */
|
|
4
6
|
conflicts: PatchReport["conflicts"];
|
|
7
|
+
/** Whether the consolidation cursor was carried over (only when the old scope had one). */
|
|
5
8
|
cursorMoved: boolean;
|
|
6
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Move EVERY entry from `fromScope` to `toScope` on one backend (id-preserving, rev-recomputed via
|
|
12
|
+
* the cross-scope move contract), then carry the consolidation cursor. NOT transactional across
|
|
13
|
+
* entries (the contract's per-patch independence): a conflict leaves that entry in place — the
|
|
14
|
+
* report says so and a re-run picks it up. Refuses same-key no-ops.
|
|
15
|
+
*/
|
|
7
16
|
export declare function migrateScope(backend: MemoryBackend, fromScope: string, toScope: string): Promise<MigrateScopeReport>;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import type { ScanFinding } from "./types.js";
|
|
2
|
+
/** S2-B 判决四 — the per-path-segment whitelist for memory-domain file names. */
|
|
2
3
|
export declare const MEMORY_FILENAME_SEGMENT_RE: RegExp;
|
|
4
|
+
/**
|
|
5
|
+
* Scan a memory-domain file NAME (a path relative to the writable root, `/`-separated). Returns the
|
|
6
|
+
* finding or undefined (clean). Rejected names quarantine at harvest and hard-reject at write time —
|
|
7
|
+
* a name like `foo</system-reminder>bar.md` is an injection payload wherever the path is echoed.
|
|
8
|
+
*/
|
|
3
9
|
export declare function scanMemoryFileName(relPath: string): ScanFinding | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Scan memory-domain file CONTENT. Returns every finding (empty = clean). `maxBytes` (when given)
|
|
12
|
+
* adds the per-file cap rung — harvest passes it via its own pre-check today, the write-time hook
|
|
13
|
+
* passes the engine's `perFileBytes` so the model gets the feedback BEFORE the bytes ever land.
|
|
14
|
+
*/
|
|
4
15
|
export declare function scanMemoryWrite(text: string, opts?: {
|
|
5
16
|
maxBytes?: number;
|
|
6
17
|
}): ScanFinding[];
|
|
18
|
+
/** Model-facing remediation line per scan code (S2-C: 拒写+结构化错误含整改指引). */
|
|
7
19
|
export declare function scanRemediation(code: ScanFinding["code"]): string;
|