@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
|
@@ -2,12 +2,6 @@ import { PrincipalOrgMemoryWire } from "@sema-agent/registry-core";
|
|
|
2
2
|
import { assertPrincipalShape } from "./security.js";
|
|
3
3
|
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
4
4
|
const ORG_SCOPE_RE = /^org:\S+$/;
|
|
5
|
-
/**
|
|
6
|
-
* 单机腿:装载 `MEMORY_ORG_DIRECTORY_JSON`(`Record<principal, Record<orgScope, {write?}>>`)。启动期调用,
|
|
7
|
-
* **fail-loud**——任何非法直接 throw 且消息点名坏在哪个键(响亮拒是正确方向:一张半坏的授权表比拒绝
|
|
8
|
-
* 启动更危险)。principal 键复用 {@link assertPrincipalShape}(拒保留哨兵/超长租户名,与所有其它
|
|
9
|
-
* principal 入口同一道闸);scope 键必须是 `org:` 前缀、值必须是仅含可选 `write: boolean` 的 plain object。
|
|
10
|
-
*/
|
|
11
5
|
export function parseOrgDirectoryStatic(json) {
|
|
12
6
|
let parsed;
|
|
13
7
|
try {
|
|
@@ -21,7 +15,7 @@ export function parseOrgDirectoryStatic(json) {
|
|
|
21
15
|
}
|
|
22
16
|
const table = new Map();
|
|
23
17
|
for (const [principal, scopesRaw] of Object.entries(parsed)) {
|
|
24
|
-
assertPrincipalShape(principal);
|
|
18
|
+
assertPrincipalShape(principal);
|
|
25
19
|
if (!isPlainObject(scopesRaw)) {
|
|
26
20
|
throw new Error(`MEMORY_ORG_DIRECTORY_JSON[${JSON.stringify(principal)}] must be an object of org scopes`);
|
|
27
21
|
}
|
|
@@ -47,10 +41,6 @@ export function parseOrgDirectoryStatic(json) {
|
|
|
47
41
|
}
|
|
48
42
|
return table;
|
|
49
43
|
}
|
|
50
|
-
/**
|
|
51
|
-
* 建目录:远程腿(fetchSection,per-principal TTL 缓存 + in-flight 去重 + 退避窗 + gen 高水位)或单机腿
|
|
52
|
-
* (staticTable,恒同步成功、无 TTL/退避语义——operator 自证配置面)。两腿互斥,装配点必须恰好给一个。
|
|
53
|
-
*/
|
|
54
44
|
export function createOrgMemoryDirectory(opts) {
|
|
55
45
|
if ((opts.fetchSection !== undefined) === (opts.staticTable !== undefined)) {
|
|
56
46
|
throw new Error("createOrgMemoryDirectory requires exactly one of fetchSection or staticTable (both given or both missing)");
|
|
@@ -64,8 +54,6 @@ export function createOrgMemoryDirectory(opts) {
|
|
|
64
54
|
};
|
|
65
55
|
}
|
|
66
56
|
if (opts.fetchSection === undefined) {
|
|
67
|
-
// Unreachable given the xor guard above (staticTable was undefined, so fetchSection must not be) — this
|
|
68
|
-
// check exists ONLY so the type checker narrows fetchSection to a function without a cast.
|
|
69
57
|
throw new Error("createOrgMemoryDirectory: fetchSection missing (invariant violated)");
|
|
70
58
|
}
|
|
71
59
|
const fetchSection = opts.fetchSection;
|
|
@@ -76,7 +64,6 @@ export function createOrgMemoryDirectory(opts) {
|
|
|
76
64
|
const inflight = new Map();
|
|
77
65
|
function markUnavailable(state, reason) {
|
|
78
66
|
state.unavailable = { until: now() + backoffMs, reason };
|
|
79
|
-
// C6: retryAfterMs never 0/negative — the window just opened, so its remaining time IS backoffMs.
|
|
80
67
|
return { kind: "unavailable", reason, retryAfterMs: Math.max(backoffMs, 1000) };
|
|
81
68
|
}
|
|
82
69
|
async function doFetch(principal, state) {
|
|
@@ -91,17 +78,14 @@ export function createOrgMemoryDirectory(opts) {
|
|
|
91
78
|
return markUnavailable(state, "section_absent");
|
|
92
79
|
const parsed = PrincipalOrgMemoryWire.safeParse(raw);
|
|
93
80
|
if (!parsed.success) {
|
|
94
|
-
// malformed: does NOT touch state.grant — a bad shape must not refresh (or clear) an existing granted entry's
|
|
95
|
-
// timestamp; a still-fresh old grant keeps serving from cache on the NEXT lookup (checked before we ever get here).
|
|
96
81
|
return markUnavailable(state, "malformed");
|
|
97
82
|
}
|
|
98
83
|
if (state.highWaterGen !== undefined && parsed.data.gen < state.highWaterGen) {
|
|
99
|
-
// LB stale-replica defense: an older generation than one we've already seen is NEVER adopted.
|
|
100
84
|
return markUnavailable(state, "stale_generation");
|
|
101
85
|
}
|
|
102
86
|
state.highWaterGen = parsed.data.gen;
|
|
103
87
|
state.grant = { scopes: parsed.data.scopes, at: now() };
|
|
104
|
-
delete state.unavailable;
|
|
88
|
+
delete state.unavailable;
|
|
105
89
|
return { kind: "granted", scopes: parsed.data.scopes };
|
|
106
90
|
}
|
|
107
91
|
return {
|
|
@@ -112,15 +96,12 @@ export function createOrgMemoryDirectory(opts) {
|
|
|
112
96
|
states.set(principal, state);
|
|
113
97
|
}
|
|
114
98
|
const t = now();
|
|
115
|
-
// 1. Fresh grant (positive OR negative/empty result) always wins — no fetch, no backoff check.
|
|
116
99
|
if (state.grant && state.grant.at + grantTtlMs > t) {
|
|
117
100
|
return { kind: "granted", scopes: state.grant.scopes };
|
|
118
101
|
}
|
|
119
|
-
// 2. Inside an open backoff window → answer from the window WITHOUT re-fetching.
|
|
120
102
|
if (state.unavailable && state.unavailable.until > t) {
|
|
121
103
|
return { kind: "unavailable", reason: state.unavailable.reason, retryAfterMs: Math.max(state.unavailable.until - t, 1000) };
|
|
122
104
|
}
|
|
123
|
-
// 3. Grant expired (or never existed) and no active backoff → fetch, deduping concurrent callers.
|
|
124
105
|
const dup = inflight.get(principal);
|
|
125
106
|
if (dup)
|
|
126
107
|
return dup;
|
|
@@ -130,9 +111,6 @@ export function createOrgMemoryDirectory(opts) {
|
|
|
130
111
|
},
|
|
131
112
|
};
|
|
132
113
|
}
|
|
133
|
-
/** core 侧「瞬时不可用,重试」契约的载体:一个带 `retryAfterMs`(ms)属性的真 Error 子类,core 据此铸
|
|
134
|
-
* **瞬时**码 memory.admission_required(503,重试可改判——与终局的 memory.admission_denied 恰好相对)
|
|
135
|
-
* 并透传该值。子类而非事后挂属性——避免任何宽松断言就能拿到正确类型。 */
|
|
136
114
|
export class OrgMemoryAdmissionRetryError extends Error {
|
|
137
115
|
retryAfterMs;
|
|
138
116
|
constructor(message, retryAfterMs) {
|
|
@@ -141,28 +119,15 @@ export class OrgMemoryAdmissionRetryError extends Error {
|
|
|
141
119
|
this.name = "OrgMemoryAdmissionRetryError";
|
|
142
120
|
}
|
|
143
121
|
}
|
|
144
|
-
/**
|
|
145
|
-
* `RunnerDeps.memoryScopeAdmission` 的 server 实装。deployment-origin 的 requested 项一律放行(operator
|
|
146
|
-
* 自证——v1 不做中央收窄部署声明);request-origin 项查 `directory.lookup(principal)`:granted 时逐项核
|
|
147
|
-
* 对是否在授权集里(任何一项缺席 = 整体终局拒绝,不做部分放行——reason 只点名调用方自己请求过的 scope
|
|
148
|
-
* 串,不逐 scope 展开「为什么」,避免变成成员探针);unavailable 时 throw 一个带 `retryAfterMs` 的
|
|
149
|
-
* {@link OrgMemoryAdmissionRetryError}。write 面独立收窄:deployment-origin 恒授,request-origin 仅当目录
|
|
150
|
-
* 条目 `write === true` 才授,否则收窄为 null(即使读侧整体放行)。
|
|
151
|
-
*/
|
|
152
122
|
export function createMemoryScopeAdmission(directory, opts) {
|
|
153
123
|
const emit = (outcome, details) => {
|
|
154
124
|
try {
|
|
155
125
|
opts.onOutcome?.(outcome, details);
|
|
156
126
|
}
|
|
157
127
|
catch {
|
|
158
|
-
/* an observability hook must never break admission */
|
|
159
128
|
}
|
|
160
129
|
};
|
|
161
130
|
return async ({ principal, requested, requestedWriteScope }) => {
|
|
162
|
-
/** 复审 D-3(7.1.0 后修):归因面。判决记录若不带 principal,运维只看得到「某个 org scope 被拒」而
|
|
163
|
-
* 无从知道**谁**被拒——「per-principal 归因走日志」这句设计承诺(与 CHANGELOG 7.0.0 同款公开陈述)
|
|
164
|
-
* 此前在代码里没有落点。principal 缺席时**不**塞键:干净 admit 那一路必须保持 `details === undefined`
|
|
165
|
-
* (装配点据此判「无事发生不打日志」;塞个 `principal: undefined` 会把每个成功任务变成一行 warn)。 */
|
|
166
131
|
const attributed = (details) => principal === undefined ? details : { principal, ...details };
|
|
167
132
|
const requestOriginRequested = requested.filter((r) => r.origin === "request");
|
|
168
133
|
const writeIsRequestOrigin = requestedWriteScope?.origin === "request";
|
|
@@ -171,18 +136,14 @@ export function createMemoryScopeAdmission(directory, opts) {
|
|
|
171
136
|
const fullWriteScope = requestedWriteScope?.scope ?? null;
|
|
172
137
|
const admitFull = () => ({ ok: true, scopes: fullScopes, writeScope: fullWriteScope });
|
|
173
138
|
async function decide() {
|
|
174
|
-
// deployment-only requests (no request-origin item anywhere, including the write slot): nothing to
|
|
175
|
-
// look up — v1 does not centrally narrow operator-declared deployment scopes.
|
|
176
139
|
if (!hasRequestOrigin)
|
|
177
140
|
return { kind: "admit", verdict: admitFull() };
|
|
178
141
|
if (principal === undefined) {
|
|
179
|
-
// Defensive arm: core already refuses this shape itself when a request-origin org scope is present
|
|
180
|
-
// with no principal, but the resolver must not assume that guard always ran first.
|
|
181
142
|
return { kind: "deny", reason: "principal absent", outcome: "principal_missing" };
|
|
182
143
|
}
|
|
183
144
|
const lookup = await directory.lookup(principal);
|
|
184
145
|
if (lookup.kind === "unavailable") {
|
|
185
|
-
const outcome = lookup.reason === "stale_generation" ? "directory_stale" : lookup.reason === "malformed" ? "directory_malformed" : "directory_absent";
|
|
146
|
+
const outcome = lookup.reason === "stale_generation" ? "directory_stale" : lookup.reason === "malformed" ? "directory_malformed" : "directory_absent";
|
|
186
147
|
return { kind: "unavailable", retryAfterMs: lookup.retryAfterMs, outcome, lookupReason: lookup.reason, details: { reason: lookup.reason } };
|
|
187
148
|
}
|
|
188
149
|
const deniedScopes = requestOriginRequested.filter((r) => !(r.scope in lookup.scopes)).map((r) => r.scope);
|
|
@@ -205,9 +166,6 @@ export function createMemoryScopeAdmission(directory, opts) {
|
|
|
205
166
|
}
|
|
206
167
|
const decision = await decide();
|
|
207
168
|
if (decision.kind === "admit") {
|
|
208
|
-
// C10(audit=纯观察**零**行为变化):write 收窄也是行为变化(enforce 下 harvest 静默零提交)——
|
|
209
|
-
// audit 模式对「读面通过但 write 未显式授」的判决只记 would-narrow,返回未收窄的全额;
|
|
210
|
-
// enforce 照常收窄。ok:true 分支的 writeScope 恒为 fullWriteScope 或 null(decide 只做这两值)。
|
|
211
169
|
if (opts.mode === "audit" && decision.verdict.ok && decision.verdict.writeScope !== fullWriteScope) {
|
|
212
170
|
emit("ok", attributed({ audited: true, wouldNarrowWriteScope: true }));
|
|
213
171
|
return admitFull();
|
|
@@ -216,8 +174,6 @@ export function createMemoryScopeAdmission(directory, opts) {
|
|
|
216
174
|
return decision.verdict;
|
|
217
175
|
}
|
|
218
176
|
if (opts.mode === "audit") {
|
|
219
|
-
// Zero behavior change: a would-deny (terminal or transient) is overridden to a full, UNNARROWED admit —
|
|
220
|
-
// the real decision is only ever recorded via onOutcome, never enforced.
|
|
221
177
|
emit(decision.outcome, attributed({ ...decision.details, audited: true, wouldDeny: true, ...(decision.kind === "unavailable" ? { retryAfterMs: decision.retryAfterMs } : { reason: decision.reason }) }));
|
|
222
178
|
return admitFull();
|
|
223
179
|
}
|
package/dist/parent-watch.js
CHANGED
|
@@ -1,43 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #219(黑板 [3617]):引擎 **parent 监视**。
|
|
3
|
-
*
|
|
4
|
-
* ## 防的是什么
|
|
5
|
-
*
|
|
6
|
-
* 壳(cli/TUI/桌面)自 spawn 引擎的**同机形**里,壳被 SIGKILL(崩溃、`kill -9`、OOM killer)之后没有
|
|
7
|
-
* 任何清理钩子跑得起来:引擎被 reparent 到 init,**继续活着** —— 占端口、占库连接池、占模型配额,而且
|
|
8
|
-
* 再没有人会给它发 SIGTERM(T11 的"骑进程组"腿只覆盖信号送得到的形;SIGKILL 那一路本来就无钩)。
|
|
9
|
-
* 于是引擎给自己装一只**自查**腿:定期问一句"当年生我的那个 pid 还在吗",不在就**自己**走正常停机。
|
|
10
|
-
*
|
|
11
|
-
* ## 机制(逐条都是契约)
|
|
12
|
-
*
|
|
13
|
-
* 1. **opt-in**:`SEMA_PARENT_PID` 缺席 ⇒ 整件不装配(`boot/shutdown.ts` 连 `createParentWatch` 都不调)。
|
|
14
|
-
* 存量部署逐字零变化。坏值拒启在 `config.ts` 的 `parentPidEnv()`。
|
|
15
|
-
* 2. **探活 = `process.kill(pid, 0)`**:0 号信号不投递任何信号,只做"这个 pid 存在吗 + 我能不能给它
|
|
16
|
-
* 发信号"的检查。两种失败方向**语义相反**,写反了后果不对称:
|
|
17
|
-
* · `ESRCH` = 没有这个进程 ⇒ 父**不在**(这是我们要抓的);
|
|
18
|
-
* · `EPERM` = 进程**在**,只是本进程无权给它发信号(父跑在另一个 uid 下 —— systemd/su/容器里
|
|
19
|
-
* 很常见)⇒ **算活着**。把 EPERM 当"不在",每台这种机器上的引擎会在开机后两拍内自杀。
|
|
20
|
-
* 3. **连续两拍才判死**:单拍抖动(探活恰好撞上一次瞬时错误)不误杀。任何一拍看到"活着"就清零。
|
|
21
|
-
* 4. **判死后走 `drain`**,即 `boot/shutdown.ts` 里 SIGTERM 用的那一条 `drainThenShutdown` —— in-flight
|
|
22
|
-
* run 的结算 / park 语义与人工停机**逐字一致**。本模块自己**从不** `process.exit`。
|
|
23
|
-
* 5. **interval 必须 unref**:监视腿不得成为"进程本该退出却退不掉"的理由(与 drain tick 刻意不 unref
|
|
24
|
-
* 的取向相反 —— 那一只 tick 就是停机驱动,这一只只是观察者)。
|
|
25
|
-
*
|
|
26
|
-
* ## 已知残余(成文,现版不收窄)
|
|
27
|
-
*
|
|
28
|
-
* · **pid 复用**:两拍窗口(≤30s)内父 pid 被系统复用,探活看到的是新进程,监视会判"父还活着"而不自退。
|
|
29
|
-
* · **僵尸父进程**(codex 复审 H,本机 perl fork 实测证实):`kill(pid, 0)` 只证明 **pid 表项**在,不证明
|
|
30
|
-
* 进程还在跑 —— 父退出但**没被它自己的父进程 wait 回收**时留下 zombie(`ps STAT=Z`),表项还在,于是
|
|
31
|
-
* 每一拍都判"活着",监视永不触发。触发条件是壳的**上级**不回收子进程(容器里的朴素 PID 1 是典型),
|
|
32
|
-
* 终端 / launchd / systemd 起的壳不在此列。
|
|
33
|
-
*
|
|
34
|
-
* 两条都源于同一个根:`kill(pid, 0)` 是**弱身份**判据。收窄需要一个真身份锚(`process.ppid` 变化、
|
|
35
|
-
* starttime、pidfd、或壳侧持有型管道),那是**机制换代**而不是参数微调,归设计属主裁。本件在定稿里
|
|
36
|
-
* 就是**尽力自愈**、不是强一致的父子生命周期绑定 —— 要强一致,该由壳侧开一条持有型管道并在断开时收尸。
|
|
37
|
-
*/
|
|
38
|
-
/** 探活拍频。**固定值,不开 env 旋钮**(设计定稿:有真需求再议,防旋钮增殖)。 */
|
|
39
1
|
export const PARENT_WATCH_INTERVAL_MS = 15_000;
|
|
40
|
-
/** 装配并**立刻**起监视(装配即 `parent_watch_armed`)。返回停机链用的 `stop()`。 */
|
|
41
2
|
export function createParentWatch(deps) {
|
|
42
3
|
const probe = deps.probe ?? ((pid) => void process.kill(pid, 0));
|
|
43
4
|
let firstMissAtMs;
|
|
@@ -52,7 +13,7 @@ export function createParentWatch(deps) {
|
|
|
52
13
|
if (stopped)
|
|
53
14
|
return;
|
|
54
15
|
if (deps.isStopped()) {
|
|
55
|
-
stop();
|
|
16
|
+
stop();
|
|
56
17
|
return;
|
|
57
18
|
}
|
|
58
19
|
let alive = true;
|
|
@@ -60,14 +21,8 @@ export function createParentWatch(deps) {
|
|
|
60
21
|
probe(deps.pid);
|
|
61
22
|
}
|
|
62
23
|
catch (err) {
|
|
63
|
-
// ⚠️ 方向:只有 ESRCH 才是"父不在"。EPERM(无权发信号)与任何其它 errno 都算**活着** ——
|
|
64
|
-
// 这一支的保守方向是"继续服务",因为误判的代价是把一台**正常**引擎连同它的 in-flight run 关掉。
|
|
65
24
|
const code = err?.code;
|
|
66
25
|
alive = code !== "ESRCH";
|
|
67
|
-
// 🔴 不静默(codex 复审 M):`kill(pid, 0)` 正常只给 ESRCH/EPERM 两个 errno,出现第三种意味着
|
|
68
|
-
// **探活这条腿自己坏了**(例:pid 越出 int32 ⇒ `ERR_INVALID_ARG_TYPE`),而它的落点恰是"判活",
|
|
69
|
-
// 于是监视会 armed 之后永久失效、零信号。判死方向不变(保守),但必须留下一条带 errno 的线索;
|
|
70
|
-
// 一次性 —— 每 15s 刷一行会把真信号淹掉。
|
|
71
26
|
if (code !== "ESRCH" && code !== "EPERM" && !probeErrorReported) {
|
|
72
27
|
probeErrorReported = true;
|
|
73
28
|
deps.log("parent_watch_probe_error", {
|
|
@@ -79,7 +34,7 @@ export function createParentWatch(deps) {
|
|
|
79
34
|
}
|
|
80
35
|
}
|
|
81
36
|
if (alive) {
|
|
82
|
-
firstMissAtMs = undefined;
|
|
37
|
+
firstMissAtMs = undefined;
|
|
83
38
|
return;
|
|
84
39
|
}
|
|
85
40
|
if (firstMissAtMs === undefined) {
|
|
@@ -95,7 +50,6 @@ export function createParentWatch(deps) {
|
|
|
95
50
|
});
|
|
96
51
|
deps.drain();
|
|
97
52
|
}, PARENT_WATCH_INTERVAL_MS);
|
|
98
|
-
// 观察者绝不是进程存活的理由(与 drain tick 刻意不 unref 的取向相反)。
|
|
99
53
|
timer.unref?.();
|
|
100
54
|
function stop() {
|
|
101
55
|
if (stopped)
|
package/dist/parked-decide.js
CHANGED
|
@@ -1,40 +1,11 @@
|
|
|
1
1
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
2
2
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope } from "./security.js";
|
|
3
|
-
/**
|
|
4
|
-
* pending checkpoint → parked bg 行匹配。
|
|
5
|
-
*
|
|
6
|
-
* 键链(core 1.390 事实):park 时 cp 落 `sourceTaskId: sessionId`(子会话 id,**不是** bg handle),
|
|
7
|
-
* 而 bg 行同样存该子会话 id(`row.sessionId`)与绑定 token(`row.parkedCheckpointToken`)。
|
|
8
|
-
* `listByScope` 只回 summary(含 handle/sessionId、不含 token),故两步:summary.sessionId 初筛 →
|
|
9
|
-
* `get(handle, scope)` 全行复核 token。
|
|
10
|
-
*
|
|
11
|
-
* token 复核是硬门,不是优化:行 REOPENED 后可在**新** token 下 re-park,旧 cp 的 decide 不得
|
|
12
|
-
* 匹配到它(后续 `claimParkedAgent` 的 CAS/binding_broken 是兜底,不是第一道门)。同理,list 与
|
|
13
|
-
* get 之间行被 stop/decide 抢走(离开 parked)时诚实 miss——调用方拿 undefined 走 legacy 腿,
|
|
14
|
-
* 该腿对已消失的 pending cp 有自己的 404/409 处置。
|
|
15
|
-
*/
|
|
16
3
|
export async function findParkedAgentForCheckpoint(agentStore, cp) {
|
|
17
|
-
// 🔴 A-057.44 [PARTIAL high](2026-08-19)—— **匿名部署上两套 scope 哨兵不同源**,分区查询恒落空。
|
|
18
|
-
// 病链:bg 行的 scope 写侧 = core `treeScope = reviveClaim?.row.scope ?? ctx.principal ??
|
|
19
|
-
// opts.background?.scope`,`ctx.principal` 在未开 REQUIRE_PRINCIPAL 的部署上恒 undefined ⇒ 落到本仓
|
|
20
|
-
// `capabilities/scenarios.ts` 填的 `"default"`;而 cp.scope 是 `encodeCheckpointScope(auth?.principal)`
|
|
21
|
-
// = `"_"`。店层是硬 `WHERE scope_key = ?`(MySQL/PG 双实现同形)⇒ 恒 miss ⇒ /decide 退回 legacy 腿 ⇒
|
|
22
|
-
// `getCtx(子代会话)` 必 null(全仓五个 putCtx 无一写 bg 子会话)⇒ 恒 409
|
|
23
|
-
// `conflict.resume_context_unavailable`,人的批准无处兑付、错误码还对真因有误导性。同一判别器被
|
|
24
|
-
// `server.ts` 的 deny-sweep `isParkedOwned` 复用,匿名下同样 miss ⇒ [1591] 那条静默跳过臂失效。
|
|
25
|
-
// 受害集 = REQUIRE_PRINCIPAL 未开 ∧ SQL 后端 ∧ DURABLE_APPROVAL=true。
|
|
26
|
-
//
|
|
27
|
-
// 归一**只能在读侧**(施工证伪了台账首选的「铸点改走 encodeCheckpointScope」——见
|
|
28
|
-
// `security.ts` 的 {@link backgroundScopesForCheckpointScope} 顶注:那个铸点的值同时喂进程内
|
|
29
|
-
// `defaultTaskRegistry`,而全仓读侧是一整族 `principal ?? "default"`,改铸点会把它们一起打瞎,
|
|
30
|
-
// 本车实测 subagent tail 建连当场 404)。所以这里按**别名集**查,别名表是单一属主。
|
|
31
4
|
for (const scope of backgroundScopesForCheckpointScope(cp.scope)) {
|
|
32
5
|
const parked = await agentStore.listByScope(scope, { status: "parked" });
|
|
33
6
|
for (const s of parked) {
|
|
34
7
|
if (s.sessionId !== cp.sessionId)
|
|
35
8
|
continue;
|
|
36
|
-
// ⚠️ 复核与下游(claim/rollback)都用**找到它的那个分区**,不是 cp.scope —— `decideParkedAgent`
|
|
37
|
-
// 之后取的是 `row.scope`/`row.owner` 双轴镜像([1588] Q3),故行自带的键就是权威。
|
|
38
9
|
const row = await agentStore.get(s.handle, scope);
|
|
39
10
|
if (row === null || row.status !== "parked")
|
|
40
11
|
continue;
|
|
@@ -45,9 +16,6 @@ export async function findParkedAgentForCheckpoint(agentStore, cp) {
|
|
|
45
16
|
}
|
|
46
17
|
return undefined;
|
|
47
18
|
}
|
|
48
|
-
/** [1588] Q1 裁定的 revive args 铸法:占位 prompt(objective 在 driveParkedResume 被丢弃,仅过非空门)、
|
|
49
|
-
* `run_in_background: true` 必须(revive.no_background 拒)、`name` 必传承重(名门校验+下游 park 资格
|
|
50
|
-
* 谓词吃 agentName)、`subagent_type` 按 roster 成员性判。 */
|
|
51
19
|
function mintReviveArgs(row, knownAgentTypes) {
|
|
52
20
|
return {
|
|
53
21
|
prompt: row.description ?? "resume parked agent",
|
|
@@ -57,34 +25,16 @@ function mintReviveArgs(row, knownAgentTypes) {
|
|
|
57
25
|
...(row.agentType !== undefined && knownAgentTypes.has(row.agentType) ? { subagent_type: row.agentType } : {}),
|
|
58
26
|
};
|
|
59
27
|
}
|
|
60
|
-
/**
|
|
61
|
-
* `/decide` parked 赎回腿(design/153 件3d server 半场;[1574]/[1584]/[1588] core 裁定形)。
|
|
62
|
-
*
|
|
63
|
-
* 返回 `undefined` = 该 pending checkpoint 不属于任何 parked 后台子代——调用方走既有任务级
|
|
64
|
-
* resumeCheckpoint 腿(零回归);返回结果对象 = 赎回腿已受理/已拒,调用方直接回 HTTP。
|
|
65
|
-
*
|
|
66
|
-
* 链:判别(findParkedAgentForCheckpoint)→ `claimParkedAgent`(access = 行镜像双轴
|
|
67
|
-
* `{scope: row.scope, owner: row.owner}`,[1588] Q3)→ outcome 铸造(D-1 echo/persisted 回落)
|
|
68
|
-
* → 裸 `reviveTool.execute(args, ctx.reviveClaim)`。execute 一旦发起 drive,rollback 全在 core 内
|
|
69
|
-
* (driveParkedResume);server 只在「claim 成功但 drive 从未发起」窗口补 `rollbackParkedClaim`
|
|
70
|
-
* ([1588] Q2:isError 入参级早退 + throw 两形,已核幂等/双滚=无害 lost)。
|
|
71
|
-
*
|
|
72
|
-
* 200 是**受理**语义(drive 是 core 内 fire-and-forget):失败经行/notification 面 + REOPENED
|
|
73
|
-
* re-park 诚实可见,操作员可再决。
|
|
74
|
-
*/
|
|
75
28
|
export async function decideParkedAgent(deps, req) {
|
|
76
29
|
let match;
|
|
77
30
|
try {
|
|
78
31
|
match = await findParkedAgentForCheckpoint(deps.agentStore, { token: req.token, scope: req.scope, sessionId: req.sessionId });
|
|
79
32
|
}
|
|
80
33
|
catch (e) {
|
|
81
|
-
// 复审 F2:bg 店瞬态读故障绝不能把(含任务级在内的)decide 变裸 500——诚实按 miss 处理,
|
|
82
|
-
// legacy 腿对 parked cp 有自己的 409 处置(retriable),任务级 cp 照常 resume。
|
|
83
34
|
try {
|
|
84
35
|
deps.warn?.("parked_decide_probe_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
85
36
|
}
|
|
86
37
|
catch {
|
|
87
|
-
// warn 自身故障不改变结果面
|
|
88
38
|
}
|
|
89
39
|
return undefined;
|
|
90
40
|
}
|
|
@@ -93,40 +43,15 @@ export async function decideParkedAgent(deps, req) {
|
|
|
93
43
|
const row = match.row;
|
|
94
44
|
const rememberRoot = req.grantRemember !== undefined ? (row.rootSessionId ?? row.parentSessionId) : undefined;
|
|
95
45
|
if (req.grantRemember !== undefined && rememberRoot === undefined) {
|
|
96
|
-
// remember 的 grant 必须锚在 root/host 会话(探针闭包键);行无任何会话锚 = 无处可落,claim 前诚实拒。
|
|
97
46
|
return { status: 400, body: { error: "remember cannot be applied: this parked agent row carries no root/parent session anchor for the exemption", errorCode: "decide.parked_remember_unsupported", taskId: match.handle } };
|
|
98
47
|
}
|
|
99
48
|
const pendingTool = req.pendingAction?.toolName;
|
|
100
49
|
if (pendingTool === "AskUserQuestion" && req.decision === "approve" && req.answer === undefined) {
|
|
101
|
-
// RB-459(core 5.7.0,F1/F6 翻案后唯一剩下的 claim 前拒):问题门的 approve 必须携 answer——
|
|
102
|
-
// parked 腿无 live answering face,消费 claim 后只会空跑一次 revive;claim 前拒是免损前置。带 answer 的
|
|
103
|
-
// approve 与任何 deny 都放行透传,core 是语义权威(header 逐字匹配等)。
|
|
104
|
-
// 🔴 #152 当年写这条时的机制理由是:core 的 pre-CAS 拒条件(「`taskConfig.onQuestion ??
|
|
105
|
-
// deps.onQuestion` 为 undefined 或 QUESTION_AWAITS_RESUME」)在 ASK_QUESTION_ENABLED 部署上恒不成立
|
|
106
|
-
// ⇒ core 放行,而赎回腿会拿 coordinator 的**空答**当人答并消费掉 checkpoint。
|
|
107
|
-
// ⚠️ 那个理由已被后续两处证伪,别再据它推理:①#166 起 QuestionCoordinator 的每一条无人应答臂(无
|
|
108
|
-
// ALS ctx / abort / 限流 / 呈现不了 / TTL)一律返 `{kind:"unavailable"}`,类头逐字承诺「It NEVER
|
|
109
|
-
// synthesizes an empty answer set」——赎回腿无 ctx,拿到的是 unavailable 而不是空答;②core 5.16 对
|
|
110
|
-
// 赎回中的 ask 走 redeemsApproval 臂返 isError(「will not silently self-answer」)。
|
|
111
|
-
// 本判据**保留**,理由换成免损前置:放过去也只会空跑一次 revive(claim 已被消费),claim 前拒是
|
|
112
|
-
// 更便宜、错误信息也更准的那一手(任务级孪生:src/http/server.ts 的 decide 路由)。
|
|
113
50
|
return { status: 400, body: { error: "pending action is AskUserQuestion — approve requires body.answer (the operator's answers[]); deny needs none", errorCode: "decide.parked_answer_required", taskId: match.handle } };
|
|
114
51
|
}
|
|
115
52
|
if (row.name === undefined) {
|
|
116
|
-
// forwardDurableApproval 只对具名子代成立,parked 行理应恒有 name;缺 = 行受损,claim 前诚实拒
|
|
117
|
-
// ([1588] Q1:name 必传承重——省略会把 revive 降级成匿名形)。
|
|
118
53
|
return { status: 409, body: { error: "parked row carries no agent name — the revive path requires a named agent (row damaged?)", errorCode: "decide.row_unrevivable", taskId: match.handle } };
|
|
119
54
|
}
|
|
120
|
-
// [1597]/A-010.1:席位重建**在 claim 之前**解开(codex 对抗复审 R1-中 的真 finding)。
|
|
121
|
-
// 🔴 位置即正确性:claim 之后到下面那道 try/catch(它才挂 `rollbackParkedClaim`)之间是一段
|
|
122
|
-
// **无回滚保护区**。工厂现在是 async(要现解 entitlement),把 await 放进那一段 = 任何 rejection
|
|
123
|
-
// 都带着一个已认领的 claim 逃逸,行上的 `parkClaimId` 要等 stale-claim 清算窗(默认可达一小时)
|
|
124
|
-
// 才放开,这期间该审批**再决不动**。搬到 claim 前:此刻还没有任何东西需要回滚,抛错就是干净地抛错。
|
|
125
|
-
// (工厂本身只读 row 的 scope/handle/sessionId/rootSessionId —— claim 不改这四个字段,搬位零语义差。)
|
|
126
|
-
// 🔴 A-057.44 第二半(codex 对抗复审 medium):席位收的是**权威 principal**,不是行上那个有损的
|
|
127
|
-
// `row.scope`。checkpoint 的 scope 列由 `encodeCheckpointScope(auth?.principal)` 铸,与 core 喂给
|
|
128
|
-
// `runtimeCapsResolver` 的是同一个值;`row.scope` 在 `principal ?? "default"` 约定下匿名与真名叫
|
|
129
|
-
// `default` 的租户同形(匿名别名匹配让这一格第一次真的可达,见 findParkedAgentForCheckpoint 顶注)。
|
|
130
55
|
const inheritedGate = deps.rebuildInheritedGate ? await deps.rebuildInheritedGate(row, { principal: decodeCheckpointScope(req.scope) }) : undefined;
|
|
131
56
|
const stores = { agentStore: deps.agentStore, checkpointStore: deps.checkpointStore };
|
|
132
57
|
const claim = await deps.registry.claimParkedAgent(stores, match.handle, { scope: row.scope, owner: row.owner });
|
|
@@ -135,13 +60,11 @@ export async function decideParkedAgent(deps, req) {
|
|
|
135
60
|
case "store_unreachable":
|
|
136
61
|
return { status: 503, body: { error: "the durable agent store is unreachable — retry", errorCode: "decide.store_unreachable", taskId: match.handle } };
|
|
137
62
|
case "claim_lost":
|
|
138
|
-
// 复审 F8:claim_lost 也覆盖「上一轮 rollback 落 retry(claim 悬挂)」的形——那时要等 reaper 的
|
|
139
|
-
// stale-claim 清算窗(BG_AGENT_PARK_CLAIM_STALE_MS)过后才能再决,文案如实给出两种可能。
|
|
140
63
|
return { status: 409, body: { error: "the parked agent is already claimed — either a concurrent decide won (re-check the pending list), or a stale claim is pending automatic cleanup (retry after the stale-claim window)", errorCode: "decide.claim_lost", taskId: match.handle } };
|
|
141
64
|
case "binding_broken":
|
|
142
65
|
case "checkpoint_not_pending":
|
|
143
66
|
return { status: 409, body: { error: "the approval gate moved under this decide (re-parked or already settled) — re-check the pending list", errorCode: "decide.gate_moved", taskId: match.handle } };
|
|
144
|
-
default:
|
|
67
|
+
default:
|
|
145
68
|
return { status: 409, body: { error: "the parked agent moved under this decide (stopped or already decided)", errorCode: "decide.not_parked", taskId: match.handle } };
|
|
146
69
|
}
|
|
147
70
|
}
|
|
@@ -150,17 +73,12 @@ export async function decideParkedAgent(deps, req) {
|
|
|
150
73
|
await req.onDecideCommitted?.();
|
|
151
74
|
}
|
|
152
75
|
catch {
|
|
153
|
-
// remember-grant 类挂点自吞错(与 legacy 腿 grantOnCommit 同姿势):绝不让豁免记账故障打断赎回。
|
|
154
76
|
}
|
|
155
77
|
if (req.grantRemember !== undefined && rememberRoot !== undefined) {
|
|
156
78
|
try {
|
|
157
|
-
// claim 赢(≈ legacy 腿 markResuming CAS 赢)后、drive 前落店——复活腿自己的下一个同工具 ask
|
|
158
|
-
// 即可命中豁免(与 legacy 腿 1.294 grant-timing 裁定同向)。grant 失败由路由闭包自记 warn 并
|
|
159
|
-
// 让 rememberApplied 如实为 false,不阻断赎回。
|
|
160
79
|
await req.grantRemember(rememberRoot);
|
|
161
80
|
}
|
|
162
81
|
catch {
|
|
163
|
-
// 同上:豁免记账故障不进赎回结果面
|
|
164
82
|
}
|
|
165
83
|
}
|
|
166
84
|
const pa = req.pendingAction;
|
|
@@ -172,13 +90,8 @@ export async function decideParkedAgent(deps, req) {
|
|
|
172
90
|
boundInputHash: req.binding?.boundInputHash ?? persistedHash,
|
|
173
91
|
decision: req.decision === "approve" ? "allow" : "deny",
|
|
174
92
|
...(req.decision === "approve" && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
|
|
175
|
-
// RB-459:answer 原样透传(核不预判、不改写——header 逐字匹配/合法组合全在 core 的 resume 校验)。
|
|
176
93
|
...(req.answer !== undefined ? { answer: req.answer } : {}),
|
|
177
|
-
// B10:存在性判定,不是真值判定 —— reason 可以是空串("运维显式选择不写理由"),这与"没传 reason"
|
|
178
|
-
// 是两件不同的事(姊妹 approval-hmac.ts `env.reason ?? null` 同判据:`??` 只在 null/undefined 时落
|
|
179
|
-
// null,空串照样入签名载荷)。真值判定会把显式 "" 与缺席折成同一个结果,审计/签名面丢了这个区分。
|
|
180
94
|
...(req.reason !== undefined ? { reason: req.reason } : {}),
|
|
181
|
-
// #204 件6③:结算出处逐字上 outcome(见 `ParkedDecideRequest.settledBy` 顶注)。
|
|
182
95
|
settledBy: req.settledBy,
|
|
183
96
|
};
|
|
184
97
|
const ctx = {
|
|
@@ -189,20 +102,15 @@ export async function decideParkedAgent(deps, req) {
|
|
|
189
102
|
parkedResume: {
|
|
190
103
|
ticket,
|
|
191
104
|
outcome,
|
|
192
|
-
// [1597] 席位:重建父约束链(同进程时 core 的内存 registry 也能自动重供,席位供了也无害
|
|
193
|
-
// ——core 侧 count 校验对得上即用;跨进程/重启形全靠这里)。
|
|
194
105
|
...(deps.rebuildInheritedGate ? { inheritedGate } : {}),
|
|
195
106
|
},
|
|
196
107
|
},
|
|
197
108
|
};
|
|
198
|
-
// 复审 F7:warn 回调自身故障(duck-typed logger 缺方法/日志面抖动)绝不能改写 rollback 的结果面——
|
|
199
|
-
// 已成功的回滚若因 warn 抛错逃逸成 500/reject,操作员会对着"失败"重试一个其实已回纯的行。
|
|
200
109
|
const quietWarn = (event, fields) => {
|
|
201
110
|
try {
|
|
202
111
|
deps.warn?.(event, fields);
|
|
203
112
|
}
|
|
204
113
|
catch {
|
|
205
|
-
// 观测面故障不进结果面
|
|
206
114
|
}
|
|
207
115
|
};
|
|
208
116
|
const rollback = async (why) => {
|
|
@@ -216,13 +124,6 @@ export async function decideParkedAgent(deps, req) {
|
|
|
216
124
|
return "retry";
|
|
217
125
|
}
|
|
218
126
|
};
|
|
219
|
-
// [3911] per-agent tail 的**内容帧供给形**必须随赎回周期作废(codex 复审 round3 high)。
|
|
220
|
-
// 本车道是第四条腿:上面这只 ctx 是**裸的**(只有 toolCallId + reviveClaim),没有宿主
|
|
221
|
-
// forward / notification sink —— 于是 ①park 是非终态,三条 run 腿的终态撤销手够不到它;
|
|
222
|
-
// ②赎回周期是否有帧供给取决于**进程内 retain 条目还在不在**(在 ⇒ core 用 spawn 期的
|
|
223
|
-
// internalsSnapshot 继续转发;不在 ⇒ 这一周期结构上一帧都不会有)。两种都不是本处能断言的事,
|
|
224
|
-
// 所以把上一周期的答案作废成「不知道」:新周期真有帧时首帧会重新登记真相,没有帧时 meta 也不会
|
|
225
|
-
// 拿旧周期的 `on` 骗订阅方去等一个永远不来的转录(那正是本批要消灭的静默形)。
|
|
226
127
|
defaultSubagentTailBus.forgetHandleContentMode(match.handle);
|
|
227
128
|
let receipt;
|
|
228
129
|
try {
|
|
@@ -242,19 +143,10 @@ export async function decideParkedAgent(deps, req) {
|
|
|
242
143
|
}
|
|
243
144
|
const isErr = typeof receipt === "object" && receipt !== null && receipt.isError === true;
|
|
244
145
|
if (isErr) {
|
|
245
|
-
// execute 的入参级早退臂(revive.no_background/name 门/type 门等)return 于任何 registry 动作之前,
|
|
246
|
-
// core 不代滚([1588] Q2 核定)——这仍在「drive 未发起」窗口,server 补滚。
|
|
247
|
-
// 复审 G-F5 口径补充:isError 不恒等于「早退未滚」——core 的 parked_resume.startup_failed 臂是
|
|
248
|
-
// drive 已发起且 core 已自滚后的 isError;此处补滚在 rollbackParkedClaim 的 claimId CAS 下退化为
|
|
249
|
-
// 无害 "lost"([1588] Q2 幂等核定),结果面仍诚实(操作员重查 pending 列表即可自愈)。
|
|
250
146
|
const disp = await rollback("execute_rejected");
|
|
251
147
|
const content = receipt.content;
|
|
252
148
|
const receiptError = receipt.details?.error;
|
|
253
149
|
if (receiptError === "parked_resume.startup_failed") {
|
|
254
|
-
// RB-459([2458] 裁定形):resume 已发起但启动即败(含 core 对 outcome.answer 的语义拒——
|
|
255
|
-
// header 不匹配/answer 配错门,invalid_outcome field:"answer" 在 drive 内抛)——422 透传 core
|
|
256
|
-
// 的码,不塌 409 revive_rejected(那是入参级早退的形)。core 已自滚,上面的补滚在 claimId CAS
|
|
257
|
-
// 下退化无害 lost(G-F5 口径),行回 parked 可再决。
|
|
258
150
|
return {
|
|
259
151
|
status: 422,
|
|
260
152
|
body: {
|
package/dist/per-task-image.js
CHANGED
|
@@ -1,34 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* §7 P0.5 — per-task sandbox image binding (門B "用户按需选像"). IMAGE-API-DESIGN §7.
|
|
3
|
-
*
|
|
4
|
-
* The caller sends a PROFILE (intent) on the run body — NEVER a digest. The trusted control plane (this service,
|
|
5
|
-
* with the caller's principal) resolves the profile → a pullable `repo@digest`, FAIL-CLOSED, re-admitting by the
|
|
6
|
-
* SAME per-principal visibility the `/v1/images/select` admission uses. Why profile-not-digest: a digest is a
|
|
7
|
-
* stable, enumerable sha256 with no caller-binding — trusting a caller-supplied digest is fail-OPEN (a user could
|
|
8
|
-
* pass another tenant's digest to pull an image they can't see). Resolving inside the trust boundary closes that.
|
|
9
|
-
*
|
|
10
|
-
* The resolved ref reaches the worker-global {@link import("@sema-agent/core").ExecutionEnvFactory} (which
|
|
11
|
-
* only receives core's deliberately-minimal `{sessionId}` context) via {@link PerTaskImageRegistry} — core's own
|
|
12
|
-
* doctrine: "richer routing is captured in the factory closure by the trusted control plane". This module is that
|
|
13
|
-
* capture, kept narrow + unit-testable.
|
|
14
|
-
*/
|
|
15
1
|
import { BoundedSessionMap } from "./bounded-session-map.js";
|
|
16
|
-
/** Boolean capability keys a caller may require. `nestedBuildMode` is a posture string, not a boolean gate, so it
|
|
17
|
-
* is intentionally NOT requestable here (capability admission is allow-on-true only). */
|
|
18
2
|
const REQUESTABLE_CAPS = new Set(["browser", "db", "nestedBuild"]);
|
|
19
|
-
/**
|
|
20
|
-
* Resolve a requested sandbox image PROFILE to a pullable `repo@digest`, FAIL-CLOSED, re-admitting by `viewer`:
|
|
21
|
-
* - `latestPublished(profile, viewer)` applies the SAME `visibilityClause` + `status='published'` filter the
|
|
22
|
-
* select admission uses. Not visible / not published ⇒ `{ok:false, 404}` (never silent-allow).
|
|
23
|
-
* - each `capabilitiesNeeded` entry must be a known boolean capability AND true on the resolved image, else fail.
|
|
24
|
-
* Pure over the injected `index` ⇒ unit-testable with a fake resolver.
|
|
25
|
-
*
|
|
26
|
-
* NOTE (review LOW — error-code asymmetry, DELIBERATE): a not-visible / capability-miss here returns 404, whereas
|
|
27
|
-
* the operator-facing `POST /v1/images/select` returns 409 + `missing`. That is intentional: task admission is an
|
|
28
|
-
* UNTRUSTED-caller gate that must NOT distinguish "profile exists but you can't see it" from "doesn't exist"
|
|
29
|
-
* (revealing that leaks the existence of other tenants' images), so it collapses both to 404. The select catalog
|
|
30
|
-
* is the operator's own scoped view, where the richer 409 + `missing` detail is safe.
|
|
31
|
-
*/
|
|
32
3
|
export async function resolveSandboxImageRef(opts) {
|
|
33
4
|
const entry = await opts.index.latestPublished(opts.profile, opts.viewer);
|
|
34
5
|
if (!entry) {
|
|
@@ -43,44 +14,16 @@ export async function resolveSandboxImageRef(opts) {
|
|
|
43
14
|
return { ok: false, status: 404, message: `sandbox image for profile "${opts.profile}" does not provide capability "${cap}"` };
|
|
44
15
|
}
|
|
45
16
|
}
|
|
46
|
-
// capabilities ride along for RFC A1 envFacts (the resolved entry is already in hand — no second query).
|
|
47
17
|
return { ok: true, ref: `${entry.repo}@${entry.digest}`, capabilities: entry.capabilities };
|
|
48
18
|
}
|
|
49
|
-
/**
|
|
50
|
-
* The trusted-control-plane bridge from `resolveSpec` (resolves the per-task image with the caller's principal) to
|
|
51
|
-
* the worker-global `ExecutionEnvFactory` (gets only core's minimal `{sessionId, taskId?}`). NOT a durable store
|
|
52
|
-
* and NOT a cache: `resolveSpec` REPOPULATES it on EVERY call — initial AND resume, on whichever replica runs
|
|
53
|
-
* resolveSpec immediately before the factory — so a cross-replica resume re-resolves LIVE (the resume re-admit
|
|
54
|
-
* discipline: a stale persisted digest can never bypass admission), and there is nothing durable to go stale.
|
|
55
|
-
*
|
|
56
|
-
* KEYED BY sessionId — the ONLY identifier stable across the factory `ctx` on every path (adversarial-review
|
|
57
|
-
* round-2): `/v1/runs` mints its OWN durable taskId that clobbers `spec.taskId`, so keying by taskId misses on the
|
|
58
|
-
* primary path; `sessionId` survives the spec spread on every path. `get()` is NON-removing (the factory may be
|
|
59
|
-
* invoked >once per logical task). Cleanup is by bounded eviction (oldest-first) — each entry is a tiny string; an
|
|
60
|
-
* evicted task is long finished.
|
|
61
|
-
*
|
|
62
|
-
* ⚠️ KNOWN narrow edge (same-principal correctness, NOT a security breach): two CONCURRENT requests for the SAME
|
|
63
|
-
* session (necessarily the same owner — session ownership is enforced) race `set()`; a request the run-claim later
|
|
64
|
-
* rejects (409) can leave its image bound for the active run. Both images are the same principal's own admitted
|
|
65
|
-
* images, so the worst case is the user's run using the user's OTHER selected image. The hardening
|
|
66
|
-
* (register-after-claim, keyed by the durable run id) needs the registry threaded into the server claim path —
|
|
67
|
-
* tracked as a follow-up. cascade/verify + a profile are rejected up front (they strip/replace the session).
|
|
68
|
-
*/
|
|
69
19
|
export class PerTaskImageRegistry {
|
|
70
|
-
// #97 R11: the sessionId-keyed bounded-eviction map is the shared BoundedSessionMap basis (this class was
|
|
71
|
-
// its original verbatim form); the composition below keeps THIS class's own ctor/method surface unchanged.
|
|
72
20
|
bounded;
|
|
73
21
|
constructor(maxEntries = 4096) {
|
|
74
22
|
this.bounded = new BoundedSessionMap(maxEntries);
|
|
75
23
|
}
|
|
76
|
-
/** Register the resolved `repo@digest` for a session. A re-set (resume / a later same-session task) overwrites;
|
|
77
|
-
* on overflow the oldest insertion is evicted so an orphaned entry (a resolveSpec not followed by a factory
|
|
78
|
-
* call, e.g. a rejected/pre-exec-failed request) stays bounded. */
|
|
79
24
|
set(sessionId, ref) {
|
|
80
25
|
this.bounded.set(sessionId, ref);
|
|
81
26
|
}
|
|
82
|
-
/** Read the ref for a session (NON-removing — the factory may be invoked more than once per logical task).
|
|
83
|
-
* `undefined` ⇒ no per-task image for this session ⇒ the factory uses its worker-global default image. */
|
|
84
27
|
get(sessionId) {
|
|
85
28
|
return this.bounded.get(sessionId);
|
|
86
29
|
}
|
package/dist/plan-cache-probe.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
// Verbatim from core strategy-store.ts. 🔴 「keep in sync」不是靠这句话 —— lockstep 活钉在
|
|
2
|
-
// test/plan-cache-probe.test.ts(真读 core 的 dist strategy-store.js 做 set diff + 切词正则/阈值同源),
|
|
3
|
-
// 任一侧改词表当场红。2026-07-30 之前这里只有声明没有钉:删掉一个词全量零红(实测)。
|
|
4
1
|
const STOP = new Set([
|
|
5
2
|
"the", "and", "for", "with", "from", "this", "that", "into", "your", "you", "are", "was", "were", "has",
|
|
6
3
|
"have", "had", "not", "but", "all", "any", "can", "use", "using", "via", "then", "than", "out", "get",
|
|
7
4
|
"got", "its", "it's", "their", "them", "they", "what", "when", "which", "who", "how", "why", "a", "an",
|
|
8
5
|
"of", "to", "in", "on", "is", "it", "or", "as", "at", "by", "be", "do", "if", "so", "no", "up", "we",
|
|
9
6
|
]);
|
|
10
|
-
/** Lowercased word tokens (core口径). */
|
|
11
|
-
/** core 5.49 G2(CJK 检索修)同形:拉丁/数字按非字母数字切段 + CJK 按单字与相邻二元组入集
|
|
12
|
-
* (core `memory-vector.termSet` 逐字搬)。旧形 `match(/[a-z0-9]+/g)` 对纯中文任务名恒空 ⇒
|
|
13
|
-
* probe 测出的 recurrence 与 plan-cache 真命中脱钩——同源钉(plan-cache-probe.test.ts)就是
|
|
14
|
-
* 为这次换形红的。 */
|
|
15
7
|
const CJK_RUN = /[-ヿㇰ-ㇿ㐀-䶿一-鿿가-힣豈-]+/g;
|
|
16
8
|
export function tokens(s) {
|
|
17
9
|
const lower = s.toLowerCase();
|
|
@@ -28,18 +20,13 @@ export function tokens(s) {
|
|
|
28
20
|
}
|
|
29
21
|
return [...out];
|
|
30
22
|
}
|
|
31
|
-
/** CJK 词条豁免长度/停词判(core 5.49 `CJK_LEAD` 同形:单字即显著);拉丁保持 ≥3 且非停词。 */
|
|
32
23
|
const CJK_LEAD = /^[-ヿㇰ-ㇿ㐀-䶿一-鿿가-힣豈-]/;
|
|
33
24
|
export function significant(s) {
|
|
34
25
|
return tokens(s).filter((t) => (CJK_LEAD.test(t) ? true : t.length >= 3 && !STOP.has(t)));
|
|
35
26
|
}
|
|
36
|
-
const RING = 1000;
|
|
37
|
-
const MAX_SCOPES = 2000;
|
|
38
|
-
export const QSIG_CAP = 12;
|
|
39
|
-
/**
|
|
40
|
-
* In-memory recurrence tracker. One per process (single replica per worker in prod; cross-replica
|
|
41
|
-
* aggregation is unnecessary for a 2-week instrument — each instance reports its own slice).
|
|
42
|
-
*/
|
|
27
|
+
const RING = 1000;
|
|
28
|
+
const MAX_SCOPES = 2000;
|
|
29
|
+
export const QSIG_CAP = 12;
|
|
43
30
|
export class PlanCacheProbe {
|
|
44
31
|
metrics;
|
|
45
32
|
logger;
|
|
@@ -48,19 +35,10 @@ export class PlanCacheProbe {
|
|
|
48
35
|
this.metrics = metrics;
|
|
49
36
|
this.logger = logger;
|
|
50
37
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Record one finished task. `completed` = it succeeded (only those enter the history ring). Pure
|
|
53
|
-
* in-memory + a counter/histogram + a debug log line; never throws into the caller.
|
|
54
|
-
*/
|
|
55
38
|
record(scopeRaw, objective, completed) {
|
|
56
39
|
const scope = scopeRaw && scopeRaw.length > 0 ? scopeRaw : "anon";
|
|
57
40
|
const qSig = significant(objective);
|
|
58
41
|
const st = this.touch(scope);
|
|
59
|
-
// Hit = some historical (completed) task's full token set ⊇ this task's significant tokens,
|
|
60
|
-
// OR(core 5.49 compileStrategyQuery 的 legacy acceptance 臂,合并窗重扫 medium)⊇ 其拉丁/数字
|
|
61
|
-
// 子集 qAlnum(非空时)——changelog 原句 "old hits preserved via a legacy acceptance arm"。
|
|
62
|
-
// 少这条臂时「CJK 团不全匹配但拉丁标识符全匹配」的真命中被判 miss ⇒ recurrence 系统性低估。
|
|
63
|
-
// qSig empty → never a hit (mirrors find()'s `qSig.length===0 → []`, no injection).
|
|
64
42
|
let hit = false;
|
|
65
43
|
if (qSig.length > 0) {
|
|
66
44
|
const qAlnum = qSig.filter((t) => !CJK_LEAD.test(t));
|
|
@@ -84,7 +62,6 @@ export class PlanCacheProbe {
|
|
|
84
62
|
this.metrics?.observe("plan_cache_qsig", qSig.length);
|
|
85
63
|
this.logger?.debug("plan-cache recurrence probe", { scope, qSig: qSig.length, hit, completed });
|
|
86
64
|
}
|
|
87
|
-
/** Per-scope recurrence snapshot for the handoff (token-gated dump endpoint). */
|
|
88
65
|
dump() {
|
|
89
66
|
const out = {};
|
|
90
67
|
for (const [scope, st] of this.byScope) {
|
|
@@ -97,7 +74,6 @@ export class PlanCacheProbe {
|
|
|
97
74
|
}
|
|
98
75
|
return out;
|
|
99
76
|
}
|
|
100
|
-
/** LRU get-or-create: re-insert on access so the oldest-touched scope is evicted past the cap. */
|
|
101
77
|
touch(scope) {
|
|
102
78
|
let st = this.byScope.get(scope);
|
|
103
79
|
if (st) {
|