@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
package/dist/project-memory.js
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/113 C4 — `RunnerDeps.loadProjectMemory` (core 1.182): the DEPLOYMENT half that makes sema "open like CC" —
|
|
3
|
-
* inject the project's CLAUDE.md + recent git narrative as a read-only, fenced virtual memory layer at the tail of the
|
|
4
|
-
* env block, so a fresh "你好" lands on "I'm the AI for the acme-widget project…" instead of a blank greeting.
|
|
5
|
-
*
|
|
6
|
-
* core's half (1.182/1.184): it calls this once per prepare, fences+sanitizes the return as UNTRUSTED data (CLAUDE.md
|
|
7
|
-
* is data, not an instruction), caps it at MAX_MEMORY_BYTES, and (1.184) auto-prepends a TRUSTED framing line. We just
|
|
8
|
-
* return the content.
|
|
9
|
-
*
|
|
10
|
-
* 🔴 HOST/FILE LANE ONLY. The ctx hands us only `cwd` (a string) — NO executionEnv instance and NO sessionId. So we can
|
|
11
|
-
* read with the worker's own `fs`/`execFile` ONLY when cwd is a LOCAL path (the host lane). On a remote sandbox lane
|
|
12
|
-
* (e2b/k8s/ssh/adb/local-docker) cwd lives in the container — the worker can't read it, and there's no env handle in
|
|
13
|
-
* the ctx to exec through — so main.ts only wires this on the host lane; here we defensively re-check (stat the dir).
|
|
14
|
-
*/
|
|
15
1
|
import { promises as fs, constants as fsConstants } from "node:fs";
|
|
16
2
|
import { execFile } from "node:child_process";
|
|
17
3
|
import { createHmac, randomBytes } from "node:crypto";
|
|
@@ -19,78 +5,43 @@ import { isAbsolute, join, sep } from "node:path";
|
|
|
19
5
|
import { scrubSecretEnv } from "@sema-agent/core";
|
|
20
6
|
import { recordSecretEnvScrub } from "./observability/secret-env-scrub.js";
|
|
21
7
|
import { redactSecrets } from "./trace/redact.js";
|
|
22
|
-
/** The instruction-file name chain (core 1.302 S-1): AGENTS.md wins, CLAUDE.md is the fallback, then the
|
|
23
|
-
* `.claude/` nested form. The FIRST hit is the single declared instruction source. */
|
|
24
8
|
const INSTRUCTION_FILE_CHAIN = ["AGENTS.md", "CLAUDE.md", ".claude/CLAUDE.md"];
|
|
25
|
-
/** One byte budget shared by the loader AND the probe — the fingerprint is sha256 over the first
|
|
26
|
-
* `INSTRUCTION_READ_CAP` raw bytes, so both sides MUST read the same bounded window or every probe
|
|
27
|
-
* would report a phantom change. (Consequence: an edit past the cap is invisible to the probe —
|
|
28
|
-
* honest limit of the bounded-read design, same reason the injected content is capped.) */
|
|
29
9
|
const INSTRUCTION_READ_CAP = 24 * 1024;
|
|
30
|
-
/** Cap a string to a byte budget on a UTF-8 character boundary (never split a multi-byte char). */
|
|
31
10
|
function capUtf8(s, maxBytes) {
|
|
32
11
|
const buf = Buffer.from(s, "utf8");
|
|
33
12
|
if (buf.length <= maxBytes)
|
|
34
13
|
return s;
|
|
35
14
|
let end = maxBytes;
|
|
36
|
-
// back off to a UTF-8 lead byte (0xxxxxxx or 11xxxxxx) so we don't slice mid-character
|
|
37
15
|
while (end > 0 && (buf[end] & 0xc0) === 0x80)
|
|
38
16
|
end -= 1;
|
|
39
17
|
return buf.subarray(0, end).toString("utf8");
|
|
40
18
|
}
|
|
41
|
-
/** Bounded raw read of an ABSOLUTE path (lstat symlink gate → first `maxBytes` bytes). The shared primitive
|
|
42
|
-
* under both the loader and `makeProbeInstructionSources` — one read posture, one fingerprint. */
|
|
43
19
|
async function readRawCapped(target, maxBytes, truncatedFlag = { v: false }) {
|
|
44
20
|
let fh;
|
|
45
21
|
try {
|
|
46
|
-
// Symlink containment: a `CLAUDE.md` symlink must NOT read an arbitrary file outside the repo (double-review #3).
|
|
47
|
-
// 🔴 win32 真机(S6, 2026-07-04): `fsConstants.O_NOFOLLOW` is UNDEFINED on Windows Node, so `O_RDONLY | undefined`
|
|
48
|
-
// silently collapses to `O_RDONLY` (0) — the flag is a no-op and the symlink IS followed (verified: read out the
|
|
49
|
-
// outside file). O_NOFOLLOW only exists/atomically-enforces on POSIX. So gate portably with an explicit lstat
|
|
50
|
-
// symlink-reject: authoritative on win32 (where the open-flag can't enforce it), belt-and-suspenders on POSIX.
|
|
51
22
|
const lst = await fs.lstat(target);
|
|
52
23
|
if (lst.isSymbolicLink())
|
|
53
|
-
return null;
|
|
24
|
+
return null;
|
|
54
25
|
if (lst.size > maxBytes)
|
|
55
|
-
truncatedFlag.v = true;
|
|
56
|
-
// Keep O_NOFOLLOW in the flags too: on POSIX it closes the tiny lstat→open TOCTOU window atomically; on win32 it's
|
|
57
|
-
// a harmless 0. BOUNDED read of the first maxBytes bytes — never buffer the whole file, so a 50MB/hostile CLAUDE.md
|
|
58
|
-
// can't amplify memory (double-review #2: the old fs.readFile loaded the WHOLE file before capping).
|
|
26
|
+
truncatedFlag.v = true;
|
|
59
27
|
fh = await fs.open(target, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));
|
|
60
28
|
const buf = Buffer.allocUnsafe(maxBytes);
|
|
61
29
|
const { bytesRead } = await fh.read(buf, 0, maxBytes, 0);
|
|
62
30
|
return buf.subarray(0, bytesRead);
|
|
63
31
|
}
|
|
64
32
|
catch {
|
|
65
|
-
return null;
|
|
33
|
+
return null;
|
|
66
34
|
}
|
|
67
35
|
finally {
|
|
68
36
|
await fh?.close();
|
|
69
37
|
}
|
|
70
38
|
}
|
|
71
|
-
/** Keyed fingerprint over the RAW bounded-read window (pre-capUtf8 bytes — the ①"cap 前原始字节" contract).
|
|
72
|
-
* KEYED, not a plain digest (self-review, information-surface lens): core puts the short form of this
|
|
73
|
-
* fingerprint into the MODEL-VISIBLE instructions-change notice and the steering trace — a plain sha256
|
|
74
|
-
* of the raw bytes would let anyone holding a known template dictionary-compare low-entropy controlled
|
|
75
|
-
* fields against the digest. A per-process random key makes the digest correspond to nothing outside
|
|
76
|
-
* this process. Loader and probe share THIS one function (any split would fabricate phantom "changed"
|
|
77
|
-
* signals); sources are re-declared at every prepare in the declaring process, so a restart (new key)
|
|
78
|
-
* never gets compared against old fingerprints. */
|
|
79
39
|
const INSTRUCTION_HASH_KEY = randomBytes(16);
|
|
80
40
|
function instructionHash(raw) {
|
|
81
41
|
return `hmac-sha256:${createHmac("sha256", INSTRUCTION_HASH_KEY).update(raw).digest("hex")}`;
|
|
82
42
|
}
|
|
83
|
-
/** The "declared absent, watched for appearance" fingerprint for higher-priority chain members —
|
|
84
|
-
* core 1.310 ([1006]①) widened `instructionSources[].contentHash` to `string | null`, so this is a
|
|
85
|
-
* plain typed null (an appearing file renders as `hash absent → <hash>` in the change notice). */
|
|
86
43
|
const ABSENT_HASH = null;
|
|
87
|
-
/** Recover (root, rel) for a path the LOADER declared — every declared path is `root + one chain member`,
|
|
88
|
-
* so the chain suffix identifies the split unambiguously. The probe needs this to re-run the loader's
|
|
89
|
-
* parent-directory symlink gate each round (self-review: the probe checked only the final component, so
|
|
90
|
-
* swapping `.claude` for a symlink out of the repo MID-RUN made the probe fingerprint an outside file). */
|
|
91
44
|
function chainCoordinates(absPath) {
|
|
92
|
-
// LONGEST suffix wins: `…/.claude/CLAUDE.md` also ends with the bare `/CLAUDE.md` — matching in chain
|
|
93
|
-
// order would split off `.claude` into the root and skip the parent gate entirely (caught by the test).
|
|
94
45
|
let best = null;
|
|
95
46
|
for (const rel of INSTRUCTION_FILE_CHAIN) {
|
|
96
47
|
const suffix = sep + rel.split("/").join(sep);
|
|
@@ -100,11 +51,6 @@ function chainCoordinates(absPath) {
|
|
|
100
51
|
}
|
|
101
52
|
return best && { root: best.root, rel: best.rel };
|
|
102
53
|
}
|
|
103
|
-
/** Reject a symlink on ANY intermediate directory of `rel` under root (adversarial review F2): the final
|
|
104
|
-
* component's lstat/O_NOFOLLOW can't see a `.claude` DIRECTORY that is itself a symlink out of the repo
|
|
105
|
-
* (e.g. → ~/.claude) — the target file there lstat's as a plain file and would be read and injected.
|
|
106
|
-
* Same TOCTOU honesty as the final-component gate: the lstat→open window on a swapped intermediate dir
|
|
107
|
-
* is accepted (a local attacker with user rights can read the target directly anyway). */
|
|
108
54
|
async function hasSymlinkedParent(root, rel) {
|
|
109
55
|
const parts = rel.split("/").slice(0, -1);
|
|
110
56
|
let cur = root;
|
|
@@ -116,65 +62,33 @@ async function hasSymlinkedParent(root, rel) {
|
|
|
116
62
|
}
|
|
117
63
|
return false;
|
|
118
64
|
}
|
|
119
|
-
/** Run a fixed git subcommand under root with execFile (argv array → NO shell → no injection), timeout + maxBuffer.
|
|
120
|
-
* 🔴 The repo's own `.git/config` can otherwise STEER these read-only calls into code execution — double-review proved
|
|
121
|
-
* `core.fsmonitor=<prog>` runs `<prog>` on `git status` (auto-fires on every prepare in a hostile working dir). We
|
|
122
|
-
* neutralize the exec-config inlets: `-c core.fsmonitor=false -c core.hooksPath=/dev/null` override the repo's values,
|
|
123
|
-
* and GIT_CONFIG_GLOBAL/SYSTEM=/dev/null drop the global/system config so a planted one can't inject either. */
|
|
124
65
|
function git(root, args, maxBytes) {
|
|
125
66
|
const safeArgs = ["-c", "core.fsmonitor=false", "-c", "core.hooksPath=/dev/null", ...args];
|
|
126
|
-
// A-033.3(#256):带 `findings` 收集器 —— 见下方 env 行的注释,这条腿丢掉的是「仓内的钩子/凭证助手
|
|
127
|
-
// 本来会看到的」那批 key,零留痕的话保护生效与否在遥测里读不出来。收集器不改变剥除行为。
|
|
128
67
|
const scrubFindings = [];
|
|
129
68
|
const scrubbedEnv = scrubSecretEnv(process.env, scrubFindings);
|
|
130
69
|
recordSecretEnvScrub("server.project-memory.git-subprocess", scrubFindings);
|
|
131
70
|
return new Promise((res) => {
|
|
132
|
-
execFile("git", safeArgs,
|
|
133
|
-
// scrubSecretEnv: a git hook / credential helper must not see the orchestrator's *_KEY/*_TOKEN/*_SECRET env.
|
|
134
|
-
// GIT_CONFIG_GLOBAL/SYSTEM=/dev/null: ignore ~/.gitconfig + /etc/gitconfig so a planted exec-config can't steer us.
|
|
135
|
-
{ cwd: root, timeout: 5000, maxBuffer: maxBytes, env: { ...scrubbedEnv, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_SYSTEM: "/dev/null" } }, (err, stdout, stderr) => {
|
|
71
|
+
execFile("git", safeArgs, { cwd: root, timeout: 5000, maxBuffer: maxBytes, env: { ...scrubbedEnv, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_SYSTEM: "/dev/null" } }, (err, stdout, stderr) => {
|
|
136
72
|
if (!err)
|
|
137
73
|
return res({ out: stdout.trim() });
|
|
138
|
-
// S23: the git narrative used to vanish with zero fingerprint — classify why (→ gitDropReason).
|
|
139
74
|
const e = err;
|
|
140
75
|
const fail = e.killed ? "timeout" : e.code === "ENOENT" ? "git-missing" : /not a git repository/i.test(String(stderr)) ? "non-repo" : /maxBuffer/i.test(String(e.message)) ? "maxbuffer" : "error";
|
|
141
76
|
res({ out: "", fail });
|
|
142
77
|
});
|
|
143
78
|
});
|
|
144
79
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Build a `loadProjectMemory` for the host lane. Returns null (skip) unless hands are enabled AND cwd is a local
|
|
147
|
-
* directory. Reads the instruction-file chain (AGENTS.md → CLAUDE.md → .claude/CLAUDE.md, S-1) + (non-subagent)
|
|
148
|
-
* `git log -5` / `git status`, redacts secrets, and caps the total. A subagent gets a trimmed instruction file and
|
|
149
|
-
* no git narrative (cost control, H7). When an instruction file is hit the return is a `ProjectMemoryLoad`
|
|
150
|
-
* declaring it in `instructionSources` (path + sha256 over the raw bounded-read window) so core's
|
|
151
|
-
* instructions-change lane can probe it mid-run.
|
|
152
|
-
*
|
|
153
|
-
* Session snapshot cache (S-2): keyed on ctx.sessionId — a session keeps ONE stable instruction snapshot
|
|
154
|
-
* (mid-run file edits surface via the probe as tail attachments, never a silently re-rendered prefix).
|
|
155
|
-
* `phase: "post-compact"` forces a re-read (core re-renders the prefix there anyway). No entry for
|
|
156
|
-
* sessionless one-shot runs. (post-clear never arrives from core — RB-9 — so it is not a cache key here.)
|
|
157
|
-
*/
|
|
158
80
|
export function makeLoadProjectMemory(opts) {
|
|
159
|
-
const totalCap = opts.maxBytes ?? 32 * 1024;
|
|
81
|
+
const totalCap = opts.maxBytes ?? 32 * 1024;
|
|
160
82
|
const claudeCap = INSTRUCTION_READ_CAP;
|
|
161
83
|
const subagentClaudeCap = 4 * 1024;
|
|
162
84
|
const gitCap = 8 * 1024;
|
|
163
85
|
const cache = new Map();
|
|
164
86
|
const CACHE_MAX = opts.cacheMax ?? 256;
|
|
165
87
|
return async (ctx) => {
|
|
166
|
-
// All gates that decide WHETHER anything loads run BEFORE the cache (F3): a hands-less or bad-cwd call
|
|
167
|
-
// must answer null itself, never serve (or seed) a session slot it wasn't allowed to read.
|
|
168
88
|
if (!ctx.handsEnabled)
|
|
169
|
-
return null;
|
|
89
|
+
return null;
|
|
170
90
|
if (!isAbsolute(ctx.cwd))
|
|
171
91
|
return null;
|
|
172
|
-
// Canonicalize through the FILESYSTEM, not lexically (self-review, information-surface lens): a
|
|
173
|
-
// lexical resolve() keeps symlinks and case aliases distinct — a symlinked cwd re-pointed at another
|
|
174
|
-
// project would keep hitting the OLD slot and serve project A's instruction snapshot inside project B,
|
|
175
|
-
// and a case alias would silently split one project across two slots. realpath is both the cache key
|
|
176
|
-
// AND the read root, so key and content can never disagree. Unresolvable cwd (gone/unreadable) → null,
|
|
177
|
-
// the same answer doLoad's stat gate would give.
|
|
178
92
|
let root;
|
|
179
93
|
try {
|
|
180
94
|
root = await fs.realpath(ctx.cwd);
|
|
@@ -182,26 +96,17 @@ export function makeLoadProjectMemory(opts) {
|
|
|
182
96
|
catch {
|
|
183
97
|
return null;
|
|
184
98
|
}
|
|
185
|
-
// Subagents share the parent's project but get a differently-capped view — never let the two shapes
|
|
186
|
-
// poison one cache slot; the subagent leg just stays uncached (cheap, trimmed read).
|
|
187
99
|
const cacheKey = ctx.sessionId && !ctx.isSubagent ? `${ctx.sessionId}\u0000${root}` : undefined;
|
|
188
100
|
if (cacheKey) {
|
|
189
101
|
const hit = cache.get(cacheKey);
|
|
190
102
|
if (hit && ctx.phase !== "post-compact")
|
|
191
103
|
return hit.load;
|
|
192
|
-
// Post-compact single-flight (codex lens-1 remainder): two post-compact calls on one slot must share
|
|
193
|
-
// ONE fresh read — each starting its own would leave the surviving snapshot dependent on launch
|
|
194
|
-
// order. An in-flight entry that was ITSELF started post-compact is that generation's read (the
|
|
195
|
-
// settle window is milliseconds; a next compaction landing inside it joins the same read — accepted).
|
|
196
104
|
if (hit && !hit.settled && hit.phase === "post-compact")
|
|
197
105
|
return hit.load;
|
|
198
106
|
}
|
|
199
107
|
const load = doLoad(ctx, root);
|
|
200
108
|
if (cacheKey) {
|
|
201
109
|
if (cache.size >= CACHE_MAX && !cache.has(cacheKey)) {
|
|
202
|
-
// Evict the oldest SETTLED entry only (codex lens-1 remainder): deleting an in-flight promise
|
|
203
|
-
// orphans its read (the key's next call starts a second one). No settled entry → allow a
|
|
204
|
-
// temporary over-cap; loads settle in milliseconds, so the map stays bounded in practice.
|
|
205
110
|
for (const [k, e] of cache) {
|
|
206
111
|
if (e.settled) {
|
|
207
112
|
cache.delete(k);
|
|
@@ -211,10 +116,6 @@ export function makeLoadProjectMemory(opts) {
|
|
|
211
116
|
}
|
|
212
117
|
const entry = { load, settled: false, phase: ctx.phase };
|
|
213
118
|
cache.set(cacheKey, entry);
|
|
214
|
-
// Release-review finding: a burst of >CACHE_MAX concurrent DISTINCT loads sails past the insert-time
|
|
215
|
-
// eviction (nothing settled to evict yet) and, without this, the map would stay at the burst's
|
|
216
|
-
// high-water mark forever (each later insert evicts exactly one settled entry, net zero). Trim back
|
|
217
|
-
// down to the cap as loads settle — oldest settled first, in-flight entries always survive.
|
|
218
119
|
const trimSettled = () => {
|
|
219
120
|
if (cache.size <= CACHE_MAX)
|
|
220
121
|
return;
|
|
@@ -231,9 +132,6 @@ export function makeLoadProjectMemory(opts) {
|
|
|
231
132
|
trimSettled();
|
|
232
133
|
}, () => {
|
|
233
134
|
entry.settled = true;
|
|
234
|
-
// A rejected load must not poison the slot (doLoad catches everything today; belt for future
|
|
235
|
-
// edits). Guard the delete on identity (codex lens-1): an OLD generation's failure must not
|
|
236
|
-
// evict the NEWER entry that a post-compact refresh has already installed in the same slot.
|
|
237
135
|
if (cache.get(cacheKey) === entry)
|
|
238
136
|
cache.delete(cacheKey);
|
|
239
137
|
trimSettled();
|
|
@@ -244,12 +142,7 @@ export function makeLoadProjectMemory(opts) {
|
|
|
244
142
|
async function doLoad(ctx, root) {
|
|
245
143
|
const st = await fs.stat(root).catch(() => null);
|
|
246
144
|
if (!st?.isDirectory())
|
|
247
|
-
return null;
|
|
248
|
-
// S-1 file-name chain: first hit wins and is the ONE declared instruction source.
|
|
249
|
-
// Consistent-snapshot selection (codex lens-1 remainder): the chain walk is not atomic — a
|
|
250
|
-
// higher-priority file created between "checked absent" and "picked the fallback" would silently
|
|
251
|
-
// lose to the fallback until a post-compact reload. After a non-top pick, re-verify the members
|
|
252
|
-
// above it; an appearance redoes the walk (bounded — after the retries the last pick stands).
|
|
145
|
+
return null;
|
|
253
146
|
const claudeTrunc = { v: false };
|
|
254
147
|
let claudeRaw = null;
|
|
255
148
|
let hitFile;
|
|
@@ -264,7 +157,7 @@ export function makeLoadProjectMemory(opts) {
|
|
|
264
157
|
for (let i = 0; i < INSTRUCTION_FILE_CHAIN.length; i++) {
|
|
265
158
|
const rel = INSTRUCTION_FILE_CHAIN[i];
|
|
266
159
|
if (rel.includes("/") && (await hasSymlinkedParent(root, rel)))
|
|
267
|
-
continue;
|
|
160
|
+
continue;
|
|
268
161
|
const raw = await readRawCapped(join(root, rel), claudeCap, claudeTrunc);
|
|
269
162
|
if (raw !== null) {
|
|
270
163
|
claudeRaw = capUtf8(raw.toString("utf8"), claudeCap);
|
|
@@ -275,11 +168,10 @@ export function makeLoadProjectMemory(opts) {
|
|
|
275
168
|
}
|
|
276
169
|
}
|
|
277
170
|
if (hitIndex <= 0)
|
|
278
|
-
break;
|
|
171
|
+
break;
|
|
279
172
|
let appeared = false;
|
|
280
173
|
for (const rel of INSTRUCTION_FILE_CHAIN.slice(0, hitIndex)) {
|
|
281
174
|
const higher = await fs.lstat(join(root, rel)).catch(() => null);
|
|
282
|
-
// A symlink would be REFUSED by the read gate — its appearance is not a better pick.
|
|
283
175
|
if (higher && !higher.isSymbolicLink()) {
|
|
284
176
|
appeared = true;
|
|
285
177
|
break;
|
|
@@ -289,9 +181,9 @@ export function makeLoadProjectMemory(opts) {
|
|
|
289
181
|
break;
|
|
290
182
|
}
|
|
291
183
|
const claude = ctx.isSubagent && claudeRaw ? capUtf8(claudeRaw, subagentClaudeCap) : claudeRaw;
|
|
292
|
-
const claudeTruncated = claudeTrunc.v || Boolean(ctx.isSubagent && claudeRaw && Buffer.byteLength(claudeRaw, "utf8") > subagentClaudeCap);
|
|
184
|
+
const claudeTruncated = claudeTrunc.v || Boolean(ctx.isSubagent && claudeRaw && Buffer.byteLength(claudeRaw, "utf8") > subagentClaudeCap);
|
|
293
185
|
let gitNarrative = "";
|
|
294
|
-
let gitOk = true;
|
|
186
|
+
let gitOk = true;
|
|
295
187
|
let gitDropReason;
|
|
296
188
|
if (!ctx.isSubagent) {
|
|
297
189
|
const [log, status] = await Promise.all([
|
|
@@ -304,33 +196,22 @@ export function makeLoadProjectMemory(opts) {
|
|
|
304
196
|
if (log.out)
|
|
305
197
|
parts.push(`## Recent commits\n${log.out}`);
|
|
306
198
|
if (status.out)
|
|
307
|
-
parts.push(`## Working tree\n${status.out.split("\n").slice(0, 40).join("\n")}`);
|
|
199
|
+
parts.push(`## Working tree\n${status.out.split("\n").slice(0, 40).join("\n")}`);
|
|
308
200
|
gitNarrative = parts.join("\n\n");
|
|
309
201
|
}
|
|
310
202
|
const sections = [];
|
|
311
203
|
if (claude && hitFile)
|
|
312
|
-
sections.push(`# ${hitFile}\n${claude}`);
|
|
204
|
+
sections.push(`# ${hitFile}\n${claude}`);
|
|
313
205
|
if (gitNarrative)
|
|
314
206
|
sections.push(gitNarrative);
|
|
315
207
|
if (sections.length === 0)
|
|
316
208
|
return null;
|
|
317
|
-
// redact secrets BEFORE capping (a key must not survive into the injected context, where a tool could exfil it).
|
|
318
209
|
const redacted = redactSecrets(sections.join("\n\n"));
|
|
319
210
|
const capped = capUtf8(redacted, totalCap);
|
|
320
|
-
// S23: gitOk/gitDropReason/claudeTruncated — a silently-emptied narrative now has a fingerprint here.
|
|
321
|
-
// cacheSize: the snapshot cache's population at THIS load (bound observability — the settle-time trim
|
|
322
|
-
// keeps it ≤ the cap once in-flight bursts settle; also what the deterministic trim test asserts).
|
|
323
211
|
opts.logger?.debug?.("project_memory_loaded", { cwd: root, bytes: Buffer.byteLength(capped, "utf8"), isSubagent: ctx.isSubagent, gitOk, ...(gitDropReason ? { gitDropReason } : {}), claudeTruncated, ...(hitFile ? { instructionFile: hitFile } : {}), ...(ctx.phase ? { phase: ctx.phase } : {}), cacheSize: cache.size });
|
|
324
212
|
if (!capped)
|
|
325
213
|
return null;
|
|
326
|
-
// Declare the instruction source (①): absolute path + fingerprint. The hash is over the PRE-redaction,
|
|
327
|
-
// pre-cap raw window — the probe re-reads the file itself, so the fingerprint must be file-derived, not
|
|
328
|
-
// derived from the redacted/assembled injection text.
|
|
329
214
|
if (hitFile && hitHash) {
|
|
330
|
-
// Watch the chain members ABOVE the pick too (codex lens-1 remainder): core's change lane only
|
|
331
|
-
// probes DECLARED paths, so a higher-priority file appearing mid-run would otherwise never produce
|
|
332
|
-
// a signal until a post-compact reload. Absent members are pre-declared with the runtime's
|
|
333
|
-
// "absent" hash (ABSENT_HASH note) — appearance renders as `hash absent → <hash>` in the notice.
|
|
334
215
|
return {
|
|
335
216
|
content: capped,
|
|
336
217
|
instructionSources: [
|
|
@@ -342,27 +223,15 @@ export function makeLoadProjectMemory(opts) {
|
|
|
342
223
|
return capped;
|
|
343
224
|
}
|
|
344
225
|
}
|
|
345
|
-
/**
|
|
346
|
-
* ② The `RunnerDeps.probeInstructionSources` seam: re-fingerprint the declared instruction files mid-run so
|
|
347
|
-
* core's instructions-change lane can append a tail attachment when one changes. Same read posture as the
|
|
348
|
-
* loader (lstat symlink gate → bounded read of the first `INSTRUCTION_READ_CAP` bytes → sha256) — any drift
|
|
349
|
-
* here would fabricate phantom "changed" signals every turn. Per-source failure → `contentHash: null`
|
|
350
|
-
* (source gone/unreadable); any unexpected top-level failure → `null` (probe abstains; core also races this
|
|
351
|
-
* against a 2s ceiling and treats a throw as null, but we stay honest on our side of the seam).
|
|
352
|
-
*/
|
|
353
226
|
export function makeProbeInstructionSources() {
|
|
354
227
|
return async (sources) => {
|
|
355
228
|
try {
|
|
356
229
|
return await Promise.all(sources.map(async ({ path }) => {
|
|
357
230
|
if (!isAbsolute(path))
|
|
358
|
-
return { path, contentHash: null };
|
|
359
|
-
// Re-run the loader's parent-directory symlink gate EVERY round (self-review: the probe checked
|
|
360
|
-
// only the final component, so swapping `.claude` for a symlink out of the repo mid-run made the
|
|
361
|
-
// probe fingerprint an outside file — and its short form reaches the model-visible notice).
|
|
362
|
-
// Declared paths are always root+chain-member, so the coordinates are recoverable from the path.
|
|
231
|
+
return { path, contentHash: null };
|
|
363
232
|
const coords = chainCoordinates(path);
|
|
364
233
|
if (coords && coords.rel.includes("/") && (await hasSymlinkedParent(coords.root, coords.rel))) {
|
|
365
|
-
return { path, contentHash: null };
|
|
234
|
+
return { path, contentHash: null };
|
|
366
235
|
}
|
|
367
236
|
const raw = await readRawCapped(path, INSTRUCTION_READ_CAP);
|
|
368
237
|
return { path, contentHash: raw === null ? null : instructionHash(raw) };
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* prompts 域候选门(判形)——从 capabilities/center-prompts.ts **下沉**的中立叶子(design/158 S6,
|
|
3
|
-
* lens2 §G:`config-lkg.ts → capabilities/center-prompts.ts` 是全仓唯一一条显式分层反向边,config 层
|
|
4
|
-
* 值 import capabilities 层)。本模块只依赖 node 内置 + `@sema-agent/core`,不依赖 capabilities/
|
|
5
|
-
* config 任何东西,config-lkg(LKG 读侧复判)与 capabilities(候选门)同引即无反向边。
|
|
6
|
-
* 消费者直引本模块([2354] 兼容面全清:center-prompts.ts 的原位兼容 re-export 已删);语义/文案零改动——逐字搬运。
|
|
7
|
-
*
|
|
8
|
-
* ─── 以下为原 center-prompts.ts 的判形半场,注释原样保留 ───
|
|
9
|
-
*
|
|
10
|
-
* 本模块仅有的一层「形校验」不是语义复检,是**防炸底线**:core 对坏形声明的守卫是 THROW(assemble 期),
|
|
11
|
-
* 一个坏 pack 会让每个任务 prepare 炸掉——server 在采用前做结构判形,坏形=拒绝采用该候选的 prompts 面
|
|
12
|
-
* (warn 点名,沿用上一个好的/内置),任务面永不因配置坏形而全灭(config 候选门同精神,只作用于本域)。
|
|
13
|
-
*/
|
|
14
1
|
import { createHash } from "node:crypto";
|
|
15
2
|
import { createRequire } from "node:module";
|
|
16
3
|
import { readFileSync } from "node:fs";
|
|
17
4
|
import { dirname, join } from "node:path";
|
|
18
5
|
import { verifyPromptArtifact } from "@sema-agent/core";
|
|
19
6
|
import { SHA256_DIGEST_RE } from "./digest-form.js";
|
|
20
|
-
const ID_RE = /^[a-z0-9][a-z0-9/._-]*$/;
|
|
7
|
+
const ID_RE = /^[a-z0-9][a-z0-9/._-]*$/;
|
|
21
8
|
const SLOTS = new Set(["identity", "scenario", "behavior"]);
|
|
22
9
|
function declarationIssue(d, where) {
|
|
23
10
|
if (d === null || typeof d !== "object")
|
|
@@ -31,10 +18,6 @@ function declarationIssue(d, where) {
|
|
|
31
18
|
return `${where}: bad slot`;
|
|
32
19
|
if (typeof o.text !== "string" || o.text.length === 0)
|
|
33
20
|
return `${where}: text must be a non-empty string`;
|
|
34
|
-
// 发版复审(1.224 批)F1:contentHash **必备且重算比对**——core 的 epoch 声明轴用 (id,slot,chars,
|
|
35
|
-
// contentHash?),hash 缺席/被 core 丢弃(错值)后**同长度文本编辑不改 artifactDigest=不重 pin**,
|
|
36
|
-
// 恰是撤 restart 臂所依赖的审计不变量被击穿。center 链路恒逐 section 铸([1065]),正常路径零影响;
|
|
37
|
-
// 这里拒的是坏发布/被改载荷(整拒候选,LKG/内置继续服务)。
|
|
38
21
|
if (typeof o.contentHash !== "string" || !SHA256_DIGEST_RE.test(o.contentHash))
|
|
39
22
|
return `${where}: contentHash is required (sha256:<64hex> — the epoch declaration axis needs it for byte-level change detection)`;
|
|
40
23
|
const expected = `sha256:${createHash("sha256").update(o.text).digest("hex")}`;
|
|
@@ -42,11 +25,6 @@ function declarationIssue(d, where) {
|
|
|
42
25
|
return `${where}: contentHash does not match sha256(text) — stale or tampered declaration`;
|
|
43
26
|
return undefined;
|
|
44
27
|
}
|
|
45
|
-
/**
|
|
46
|
-
* 结构判形(防炸底线,非语义复检)。`ok:false` 时调用方拒绝采用该候选的 prompts 面并 warn——绝不让
|
|
47
|
-
* 一个坏 pack 把每个任务的 prepare 炸掉。校验与 core assemble 的 THROW 条件同形(id 规范/长度/core/
|
|
48
|
-
* 保留、slot 闭集)+ 下发信封两键形;`contentHash` 只查形(值的校验归 core 1.315,错值 core 丢弃+warn)。
|
|
49
|
-
*/
|
|
50
28
|
export function validateCenterPrompts(raw) {
|
|
51
29
|
if (raw === null || typeof raw !== "object")
|
|
52
30
|
return { ok: false, error: "prompts is not an object" };
|
|
@@ -55,11 +33,6 @@ export function validateCenterPrompts(raw) {
|
|
|
55
33
|
return { ok: false, error: "bad packId form (center:<digest12>)" };
|
|
56
34
|
if (typeof p.contentDigest !== "string" || !SHA256_DIGEST_RE.test(p.contentDigest))
|
|
57
35
|
return { ok: false, error: "bad contentDigest form" };
|
|
58
|
-
// 发版复审 F2/F4 两条硬化:
|
|
59
|
-
// - 数组内 id 必须唯一(core composer 对 duplicate section id 是 THROW——独立判形查不出对撞,必须
|
|
60
|
-
// 整数组查重;基线与各 override 互不查——override 整包替换,不同数组不共存于一次装配);
|
|
61
|
-
// - 空数组=坏形拒绝(F4:wire 契约是「整包替换终形」,空数组语义未定义——「显式清空场景」不是 v1
|
|
62
|
-
// 用例,静默把空重解释为「回落内置」会跑出与 center 发布物不同的提示词;语义决定权推回 center)。
|
|
63
36
|
const arrayIssue = (decls, where) => {
|
|
64
37
|
if (!Array.isArray(decls))
|
|
65
38
|
return `${where} is not an array`;
|
|
@@ -92,10 +65,6 @@ export function validateCenterPrompts(raw) {
|
|
|
92
65
|
}
|
|
93
66
|
return { ok: true, value: raw };
|
|
94
67
|
}
|
|
95
|
-
// ═══ 双轴门(a 实现班车,[985]a/[1242]/[1248] 过轮定稿;registry-core 0.10.20 同班车)═══
|
|
96
|
-
/** core 引擎版本(verifyPromptArtifact 的 compatibleCore range 输入)——经 exports map 解析包根读
|
|
97
|
-
* package.json(`require('@sema-agent/core/package.json')` 被 exports 封死,ERR_PACKAGE_PATH_NOT_EXPORTED
|
|
98
|
-
* 第一手教训)。解析失败回 "0.0.0" = range-gated artifact 被诚实拒(fail-closed 响,绝不静默采用)。 */
|
|
99
68
|
export const CORE_ENGINE_VERSION = (() => {
|
|
100
69
|
try {
|
|
101
70
|
const entry = createRequire(import.meta.url).resolve("@sema-agent/core");
|
|
@@ -105,21 +74,11 @@ export const CORE_ENGINE_VERSION = (() => {
|
|
|
105
74
|
return "0.0.0";
|
|
106
75
|
}
|
|
107
76
|
})();
|
|
108
|
-
/**
|
|
109
|
-
* prompts 域候选门 v2(validateCenterPrompts 的双轴升级;整拒语义不变=任一在场轴坏形 ⇒ 整个 prompts
|
|
110
|
-
* 面拒绝采用,LKG/内置继续服务)。唯一认可形=`{schemaVersion:1, declarations?, catalog?}`
|
|
111
|
-
* (registry-core `EffectivePromptsWire`)至少一轴。[2373]B-9(5.0.0):legacy 平铺兼容窗到期删——
|
|
112
|
-
* 0.13.0 起 EffectivePromptsWire 首字段是 `schemaVersion: z.ZodLiteral<1>`,与 server 共用 ^0.13.0 的
|
|
113
|
-
* 合规 center 不可能再发平铺;非合规体=响亮拒绝(整拒落 LKG/内置),不再静默吸收。
|
|
114
|
-
* catalog 轴的语义权威=core `verifyPromptArtifact`(形/digest 逐位复算/ARTIFACT_LIMITS/compatibleCore
|
|
115
|
-
* range 全在 core 一处——[1242]③ a 解,上限单真源永不漂)。
|
|
116
|
-
*/
|
|
117
77
|
export function validatePromptsDomain(raw) {
|
|
118
78
|
if (raw === null || typeof raw !== "object")
|
|
119
79
|
return { ok: false, error: "prompts is not an object" };
|
|
120
80
|
const w = raw;
|
|
121
81
|
if (w.schemaVersion === undefined) {
|
|
122
|
-
// [2373]B-9:平铺形拒绝(兼容窗 5.0.0 到期)。文案带指引=下游冒烟立刻可自修,不是哑谜。
|
|
123
82
|
return { ok: false, error: "prompts payload has no schemaVersion — the legacy flat shape was retired in 5.0.0; wrap as {schemaVersion:1, declarations:{...}} (registry-core EffectivePromptsWire)" };
|
|
124
83
|
}
|
|
125
84
|
if (w.schemaVersion !== 1)
|
|
@@ -138,21 +97,12 @@ export function validatePromptsDomain(raw) {
|
|
|
138
97
|
const c = verifyPromptArtifact(w.catalog, { engineVersion: CORE_ENGINE_VERSION });
|
|
139
98
|
if (!c.ok)
|
|
140
99
|
return { ok: false, error: `catalog axis: ${c.code}: ${c.reason}` };
|
|
141
|
-
// verify 返回的是重建后的 typed artifact;wire 上的 envelope 原样保留(sourceRevision 不进 digest,
|
|
142
|
-
// e/b 接线时 core artifact 仓吃 envelope 全形)。
|
|
143
100
|
value.catalog = w.catalog;
|
|
144
101
|
value.axes.push("catalog");
|
|
145
102
|
}
|
|
146
103
|
value.identity = `${value.declarations?.contentDigest ?? "-"}+${value.catalog?.artifact.artifactDigest ?? "-"}`;
|
|
147
104
|
return { ok: true, value };
|
|
148
105
|
}
|
|
149
|
-
/** #100(docs/R100-PROMPTS-LANE-FIX.md §V2-B)worker 腿 effective 上 `prompts` 键的**形-肯定**判别:
|
|
150
|
-
* 消费形特征(v2 `schemaVersion` 键,或 legacy 三键 packId/contentDigest/sections)⇒ 原样返回交
|
|
151
|
-
* {@link validatePromptsDomain}(收或拒,方向不变);其余=center 存储原文(global lane [1057]①e 下发
|
|
152
|
-
* 存储形是合法输入)——catalog 在场则投影 `{schemaVersion:1, catalog}`(config-provider 本地腿先例),
|
|
153
|
-
* 否则视为「center 未发」⇒ undefined。null=有意 cleared 语义(#100 前 null 走拒,方向为宽是设计变更,
|
|
154
|
-
* 留痕于设计档)。垃圾标量原样返回(fail-loud 归 validate)。
|
|
155
|
-
* 属主放本中立叶:config-lkg 与 boot/refresh 同吃,capabilities 层会重开 S6 刚消的反向依赖边。 */
|
|
156
106
|
export function centerPromptsFromEffective(eff) {
|
|
157
107
|
const prompts = eff?.prompts;
|
|
158
108
|
if (prompts === undefined || prompts === null)
|