@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,35 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/99 §E6 — per-session permission rules: a durable, session-scoped policy narrowing folded into the
|
|
3
|
+
* task-time {@link import("./tool-policy.js").ToolPolicy} (see {@link import("./runner/session-rule-policy.js").createSessionRulePolicy}).
|
|
4
|
+
*
|
|
5
|
+
* Why core-owned (not a parallel service store): the rules must be ENFORCED by the same policy fold that runs
|
|
6
|
+
* every tool call, so storage and enforcement can't drift (the [210] capability-token invariant). core defines
|
|
7
|
+
* the seam + an InMemory default; a deployment supplies a DURABLE cross-replica backend (its CAS-rev semantics
|
|
8
|
+
* must match this InMemory one byte-for-byte — a cross-backend equivalence test is the contract).
|
|
9
|
+
*
|
|
10
|
+
* Safety model:
|
|
11
|
+
* - **tighten-only by default**: a NORMAL write may only ADD denies / NARROW allowlists / NARROW allowDirs. A
|
|
12
|
+
* write that LOOSENS (removes a deny, widens an allowlist/allowDirs) is REFUSED unless it is an OPERATOR
|
|
13
|
+
* write. core has NO role system — `operator` is a boolean the caller FREEZES from a verified principal
|
|
14
|
+
* (mirrors `steer.trusted`); core never adjudicates operator-ness, it only honors the flag.
|
|
15
|
+
* - **CAS rev** (optimistic concurrency): a `putRules` with a stale `expectedRev` is rejected, so two replicas
|
|
16
|
+
* can't lose an update (mirrors the checkpoint store's `resolve` OCC).
|
|
17
|
+
* - **structured fields only**: core interprets `toolAllow/Deny`, `allowDirs`, `commandAllow/Deny` (argv[0]
|
|
18
|
+
* names). A richer rule DSL is the service's/profile's job — core grows no rule interpreter.
|
|
19
|
+
* - **NOT A SANDBOX**: command rules inherit the coarse argv[0] matching (wrappers like `sh -c`/`sudo` bypass);
|
|
20
|
+
* the real isolation boundary is the executionEnv, never these rules.
|
|
21
|
+
*/
|
|
22
|
+
/** The structured per-session rules. All optional; an absent field imposes no constraint of that kind. */
|
|
1
23
|
export interface SessionPermissionRules {
|
|
24
|
+
/** If set, ONLY these tool names are allowed (others denied). A narrowing allowlist. */
|
|
2
25
|
toolAllow?: string[];
|
|
26
|
+
/** Tool names always denied (deny wins). */
|
|
3
27
|
toolDeny?: string[];
|
|
28
|
+
/** If set, WRITE tools (`write_file`/`edit_file`) may only target paths within these dirs; a write-capable
|
|
29
|
+
* tool that can't be path-confined (e.g. `bash`) is denied while this is set. Stored RAW; resolved at run time. */
|
|
4
30
|
allowDirs?: string[];
|
|
31
|
+
/** If set, ONLY these `bash` command names (argv[0]) are allowed. */
|
|
5
32
|
commandAllow?: string[];
|
|
33
|
+
/** `bash` command names (argv[0]) always denied. */
|
|
6
34
|
commandDeny?: string[];
|
|
7
35
|
}
|
|
36
|
+
/** {@link SessionPermissionRules} plus the monotonic `rev` the store stamps (the OCC key). */
|
|
8
37
|
export interface StoredSessionRules extends SessionPermissionRules {
|
|
9
38
|
rev: number;
|
|
10
39
|
}
|
|
40
|
+
/** Options for {@link SessionPolicyStore.putRules}. */
|
|
11
41
|
export interface PutRulesOptions {
|
|
42
|
+
/** CAS: reject the write if the stored `rev` is not this (optimistic concurrency). Omit to skip the check. */
|
|
12
43
|
expectedRev?: number;
|
|
44
|
+
/** When true, this write MAY loosen the rules (remove a deny / widen an allowlist or allowDirs). A NORMAL
|
|
45
|
+
* (non-operator) write is TIGHTEN-ONLY. core does NOT verify operator-ness — the caller (service) FREEZES
|
|
46
|
+
* this from a verified, role-checked principal (mirrors `steer.trusted`). */
|
|
13
47
|
operator?: boolean;
|
|
14
48
|
}
|
|
49
|
+
/** Typed error from {@link SessionPolicyStore.putRules}. `conflict` = stale `expectedRev`; `loosen_forbidden`
|
|
50
|
+
* = a non-operator write tried to relax the rules. */
|
|
15
51
|
export declare class SessionPolicyError extends Error {
|
|
16
52
|
readonly code: "conflict" | "loosen_forbidden";
|
|
17
53
|
constructor(code: "conflict" | "loosen_forbidden", message: string);
|
|
18
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The seam a deployment implements for DURABLE, cross-replica per-session rules. Unset on `RunnerDeps` ⇒ the
|
|
57
|
+
* feature is OFF (no rules read, zero behavior change). Keyed on `(sessionId, principal-owner)`: core passes
|
|
58
|
+
* the running task's `principal` as the owner key; ownership/operator AUTHORIZATION is the implementation's job
|
|
59
|
+
* (core has no identity system). `getRules` returns the rules to ENFORCE for a task; `putRules` writes them.
|
|
60
|
+
*/
|
|
61
|
+
/** A (principal, rules) pair for one session — the unit {@link SessionPolicyStore.listBySession} returns. */
|
|
19
62
|
export interface SessionRulesRecord {
|
|
63
|
+
/** The principal these rules apply to (undefined = the session-wide default). */
|
|
20
64
|
principal: string | undefined;
|
|
21
65
|
rules: StoredSessionRules;
|
|
22
66
|
}
|
|
23
67
|
export interface SessionPolicyStore {
|
|
24
68
|
getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
|
|
25
69
|
putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
|
|
70
|
+
/**
|
|
71
|
+
* 2c session-sync ([271]): every (principal, rules) record for `sessionId` across ALL principals — `getRules`
|
|
72
|
+
* is per-(sessionId, principal), but a cross-backend session EXPORT must bundle the WHOLE session's policy.
|
|
73
|
+
* The importer replays each via `putRules(sessionId, record.principal, record.rules)`. Optional — a backend
|
|
74
|
+
* that supports cross-backend policy export implements it (local backend uses the in-memory/file impl).
|
|
75
|
+
*/
|
|
26
76
|
listBySession?(sessionId: string): Promise<SessionRulesRecord[]>;
|
|
77
|
+
/**
|
|
78
|
+
* 2c session-sync, the ERASE half (server C1b, 2026-07-26): remove EVERY (principal, rules) record for
|
|
79
|
+
* `sessionId`. An importer that OVERWRITES a session must be able to erase the abandoned branch's policy
|
|
80
|
+
* rows — without this seam they survive the overwrite and keep enforcing the old branch's rules against
|
|
81
|
+
* the new content. Optional like {@link listBySession} (a sync-capable backend implements both); the two
|
|
82
|
+
* bundled impls do. Idempotent: deleting a session with no records is a no-op.
|
|
83
|
+
*/
|
|
27
84
|
deleteBySession?(sessionId: string): Promise<void>;
|
|
28
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* LEXICAL path containment for the tighten gate. **Must agree with the runtime's view of what a dir IS** (the
|
|
88
|
+
* runtime resolves `..`/`.`/`//` via `canonicalizeTarget`): both args are `posix.normalize`d FIRST so a
|
|
89
|
+
* non-operator can't smuggle a write-confinement OUTSIDE the prior dir by writing `<prior>/../etc` — which is
|
|
90
|
+
* lexically "within `<prior>`" by a naive `startsWith` but canonicalizes to `/etc` at run time (the tighten
|
|
91
|
+
* gate must never UNDER-report a loosening).
|
|
92
|
+
*
|
|
93
|
+
* The `..`-resolving `posix.normalize` pass is THIS function's own job (the tighten gate's `..`-smuggling
|
|
94
|
+
* defense above) and is NOT shared with the runtime's `isWithin` (session-rule-policy.ts), which compares
|
|
95
|
+
* already-canonicalized keys and never resolves `..` itself — the two functions agree on every OTHER input
|
|
96
|
+
* (same win-family fold, same prefix semantics) but diverge on inputs that still carry an unresolved `..`,
|
|
97
|
+
* which is exactly the tighten-gate-only case this function exists to catch.
|
|
98
|
+
*
|
|
99
|
+
* RESIDUAL (documented, bounded): the gate is LEXICAL and cannot follow symlinks at write time, while the
|
|
100
|
+
* runtime IS symlink-aware. A stored allowDir whose LAST component is a pre-existing symlink to outside the
|
|
101
|
+
* prior set would be seen as "within" by this gate yet resolve elsewhere at run time. This is bounded: while an
|
|
102
|
+
* `allowDirs` confinement is active the runtime DENIES `bash` (non-path-confinable), so a confined non-operator
|
|
103
|
+
* cannot CREATE such a symlink — it must pre-exist on the deployment's filesystem. Operators / the durable
|
|
104
|
+
* backend should keep `allowDirs` to real directories; a future symlink-aware write-time canonicalize closes it.
|
|
105
|
+
*/
|
|
29
106
|
declare function lexWithin(root: string, p: string): boolean;
|
|
107
|
+
/** Normalize a rules object's `allowDirs` (lexical `posix.normalize`) so stored rules are `..`-free and the
|
|
108
|
+
* tighten diff compares the same canonical-ish form the runtime resolves. */
|
|
30
109
|
export declare function normalizeRules(rules: SessionPermissionRules): SessionPermissionRules;
|
|
110
|
+
/**
|
|
111
|
+
* Pure tighten-only diff: the reasons `next` LOOSENS `prior` (empty ⇒ `next` is a pure tightening or no-op).
|
|
112
|
+
* Direction per field — deny-lists: removing an entry loosens. allowlists: removing the list (→ everything
|
|
113
|
+
* allowed) or adding an entry outside the prior set loosens; prior-undefined → any defined next only narrows.
|
|
114
|
+
* allowDirs: removing confinement, or adding a dir not within some prior dir, loosens (coarse raw containment).
|
|
115
|
+
*/
|
|
31
116
|
export declare function loosenReasons(prior: SessionPermissionRules, next: SessionPermissionRules): string[];
|
|
117
|
+
/** strip any caller-passed `rev` from a rules object so the store is the sole authority on it. */
|
|
32
118
|
export declare function stripRev(rules: SessionPermissionRules): SessionPermissionRules;
|
|
119
|
+
/**
|
|
120
|
+
* In-process default {@link SessionPolicyStore} (single-replica). A deployment that needs cross-replica
|
|
121
|
+
* durability supplies its own backend with the SAME CAS-rev + tighten-only semantics. Single-threaded JS makes
|
|
122
|
+
* the read-check-write trivially atomic here; a durable backend folds it into a CAS WHERE clause.
|
|
123
|
+
*/
|
|
33
124
|
export declare class InMemorySessionPolicyStore implements SessionPolicyStore {
|
|
34
125
|
private readonly map;
|
|
35
126
|
private key;
|
|
@@ -38,6 +129,9 @@ export declare class InMemorySessionPolicyStore implements SessionPolicyStore {
|
|
|
38
129
|
listBySession(sessionId: string): Promise<SessionRulesRecord[]>;
|
|
39
130
|
deleteBySession(sessionId: string): Promise<void>;
|
|
40
131
|
}
|
|
132
|
+
/** Test-only escape hatch for the module-private tighten-gate containment predicate (mirrors the
|
|
133
|
+
* `__testables` convention in file-snapshot-store.ts) — lets a regression pin the fold's win-family
|
|
134
|
+
* conditionality directly rather than only through `loosenReasons`'s allowDirs branch. */
|
|
41
135
|
export declare const __testables: {
|
|
42
136
|
lexWithin: typeof lexWithin;
|
|
43
137
|
};
|
|
@@ -1,19 +1,99 @@
|
|
|
1
1
|
import type { AgentMessage } from "../internal/harness-types.js";
|
|
2
2
|
import type { Session } from "./session.js";
|
|
3
3
|
import type { ToolEffect } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wake/resume side-effect reconciliation.
|
|
6
|
+
*
|
|
7
|
+
* Problem (design/07 · C3): a task can be interrupted (crash, kill, timeout, lost instance) in the
|
|
8
|
+
* window between an assistant message with tool calls being persisted and that turn's tool results
|
|
9
|
+
* being flushed. The session event log then ends with one or more **orphan tool calls** — a tool
|
|
10
|
+
* call with no matching `toolResult`. On wake this is two bugs at once:
|
|
11
|
+
* 1. Most chat APIs reject a request whose assistant tool_call is not followed by a tool result.
|
|
12
|
+
* 2. The tool's `execute` may already have run (the side effect happened) — or may not have. The
|
|
13
|
+
* outcome is genuinely unknown.
|
|
14
|
+
*
|
|
15
|
+
* Contract enforced here:
|
|
16
|
+
* - **Never auto-replay a tool.** Reconstruction (`buildSessionContext`) already does not re-run
|
|
17
|
+
* tools; this guarantees we also never synthesize a "success" that hides an unknown outcome.
|
|
18
|
+
* - For each orphan, append a synthetic `toolResult` (isError) that tells the model the call was
|
|
19
|
+
* interrupted and its outcome is unknown — making the transcript API-valid AND handing the
|
|
20
|
+
* decision to the model under its verification discipline.
|
|
21
|
+
* - Use the tool's {@link ToolEffect} to tune the message: `read` carries no risk at all; `idempotent`
|
|
22
|
+
* is replayable (re-issuing converges the state, but the interrupted attempt may have partially
|
|
23
|
+
* landed — RB-221 [1937]); `write` (and unknown tools, conservatively) must be verified/confirmed
|
|
24
|
+
* before repeating.
|
|
25
|
+
*
|
|
26
|
+
* This runs on `acquire` of a resumed session, before the next turn starts. New/empty sessions and
|
|
27
|
+
* cleanly-finished sessions have no orphans and are left untouched.
|
|
28
|
+
*/
|
|
29
|
+
/** One broken half of a tool call/result pair in the active branch (see {@link OrphanToolCall.kind}). */
|
|
4
30
|
export interface OrphanToolCall {
|
|
5
31
|
toolCallId: string;
|
|
6
32
|
toolName: string;
|
|
33
|
+
/**
|
|
34
|
+
* RB-398-a part 2 (blackboard [2105]): which half of the pair is missing.
|
|
35
|
+
* - absent (the historical shape) = a **call-orphan**: an assistant `toolCall` with no matching
|
|
36
|
+
* `toolResult` — the reconcile-actionable kind ({@link reconcileInterruptedSession} closes it
|
|
37
|
+
* with a synthetic interrupted result).
|
|
38
|
+
* - `"result"` = a **result-orphan**: a `toolResult` with no matching assistant `toolCall` — a
|
|
39
|
+
* protocol-invalid context every mainstream chat API rejects. The oracle used to walk only
|
|
40
|
+
* assistant→result, so it reported a context in exactly this state as clean while the
|
|
41
|
+
* request-time sweeper (`context-guard`'s `dropOrphanToolResults`) was silently deleting the
|
|
42
|
+
* result. NOT reconcile-actionable (appending another result would not repair it); consumers
|
|
43
|
+
* deciding on repair must filter on this field.
|
|
44
|
+
* Additive: absent = call-orphan, so pre-existing consumers and persisted expectations are
|
|
45
|
+
* byte-identical for the historical direction.
|
|
46
|
+
*/
|
|
7
47
|
kind?: "result";
|
|
8
48
|
}
|
|
49
|
+
/** The machine-readable discriminator on a reconcile-appended result — `details.errorKind` on the persisted
|
|
50
|
+
* message and `errorCode` on the synthetic `tool_end` frame, so a consumer never parses the text. Two-valued
|
|
51
|
+
* (a third value needs a cross-repo slip, see {@link reconcileInterruptedSession}). */
|
|
9
52
|
export type ReconciledErrorKind = "interrupted_never_started" | "interrupted_outcome_unknown";
|
|
53
|
+
/** One orphan closed by {@link reconcileInterruptedSession}, with everything the caller needs to mint the
|
|
54
|
+
* matching stream frame WITHOUT re-reading the session. */
|
|
10
55
|
export type RecoveredOrphan = OrphanToolCall & {
|
|
56
|
+
/** The persisted toolResult entry id (message-identity Phase 1). */
|
|
11
57
|
entryId: string;
|
|
58
|
+
/** The model-facing text that was persisted — the synthetic `tool_end`'s `output` body. Handed back
|
|
59
|
+
* because the caller's frame is otherwise BODY-LESS: a consumer rendering tool output from frames saw an
|
|
60
|
+
* empty result for every interrupted call, while the model-facing transcript carried the full
|
|
61
|
+
* `[INTERRUPTED]` explanation (the two faces disagreed about the same call). */
|
|
12
62
|
text: string;
|
|
63
|
+
/** Which of the two honest states this orphan was closed in — the frame's `errorCode`. */
|
|
13
64
|
errorKind: ReconciledErrorKind;
|
|
14
65
|
};
|
|
66
|
+
/** Outcome of reconciling a resumed session. */
|
|
15
67
|
export interface ReconcileReport {
|
|
68
|
+
/** Orphan calls that were closed with a synthetic interrupted result (empty when nothing to do). `entryId` =
|
|
69
|
+
* the persisted toolResult entry id — message-identity Phase 1: the caller emits a `message_committed` for each
|
|
70
|
+
* so a consumer's `entryId → message` map stays complete for these reconcile-appended results (they render a
|
|
71
|
+
* `tool_end`). They are never a compaction floor (`toolResult` is excluded from cut points), so this is map
|
|
72
|
+
* completeness, not `preserved_segment` correctness. */
|
|
16
73
|
recovered: RecoveredOrphan[];
|
|
17
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Find assistant tool calls in `messages` that have no matching `toolResult`.
|
|
77
|
+
* Order follows first appearance in the transcript (orphans are normally only at the tail).
|
|
78
|
+
*
|
|
79
|
+
* `suspendedBatch` (design/45 §15.2 net-add #7): the set of tool-call ids belonging to a **deliberately
|
|
80
|
+
* suspended** batch (the pending + not-yet-started calls of a durable-checkpoint suspension). These are
|
|
81
|
+
* NOT orphans — they are waiting to be resumed via `runner.resume()`, not crash-interrupted. Wake-path
|
|
82
|
+
* reconcile must skip them, or it would append `[INTERRUPTED]` results and **destroy the suspended
|
|
83
|
+
* batch** (the bug this guard fixes: the wake/resume naming overload became real data destruction).
|
|
84
|
+
* `runner.resume()` uses a checkpoint-aware entry that bypasses this reconcile entirely for those calls.
|
|
85
|
+
*/
|
|
18
86
|
export declare function findOrphanToolCalls(messages: AgentMessage[], suspendedBatch?: ReadonlySet<string>): OrphanToolCall[];
|
|
87
|
+
/**
|
|
88
|
+
* Reconcile a resumed session's active branch: close any orphan tool calls with a synthetic
|
|
89
|
+
* interrupted `toolResult` (never re-running the tool). Returns what was recovered.
|
|
90
|
+
*
|
|
91
|
+
* @param toolEffects optional name→effect map (from the task's tools). Unknown names are treated as
|
|
92
|
+
* `write` — the conservative default, so an unrecognized interrupted call is never said to be safe.
|
|
93
|
+
* @param startedToolCallIds optional set of tool-call ids whose execution START the caller observed
|
|
94
|
+
* during the run being reconciled (the live-abort caller has this knowledge; the wake/crash path
|
|
95
|
+
* does not — the previous process is gone). When provided, an orphan absent from the set provably
|
|
96
|
+
* never started: it is closed with a "never executed — safe to re-issue" result instead of the
|
|
97
|
+
* conservative unknown-outcome wording. When omitted, every orphan keeps the conservative texts.
|
|
98
|
+
*/
|
|
19
99
|
export declare function reconcileInterruptedSession(session: Session, toolEffects?: Map<string, ToolEffect>, suspendedBatch?: ReadonlySet<string>, startedToolCallIds?: ReadonlySet<string>): Promise<ReconcileReport>;
|
|
@@ -3,41 +3,126 @@ import { type AcquiredSession, type SessionStore, type SessionStoreSummary } fro
|
|
|
3
3
|
import type { StoreDurability } from "./checkpoint-store.js";
|
|
4
4
|
import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
|
|
5
5
|
export { SESSION_DEFAULT_TTL_DAYS };
|
|
6
|
+
/** What idle-eviction and `release` do to the underlying repo. */
|
|
6
7
|
export type EvictPolicy = "delete" | "forget";
|
|
7
8
|
export interface TtlSessionStoreOptions {
|
|
9
|
+
/** Idle TTL before a cached session is evicted. Default 7 days. */
|
|
8
10
|
defaultTtlDays?: number;
|
|
11
|
+
/** Optional background sweep interval (ms). Off by default (sweeps opportunistically on acquire). */
|
|
9
12
|
sweepIntervalMs?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Backing session repo. Default `InMemorySessionRepo` (process-only, lost on restart). Supply a
|
|
15
|
+
* **durable** repo (e.g. a TiDB-backed `SessionRepo`, or the vendored file repo) to persist
|
|
16
|
+
* sessions: `acquire` then **resumes** an existing session via `repo.open`, so any stateless
|
|
17
|
+
* runner instance — even after a restart or on another replica — continues the same conversation.
|
|
18
|
+
*/
|
|
10
19
|
repo?: SessionRepo;
|
|
20
|
+
/**
|
|
21
|
+
* What idle-eviction and `release` do to the repo:
|
|
22
|
+
* - `"delete"` — remove the session from the repo too (correct for the throwaway in-memory repo).
|
|
23
|
+
* - `"forget"` — only drop the in-memory cache; the **durable** backend keeps the history so it can
|
|
24
|
+
* be resumed later (the backend runs its own retention/GC).
|
|
25
|
+
*
|
|
26
|
+
* Defaults to `"delete"` for the built-in in-memory repo and `"forget"` when a custom `repo` is
|
|
27
|
+
* supplied — so a durable store never silently deletes history on an idle timer.
|
|
28
|
+
*/
|
|
11
29
|
evict?: EvictPolicy;
|
|
30
|
+
/**
|
|
31
|
+
* design/173 §2.3 — the store's {@link StoreDurability} declaration. This cache layer cannot
|
|
32
|
+
* inspect whether the injected {@link repo} persists across processes, so the DEPLOYMENT that
|
|
33
|
+
* wired the repo states the fact (e.g. `FileStorageBackend` passes `"durable"`). Defaults:
|
|
34
|
+
* without a custom `repo` the truth is knowable — `"process-local"` is declared; with a custom
|
|
35
|
+
* `repo` and no explicit value the store declares NOTHING, which the wiring manifest reads
|
|
36
|
+
* fail-closed as process-local (under-promise, never over-promise).
|
|
37
|
+
*/
|
|
12
38
|
durability?: StoreDurability;
|
|
13
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* TTL-cached {@link SessionStore} over a pluggable {@link SessionRepo}.
|
|
42
|
+
*
|
|
43
|
+
* Sessions are cached in process memory and evicted after `defaultTtlDays` of inactivity. With the
|
|
44
|
+
* default in-memory repo they live only in this process (lost on restart). Supply a **durable** repo
|
|
45
|
+
* and the same caching/concurrency logic becomes a "session center": on `acquire`, a session not in
|
|
46
|
+
* the live cache is **resumed from the repo** (`repo.open`) rather than recreated — so a stateless
|
|
47
|
+
* runner resumes a conversation after a restart or across replicas. The optimistic-lock seam
|
|
48
|
+
* (`SessionWriteOptions.expectedLeafId` → `SessionError("conflict")`) keeps two replicas writing the
|
|
49
|
+
* same session safe; the Runner reconciles a lost CAS and retries.
|
|
50
|
+
*
|
|
51
|
+
* See `design/10-会话持久化与会话中心.md` for the durable-backend (TiDB) contract.
|
|
52
|
+
*/
|
|
14
53
|
export declare class TtlSessionStore implements SessionStore {
|
|
54
|
+
/** Managed-retention declaration (ruled 2026-08-05, retention.ts): with a custom repo the default
|
|
55
|
+
* evict policy is `forget` — cache eviction only, the durable rows live on — and there is no
|
|
56
|
+
* tenant-scoped scheduled-deletion contract on any arm. Honestly `"none"`: a deployment with a
|
|
57
|
+
* LOCKED retention policy refuses to start over this store. (The `SessionStore` interface itself
|
|
58
|
+
* is declaration-agnostic; the retention face is the structural `RetentionDeclaring` shape.) */
|
|
15
59
|
readonly retention: "none";
|
|
60
|
+
/** design/173 §2.3 — see {@link TtlSessionStoreOptions.durability} for the declaration rules. */
|
|
16
61
|
readonly durability?: StoreDurability;
|
|
17
62
|
private repo;
|
|
18
63
|
private entries;
|
|
64
|
+
/** Session-ownership rows (the multi-tenant admission capability face — see SessionStore.ownerOf).
|
|
65
|
+
* Deliberately NOT part of `entries`: an evicted-but-durable session (`forget` / evict:"forget")
|
|
66
|
+
* still EXISTS, and its ownership row outliving the cache entry is what keeps a live session from
|
|
67
|
+
* reading as `undefined` (= re-claimable by anyone). Rows die only when the repo's history does. */
|
|
19
68
|
private owners;
|
|
69
|
+
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) share one session. */
|
|
20
70
|
private pending;
|
|
71
|
+
/** Sessions pinned by a design/45 checkpoint — skipped by idle sweep until unpinned (B6/§5). */
|
|
21
72
|
private pinned;
|
|
22
73
|
private defaultTtlMs;
|
|
23
74
|
private evictPolicy;
|
|
24
75
|
private sweepTimer?;
|
|
25
76
|
constructor(opts?: TtlSessionStoreOptions);
|
|
77
|
+
/** Get an existing session by id (resuming from the repo if needed), or create one. */
|
|
26
78
|
acquire(sessionId?: string, opts?: {
|
|
27
79
|
requireExisting?: boolean;
|
|
28
80
|
}): Promise<AcquiredSession>;
|
|
81
|
+
/** Resume an existing session from the repo, or create it if the backend has no such id (unless
|
|
82
|
+
* `requireExisting`, in which case a genuinely-missing id fails loud instead — design/114 Phase3). */
|
|
29
83
|
private openOrCreate;
|
|
30
84
|
private createAndStore;
|
|
85
|
+
/** Refresh the idle timer for a session. */
|
|
31
86
|
touch(sessionId: string): void;
|
|
87
|
+
/** Record the most recent task run on a cached session (the `/resume` `lastRunId`); also bumps the idle
|
|
88
|
+
* timer. Best-effort — a no-op if the session isn't cached here (the run will still be re-attachable via
|
|
89
|
+
* its checkpoint). */
|
|
32
90
|
noteTaskRun(sessionId: string, taskId: string): void;
|
|
91
|
+
/** List the live cache as {@link SessionStoreSummary} projections, newest-first by `lastActiveAt` (so a
|
|
92
|
+
* shell's `/resume` shows recent sessions + their last run first). The in-memory store lists only its
|
|
93
|
+
* live cache, not evicted/durable history — a durable backend overrides this to list persisted sessions. */
|
|
33
94
|
list(): Promise<SessionStoreSummary[]>;
|
|
95
|
+
/**
|
|
96
|
+
* design/110 (F3) — FORK a session: copy `sourceId`'s committed history into a NEW session and return its id
|
|
97
|
+
* (or `null` when the source doesn't exist). Delegates to the backing repo's `fork`, so a durable repo persists the
|
|
98
|
+
* branch; the in-memory repo holds it in-process. Registering the result in this cache makes `acquire(forkedId)`
|
|
99
|
+
* hit immediately — the synchronous child run that `Agent(subagent_type:"fork")` launches resumes the fork without a repo round-trip.
|
|
100
|
+
*
|
|
101
|
+
* Adding this method is what makes {@link hasSessionFork} true for a `TtlSessionStore` wrapping ANY repo, so the
|
|
102
|
+
* fork capability mounts (it was INERT before — the durable repo had `fork` but the store never exposed it).
|
|
103
|
+
*/
|
|
34
104
|
fork(sourceId: string, owner?: string | null): Promise<string | null>;
|
|
105
|
+
/** Drop a session from the live set. With a durable repo (`evict: "forget"`) the history is kept. */
|
|
35
106
|
release(sessionId: string): Promise<void>;
|
|
107
|
+
/** Drop only the cached view — the repo keeps the session regardless of `evictPolicy`, so a re-acquire
|
|
108
|
+
* re-opens the SAME history (B-17: the reconcile retry / prepare-throw paths must never delete). Cost
|
|
109
|
+
* on the default in-memory repo: ONE retained session object **per forgotten id** until process end
|
|
110
|
+
* (invisible to `size`/`sweep`, which track the cache) — accumulation across many distinct forgotten
|
|
111
|
+
* ids is the accepted trade-off vs silent history loss; deployments with churn use a durable repo,
|
|
112
|
+
* where retention is the backend's normal job. A pinned id stays pinned (a forget must never expose a
|
|
113
|
+
* suspended task's session to sweep eviction). */
|
|
36
114
|
forget(sessionId: string): void;
|
|
115
|
+
/** Pin a session so idle sweep can't evict it while a design/45 checkpoint awaits resume (§5). */
|
|
37
116
|
pin(sessionId: string): void;
|
|
117
|
+
/** Release a design/45 pin (on resume or checkpoint expiry); the session resumes normal idle eviction. */
|
|
38
118
|
unpin(sessionId: string): void;
|
|
119
|
+
/** Multi-tenant admission capability (see the SessionStore contract for the three-state semantics
|
|
120
|
+
* and the first-write-wins requirement — both are load-bearing for the deployment's owner gate). */
|
|
39
121
|
ownerOf(sessionId: string): Promise<string | null | undefined>;
|
|
122
|
+
/** First-write-wins idempotent upsert; a synchronous Map write is atomic per the contract. Makes no
|
|
123
|
+
* authorization decision — the authorizer re-reads {@link ownerOf} and judges there. */
|
|
40
124
|
register(sessionId: string, owner: string | null): Promise<void>;
|
|
125
|
+
/** Evict idle-expired sessions from the cache. Deletes durable history only when `evict: "delete"`. */
|
|
41
126
|
sweep(now?: number): void;
|
|
42
127
|
get size(): number;
|
|
43
128
|
dispose(): void;
|
package/dist/core/session.d.ts
CHANGED
|
@@ -7,39 +7,192 @@ export { SessionError } from "../internal/harness.js";
|
|
|
7
7
|
export type { SessionWriteOptions } from "../internal/harness.js";
|
|
8
8
|
import type { Session } from "../internal/harness.js";
|
|
9
9
|
import type { SessionTreeEntry } from "../internal/harness.js";
|
|
10
|
+
/** True when `err` is a session optimistic-lock conflict (a concurrent writer won the branch leaf). */
|
|
10
11
|
export declare function isSessionConflict(err: unknown): boolean;
|
|
12
|
+
/** A session handed back by a {@link SessionStore}: the harness `Session` plus its resolved id. */
|
|
11
13
|
export interface AcquiredSession {
|
|
12
14
|
session: Session;
|
|
13
15
|
sessionId: string;
|
|
14
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* A lightweight, read-only projection of one session for a `/resume` list (shell K-5 — re-attach without an
|
|
19
|
+
* N+1 walk). Returned in bulk by {@link SessionStore.list}, newest-first by `lastActiveAt` by convention.
|
|
20
|
+
* `lastTaskId` is the engine's term for what the service/wire surfaces as the session's **`lastRunId`** — the
|
|
21
|
+
* most recent task run on this session, so a shell re-attaches in ONE hop.
|
|
22
|
+
*
|
|
23
|
+
* Named `*StoreSummary` (not the bare `SessionSummary` an earlier version used) because that name collided
|
|
24
|
+
* with an unrelated, differently-shaped `SessionSummary` downstream (the SDK's wire GET /v1/sessions row) —
|
|
25
|
+
* same name, two shapes, the exact "同名异形" pattern a cross-repo cleanup was independently eradicating
|
|
26
|
+
* elsewhere (黑板 [1912]/[1913]/[1914]). This type is this store interface's own projection, never itself
|
|
27
|
+
* serialized onto the wire — a service backend re-projects/renames its fields (`lastTaskId` → `lastRunId`)
|
|
28
|
+
* when building the actual wire response.
|
|
29
|
+
*/
|
|
15
30
|
export interface SessionStoreSummary {
|
|
31
|
+
/** The session id. */
|
|
16
32
|
sessionId: string;
|
|
33
|
+
/** When the session was created ({@link SessionMetadata.createdAt}, ISO string); absent when the backend's
|
|
34
|
+
* cache view doesn't carry it. */
|
|
17
35
|
createdAt?: string;
|
|
36
|
+
/** Epoch ms the session was last acquired / touched / ran — the newest-first sort key for "recent sessions". */
|
|
18
37
|
lastActiveAt: number;
|
|
38
|
+
/** The most recent task run on this session (core's `taskId`; the service projects it as `lastRunId`).
|
|
39
|
+
* Absent until a task has run on the session (recorded via {@link SessionStore.noteTaskRun}). */
|
|
19
40
|
lastTaskId?: string;
|
|
41
|
+
/** F7 fork-GC seam: the parent session this one was forked from ({@link SessionMetadata.forkedFrom}).
|
|
42
|
+
* Lets a consumer render/reap fork lineage; absent on non-forked sessions and on cache views that
|
|
43
|
+
* predate the fork. The durable source of truth is the repo's persisted metadata. */
|
|
20
44
|
forkedFrom?: string;
|
|
21
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The session lifecycle/caching layer the {@link Runner} depends on.
|
|
48
|
+
*
|
|
49
|
+
* `TtlSessionStore` is the default (in-memory, 7-day idle TTL). Provide a custom implementation
|
|
50
|
+
* to back sessions with durable, external storage (the "Session center"): `acquire` reconstructs
|
|
51
|
+
* a session from the persisted event log so any stateless runner instance can resume it.
|
|
52
|
+
*
|
|
53
|
+
* Implementations must be safe under concurrent `acquire` of the same id (return one session).
|
|
54
|
+
*/
|
|
22
55
|
export interface SessionStore {
|
|
56
|
+
/**
|
|
57
|
+
* design/173 §2.3 — durability declaration (see
|
|
58
|
+
* {@link import("./checkpoint-store.js").StoreDurability}): whether sessions this store accepts
|
|
59
|
+
* SURVIVE A PROCESS RESTART. That is the whole claim of this axis — multi-replica
|
|
60
|
+
* coordination/concurrency is a separate backend property this field does not license (review
|
|
61
|
+
* fold r1-F2/r2-F3; a single-instance file-backed store declares `"durable"` honestly).
|
|
62
|
+
* Declaration, never duck-typing (the wiring manifest relays it verbatim — it never sniffs class
|
|
63
|
+
* names); ABSENT is read fail-closed as `"process-local"` (under-promise, never over-promise).
|
|
64
|
+
* `TtlSessionStore` over a durable {@link SessionRepo} declares via its `durability` option — the
|
|
65
|
+
* store cannot inspect the repo's persistence, so the DEPLOYMENT that wired the repo states the
|
|
66
|
+
* fact.
|
|
67
|
+
*/
|
|
23
68
|
readonly durability?: import("./checkpoint-store.js").StoreDurability;
|
|
69
|
+
/** Get an existing session by id, or create one (optionally with a caller-supplied id). design/114 Phase3:
|
|
70
|
+
* `opts.requireExisting` ⇒ a store MUST fail loud (throw a `not_found` {@link SessionError}) on a genuinely
|
|
71
|
+
* missing id rather than create-on-miss — so a reuse-style warm-resume of a gone session errors instead of
|
|
72
|
+
* silently starting a fresh empty run. A store that cannot honor it MUST still not silently create (either
|
|
73
|
+
* implement the check or reject the option). */
|
|
24
74
|
acquire(sessionId?: string, opts?: {
|
|
25
75
|
requireExisting?: boolean;
|
|
26
76
|
}): Promise<AcquiredSession>;
|
|
77
|
+
/** Mark a session recently active (resets idle TTL where applicable). May be async for durable stores. */
|
|
27
78
|
touch(sessionId: string): void | Promise<void>;
|
|
79
|
+
/** Drop a session from the live set (e.g. a throwaway task). Durable history may be retained by the backend. */
|
|
28
80
|
release(sessionId: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Drop only the CACHED view of a session — **never** deletes history (unlike `release`, which with the
|
|
83
|
+
* in-memory `TtlSessionStore`'s default `evict: "delete"` policy removes the session from the repo too).
|
|
84
|
+
* The Runner uses this where the intent is "discard a stale view and re-wake", and on a prepare-phase
|
|
85
|
+
* throw after acquire (audit B-17 + the 1.93.0 throw-cleanup contract gap: calling `release` there
|
|
86
|
+
* silently DELETED an existing session's whole history on the default store — the re-acquire then
|
|
87
|
+
* continued on an empty same-id session). **Optional, but custom stores SHOULD implement it**: when
|
|
88
|
+
* absent the Runner performs NO fallback (it never substitutes `release`, whose contract permits
|
|
89
|
+
* deletion — B-17 council) — the cached view is simply not dropped, so a persistent reconcile conflict
|
|
90
|
+
* exhausts its retry budget and propagates instead of risking history. A store whose `release` is
|
|
91
|
+
* already history-safe can implement `forget = release`.
|
|
92
|
+
*/
|
|
29
93
|
forget?(sessionId: string): void | Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Protect a session from idle eviction while a design/45 checkpoint references it (a suspended task
|
|
96
|
+
* must still be resumable). `pin` before suspend, `unpin` on resume/expiry. **Optional**: a durable
|
|
97
|
+
* backend that never idle-GCs sessions (service's `tidb-session-store`) implements both as no-ops and
|
|
98
|
+
* is still safe; only the in-memory `TtlSessionStore` needs a real pin (its `sweep` skips pinned ids).
|
|
99
|
+
* See design/45 §5 / M3.
|
|
100
|
+
*/
|
|
30
101
|
pin?(sessionId: string): void | Promise<void>;
|
|
31
102
|
unpin?(sessionId: string): void | Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Record that a task run STARTED on a session (the Runner calls this at task start, when both ids are
|
|
105
|
+
* known) so the store can surface it as {@link SessionStoreSummary.lastTaskId} — letting a `/resume` list
|
|
106
|
+
* re-attach to the most recent run, even one still in-flight or suspended. **Optional + best-effort**: a
|
|
107
|
+
* store that surfaces no session list may ignore it; it MUST NOT throw. The engine's `taskId` is the
|
|
108
|
+
* service's `lastRunId`.
|
|
109
|
+
*/
|
|
32
110
|
noteTaskRun?(sessionId: string, taskId: string): void | Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Enumerate known sessions as lightweight {@link SessionStoreSummary} projections (newest-first by
|
|
113
|
+
* `lastActiveAt`) so a shell's `/resume` lists sessions + their last run in ONE call. **Optional**: the
|
|
114
|
+
* in-memory `TtlSessionStore` lists its live cache; a durable backend lists persisted sessions. A store
|
|
115
|
+
* that cannot enumerate omits this (the shell then has no session-list affordance — honest degrade).
|
|
116
|
+
*/
|
|
33
117
|
list?(): Promise<SessionStoreSummary[]>;
|
|
118
|
+
/**
|
|
119
|
+
* design/110 — FORK a session: copy `sourceId`'s durable history (root→leaf) into a NEW session and return its
|
|
120
|
+
* id (or `null` if the source doesn't exist). The forked session is independent (writes to it don't touch the
|
|
121
|
+
* source). Backs CC `/fork` (continue with the parent's full context) AND the inheriting-context subagent fork
|
|
122
|
+
* (run a child on the fork so it inherits the parent context + shares the prompt-cache prefix). **Optional**:
|
|
123
|
+
* a store implements it only if it can replay history. `TtlSessionStore` DOES — it delegates to its
|
|
124
|
+
* {@link SessionRepo} (so {@link hasSessionFork} is true) — BUT the fork is only as durable as that repo:
|
|
125
|
+
* an in-memory repo forks IN-PROCESS yet loses the copy on restart, while a durable repo (file/pg) makes the
|
|
126
|
+
* fork survive restart/replica. **Capability presence ≠ durability** — a cross-replica consumer (design/114
|
|
127
|
+
* warm-resume) MUST wire a durable repo; core cannot verify durability, only fail-loud on a missing source at
|
|
128
|
+
* fork time (returns `null`). `owner` (if given) scopes the new session for multi-tenant stores, and a store that
|
|
129
|
+
* also implements {@link register} MUST make the fork's ownership row agree with it — a fork COPIES the source's
|
|
130
|
+
* history, so the copy must never be weaker than the original: with no `owner` argument the fork inherits the
|
|
131
|
+
* SOURCE's row (a source with no row mints none, keeping the authorizer's claim branch intact). An EXPLICIT
|
|
132
|
+
* `owner: null` follows the same inheritance, deliberately: "anonymous" on a copy of an OWNED source would
|
|
133
|
+
* make that history claimable by anyone — a downgrade this face exists to prevent — so `null` only takes
|
|
134
|
+
* effect when the source itself is unowned/anonymous. MUST copy the COMMITTED leaf (an in-flight
|
|
135
|
+
* uncommitted turn of an active source is not included — a fork is a snapshot at the fork point).
|
|
136
|
+
*/
|
|
34
137
|
fork?(sourceId: string, owner?: string | null): Promise<string | null>;
|
|
138
|
+
/**
|
|
139
|
+
* Session-ownership capability face (multi-tenant admission — the deployment's authorizer probes for
|
|
140
|
+
* BOTH methods and runs its owner gate only when they exist; a store without them forces the
|
|
141
|
+
* deployment into its fail-closed arm, refusing caller-supplied session ids under multi-tenancy).
|
|
142
|
+
*
|
|
143
|
+
* `ownerOf` is THREE-state and the states must never collapse:
|
|
144
|
+
* - `undefined` — the session has NO ownership row (unknown id): the authorizer claims it for the
|
|
145
|
+
* calling principal (or 404s under require-existing).
|
|
146
|
+
* - `null` — the session EXISTS and is anonymous (unowned): admitted (single-tenant/dev sharing).
|
|
147
|
+
* - `string` — owned: a different principal is refused.
|
|
148
|
+
* Collapsing `undefined` into `null` breaks the gate in both directions at once (an unknown id
|
|
149
|
+
* reads as an admissible anonymous session; an anonymous session reads as claimable).
|
|
150
|
+
*/
|
|
35
151
|
ownerOf?(sessionId: string): Promise<string | null | undefined>;
|
|
152
|
+
/**
|
|
153
|
+
* Idempotent ownership upsert, FIRST-WRITE-WINS and atomic: the first registered owner is kept,
|
|
154
|
+
* later calls neither overwrite nor throw. Deliberately makes NO authorization decision — the
|
|
155
|
+
* caller re-reads {@link ownerOf} after registering and judges there (which is exactly why the
|
|
156
|
+
* first-write-wins read-back must be atomic: a concurrent loser has to SEE the winner's owner on
|
|
157
|
+
* re-read, or it silently attaches to the winner's session). In-memory synchronous writes satisfy
|
|
158
|
+
* this trivially; durable implementations need a real uniqueness guarantee, not read-then-write.
|
|
159
|
+
* The ownership row must never expire BEFORE its session (a live session reading as `undefined`
|
|
160
|
+
* would be re-claimable by anyone); same-lifetime or longer both satisfy the contract — so a store that
|
|
161
|
+
* deletes the row when it deletes the history must gate that on the history delete having SUCCEEDED.
|
|
162
|
+
* An implementation must never store a value that {@link ownerOf} reads back as `undefined` (a row that
|
|
163
|
+
* EXISTS yet reads as "no row" is permanently claimable and never ownable): a FIRST write whose `owner` is
|
|
164
|
+
* neither a string nor `null` is refused loudly instead — the no-overwrite-no-throw rule above governs
|
|
165
|
+
* LATER calls, which stay silent no-ops whatever they carry.
|
|
166
|
+
*/
|
|
36
167
|
register?(sessionId: string, owner: string | null): Promise<void>;
|
|
168
|
+
/** Number of live/cached sessions (best-effort). */
|
|
37
169
|
readonly size: number;
|
|
170
|
+
/** Release resources (timers, connections). Best-effort; must not throw. */
|
|
38
171
|
dispose(): void | Promise<void>;
|
|
39
172
|
}
|
|
173
|
+
/** Structural detection: does this store expose the optional {@link SessionStore.fork}? Mounts the fork
|
|
174
|
+
* capability only when true (INERT otherwise — mirrors `hasScheduler`/`hasBackgroundShell`). */
|
|
40
175
|
export declare function hasSessionFork(store: SessionStore): store is SessionStore & {
|
|
41
176
|
fork: NonNullable<SessionStore["fork"]>;
|
|
42
177
|
};
|
|
178
|
+
/**
|
|
179
|
+
* F3 bounded-wake recipe, packaged. Given the **path-ordered** entries of a branch
|
|
180
|
+
* (root→leaf, e.g. `await session.getBranch(leafId)` or a durable store's leaf-walk),
|
|
181
|
+
* returns the smallest self-contained tail a stateless runner needs to resume:
|
|
182
|
+
*
|
|
183
|
+
* - `floorEntryId` = the **latest** compaction's `firstKeptEntryId` (null if never compacted),
|
|
184
|
+
* - `tail` = entries from that floor to the leaf — i.e. the surviving compaction summary
|
|
185
|
+
* plus every message kept after it. Everything before the floor is already folded into
|
|
186
|
+
* that summary, so it can stay cold in durable storage.
|
|
187
|
+
*
|
|
188
|
+
* Hand the result straight to a floored storage:
|
|
189
|
+
* `new InMemorySessionStorage({ entries: tail, floorEntryId })` (or your durable equivalent).
|
|
190
|
+
* `buildContext()` on that is identical to waking from the full tree — see `session-floor.test.ts`.
|
|
191
|
+
*
|
|
192
|
+
* Pure and allocation-light: it scans for the last compaction and slices. If the tree was never
|
|
193
|
+
* compacted (or the floor isn't on this path — shouldn't happen for a real branch) it returns the
|
|
194
|
+
* full input unchanged with `floorEntryId: null`, so callers can always use the result verbatim.
|
|
195
|
+
*/
|
|
43
196
|
export declare function boundedTail(pathEntries: readonly SessionTreeEntry[]): {
|
|
44
197
|
tail: SessionTreeEntry[];
|
|
45
198
|
floorEntryId: string | null;
|
|
@@ -1,4 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/177 — the SharedMemoryStoreProvider conformance suite: the cross-repo acceptance contract a
|
|
3
|
+
* third-party / deployment-authored provider runs against itself. Same shape as the memory-backend
|
|
4
|
+
* suite it is modeled on — runner-agnostic (node:assert, zero vitest), a FRESH instance per case, and
|
|
5
|
+
* an optional `runAssertion` wrapper so a vitest consumer can turn each case into an `it`.
|
|
6
|
+
*
|
|
7
|
+
* FIXTURES ARE HANDED IN, not written through the seam. Unlike the entry-transactional backend suite
|
|
8
|
+
* (which can seed itself through its own patch face), this seam is read-only and has no public write
|
|
9
|
+
* port at all, so `make` receives the intended contents and returns a provider that reads them back.
|
|
10
|
+
*
|
|
11
|
+
* NO CROSS-INSTANCE / CAS CASE — a DECLARED absence, not an omission: a compare-and-set case exists to
|
|
12
|
+
* cover a write-race axis, and a face with no write channel has no such axis. There is consequently no
|
|
13
|
+
* skip hook either: every case below is mandatory, so a passing run never means "most of it passed".
|
|
14
|
+
*
|
|
15
|
+
* WHAT IS ASSERTED. Only the PROVIDER's own obligations — faithfulness, absence-as-a-value, prefix
|
|
16
|
+
* completeness, cancellation, isolation. Rendering, pagination, sanitizing and refusal wording are the
|
|
17
|
+
* engine's job and are covered by the engine's own tests; asserting them here would make a third party
|
|
18
|
+
* responsible for behavior it does not implement.
|
|
19
|
+
*/
|
|
1
20
|
import type { SharedMemorySnapshot, SharedMemoryStoreInfo, SharedMemoryStoreProvider } from "./types.js";
|
|
21
|
+
/** What an implementation must load into its own storage before returning a provider over it. */
|
|
2
22
|
export interface SharedMemoryFixture {
|
|
3
23
|
state: SharedMemorySnapshot["state"];
|
|
4
24
|
stores: Array<{
|
|
@@ -12,6 +32,8 @@ export interface SharedMemoryFixture {
|
|
|
12
32
|
}
|
|
13
33
|
export interface SharedMemoryStoreContractHooks {
|
|
14
34
|
make: (fixture: SharedMemoryFixture) => Promise<SharedMemoryStoreProvider> | SharedMemoryStoreProvider;
|
|
35
|
+
/** Wrap each case (vitest `it`, sequential chaining, …). Default: run them sequentially. */
|
|
15
36
|
runAssertion?: (name: string, fn: () => Promise<void>) => void | Promise<void>;
|
|
16
37
|
}
|
|
38
|
+
/** Run the full conformance suite. Throws (assert) on the first violated clause. */
|
|
17
39
|
export declare function sharedMemoryStoreContract(hooks: SharedMemoryStoreContractHooks): Promise<void>;
|