@sema-agent/server 7.137.0 → 7.139.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/MIGRATION.md +600 -142
- package/README.md +7 -3
- package/README.zh-CN.md +6 -3
- package/USAGE.md +132 -41
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/lib/lanes.json +3 -2
- package/dist/adoption/wire.d.ts +3 -3
- package/dist/approval-card.d.ts +23 -16
- package/dist/approval-card.js +1 -0
- package/dist/audit.js +1 -0
- package/dist/background-agent-name.d.ts +5 -0
- package/dist/background-agent-name.js +5 -0
- package/dist/bench/s1/arms.d.ts +1 -1
- package/dist/bench/s1/arms.js +1 -3
- package/dist/bench/s1/run-firm.js +2 -2
- package/dist/boot/budget-tracing.d.ts +1 -12
- package/dist/boot/deployment-mods.d.ts +46 -16
- package/dist/boot/deployment-mods.js +82 -6
- package/dist/boot/device-lane.d.ts +8 -3
- package/dist/boot/device-lane.js +7 -2
- package/dist/boot/execution-env.d.ts +4 -2
- package/dist/boot/execution-env.js +16 -24
- package/dist/boot/execution-lane.d.ts +1 -1
- package/dist/boot/governance-seams.d.ts +12 -18
- package/dist/boot/governance-seams.js +10 -24
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +1 -0
- package/dist/boot/memory-consolidation.js +2 -2
- package/dist/boot/org-delivery.js +15 -2
- package/dist/boot/resolve-spec.d.ts +4 -1
- package/dist/boot/resolve-spec.js +29 -19
- package/dist/boot/retention-lane.d.ts +14 -8
- package/dist/boot/retention-lane.js +2 -8
- package/dist/boot/runner-deps.d.ts +10 -4
- package/dist/boot/runner-deps.js +2 -0
- package/dist/boot/session-faces.d.ts +0 -3
- package/dist/boot/session-faces.js +15 -8
- package/dist/boot/stage-02-web-admin-pull.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -12
- package/dist/boot/stage-05-execution-env.d.ts +3 -12
- package/dist/boot/stage-05-execution-env.js +8 -5
- package/dist/boot/stage-06-runners.d.ts +3 -12
- package/dist/boot/stage-06-runners.js +5 -3
- package/dist/boot/stage-07-capability-layer.d.ts +3 -12
- package/dist/boot/stage-07-capability-layer.js +2 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -12
- package/dist/boot/stage-09-leader.d.ts +3 -12
- package/dist/boot/stage-09-leader.js +2 -3
- package/dist/boot/stage-10-http-server.d.ts +3 -12
- package/dist/boot/stage-10-http-server.js +5 -3
- package/dist/boot/stores.js +4 -4
- package/dist/boot/web-admin.d.ts +5 -2
- package/dist/boot/web-admin.js +5 -5
- package/dist/brain.js +5 -5
- package/dist/budget.d.ts +17 -15
- package/dist/budget.js +2 -2
- package/dist/capabilities/mcp-probe.d.ts +36 -11
- package/dist/capabilities/mcp-probe.js +44 -5
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +21 -7
- package/dist/capability-facts.js +13 -3
- package/dist/checkpoint-identity.d.ts +13 -6
- package/dist/config-catalog.js +14 -8
- package/dist/config-invariants.d.ts +23 -3
- package/dist/config-invariants.js +42 -0
- package/dist/config-provider.d.ts +19 -24
- package/dist/config-provider.js +27 -12
- package/dist/config-types.d.ts +25 -12
- package/dist/config.d.ts +33 -0
- package/dist/config.js +52 -20
- package/dist/delegated-session-owner.d.ts +2 -2
- package/dist/deployment-shape.d.ts +7 -7
- package/dist/device-enrollment.d.ts +41 -2
- package/dist/device-enrollment.js +27 -12
- package/dist/device-store.d.ts +58 -85
- package/dist/device-store.js +19 -49
- package/dist/device-ws-hub.js +2 -1
- package/dist/env-name-allowlist-knobs.d.ts +5 -4
- package/dist/exec-cut.d.ts +60 -0
- package/dist/exec-cut.js +36 -0
- package/dist/execution-lane-caps.d.ts +19 -0
- package/dist/execution-lane-caps.js +10 -0
- package/dist/execution-placement.d.ts +259 -0
- package/dist/execution-placement.js +199 -0
- package/dist/field-conflict.d.ts +3 -2
- package/dist/field-conflict.js +1 -0
- package/dist/gate-settlement.d.ts +71 -0
- package/dist/gate-settlement.js +40 -0
- package/dist/hooks/classic-stations.d.ts +1 -0
- package/dist/hooks/classic-stations.js +1 -0
- package/dist/hooks/command-spawn.d.ts +2 -2
- package/dist/hooks/hook-runner.js +14 -14
- package/dist/hosted-posture.d.ts +2 -2
- package/dist/http/admission.js +10 -19
- package/dist/http/checkpoint-ctx.d.ts +21 -12
- package/dist/http/checkpoint-ctx.js +4 -2
- package/dist/http/claimed-run.d.ts +1 -1
- package/dist/http/dispatch.d.ts +4 -0
- package/dist/http/dispatch.js +3 -0
- package/dist/http/durable-run-leg.js +12 -6
- package/dist/http/execution-context.d.ts +218 -0
- package/dist/http/execution-context.js +330 -0
- package/dist/http/memory-compliance-failure-reply.js +3 -0
- package/dist/http/resume-legs.d.ts +5 -11
- package/dist/http/resume-legs.js +179 -104
- package/dist/http/route-ctx.d.ts +44 -59
- package/dist/http/route-ctx.js +2 -5
- package/dist/http/routes/a2a-serve.js +10 -4
- package/dist/http/routes/agents-roster.d.ts +1 -1
- package/dist/http/routes/agents-roster.js +5 -1
- package/dist/http/routes/approvals-assistant.js +6 -6
- package/dist/http/routes/background.d.ts +29 -1
- package/dist/http/routes/background.js +29 -2
- package/dist/http/routes/capabilities.js +12 -10
- package/dist/http/routes/devices.d.ts +13 -19
- package/dist/http/routes/devices.js +21 -26
- package/dist/http/routes/mcp-reconnect.d.ts +3 -2
- package/dist/http/routes/mcp-reconnect.js +7 -5
- package/dist/http/routes/memory-consolidation.d.ts +1 -1
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +3 -3
- package/dist/http/routes/runs.js +11 -7
- package/dist/http/routes/session-sync.js +110 -15
- package/dist/http/routes/sessions.d.ts +6 -0
- package/dist/http/routes/sessions.js +119 -13
- package/dist/http/routes/side-query.d.ts +12 -4
- package/dist/http/routes/side-query.js +6 -8
- package/dist/http/routes/tasks.js +800 -782
- package/dist/http/server.d.ts +61 -39
- package/dist/http/server.js +11 -0
- package/dist/http/session-inherit-env.d.ts +3 -4
- package/dist/http/session-inherit-env.js +1 -1
- package/dist/http/wire-types.d.ts +22 -18
- package/dist/identity-source.d.ts +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +7 -0
- package/dist/leader/wire.js +5 -4
- package/dist/mcp-assertion-header.d.ts +51 -40
- package/dist/mcp-assertion-header.js +10 -11
- package/dist/mcp-authorization-face.d.ts +27 -0
- package/dist/mcp-authorization-face.js +15 -0
- package/dist/memory-operator-faces.d.ts +13 -4
- package/dist/memory-operator-faces.js +1 -17
- package/dist/memory-sync-client.d.ts +53 -14
- package/dist/memory-sync-client.js +187 -92
- package/dist/memory-sync.d.ts +1 -1
- package/dist/memory-sync.js +3 -2
- package/dist/model-route-gap.d.ts +6 -22
- package/dist/model-route-gap.js +7 -5
- package/dist/mods-declaration.d.ts +110 -0
- package/dist/mods-declaration.js +109 -0
- package/dist/observability/cost-taxonomy.d.ts +2 -2
- package/dist/observability/err-text.d.ts +5 -0
- package/dist/observability/err-text.js +9 -0
- package/dist/observability/fail-open.d.ts +17 -1
- package/dist/observability/fail-open.js +17 -1
- package/dist/observability/run-terminal.js +0 -1
- package/dist/observability/secret-env-scrub.d.ts +1 -1
- package/dist/observability/secret-env-scrub.js +1 -1
- package/dist/orchestration/workflow-notify-journal.js +4 -4
- package/dist/org-memory-admission.d.ts +4 -3
- package/dist/org-memory-admission.js +6 -6
- package/dist/parked-decide.d.ts +49 -11
- package/dist/parked-decide.js +25 -26
- package/dist/parked-revive-surface.d.ts +12 -15
- package/dist/parked-revive-surface.js +3 -6
- package/dist/peer-seat.d.ts +156 -0
- package/dist/peer-seat.js +273 -0
- package/dist/permission-rule-vocab.d.ts +7 -7
- package/dist/permission-rule-vocab.js +3 -3
- package/dist/plain-string-record.d.ts +38 -0
- package/dist/plain-string-record.js +38 -0
- package/dist/plugins/approval-ask-store-file.d.ts +7 -0
- package/dist/plugins/approval-ask-store-file.js +11 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
- package/dist/plugins/approval-ask-store-memory.js +12 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
- package/dist/plugins/approval-ask-store-sql.js +30 -12
- package/dist/plugins/background-agent-store-sql.js +6 -3
- package/dist/plugins/caching-session-store.d.ts +11 -2
- package/dist/plugins/caching-session-store.js +15 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
- package/dist/plugins/checkpoint-store-sql.js +13 -15
- package/dist/plugins/device-store-sql.d.ts +25 -51
- package/dist/plugins/device-store-sql.js +49 -103
- package/dist/plugins/execution-placement-sql.d.ts +151 -0
- package/dist/plugins/execution-placement-sql.js +223 -0
- package/dist/plugins/file-history-store-sql.d.ts +7 -6
- package/dist/plugins/file-history-store-sql.js +8 -7
- package/dist/plugins/file-run-store.d.ts +6 -1
- package/dist/plugins/file-run-store.js +4 -0
- package/dist/plugins/fork-routing-session-store.d.ts +6 -0
- package/dist/plugins/fork-routing-session-store.js +16 -0
- package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
- package/dist/plugins/k8s-pod-reaper.js +3 -3
- package/dist/plugins/local-checkpoint-store.d.ts +1 -11
- package/dist/plugins/local-checkpoint-store.js +0 -1
- package/dist/plugins/local-session-store.d.ts +183 -45
- package/dist/plugins/local-session-store.js +412 -105
- package/dist/plugins/mailbox-store-sql.d.ts +11 -3
- package/dist/plugins/mailbox-store-sql.js +103 -15
- package/dist/plugins/memory-key-guards.d.ts +4 -0
- package/dist/plugins/memory-key-guards.js +1 -0
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
- package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
- package/dist/plugins/one-time-migrations.d.ts +117 -6
- package/dist/plugins/one-time-migrations.js +383 -14
- package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
- package/dist/plugins/permission-rule-store-sql.js +34 -29
- package/dist/plugins/pg-pool.d.ts +7 -3
- package/dist/plugins/pg-session-storage.d.ts +41 -22
- package/dist/plugins/pg-session-storage.js +136 -55
- package/dist/plugins/remote-env-adb.d.ts +22 -4
- package/dist/plugins/remote-env-adb.js +70 -62
- package/dist/plugins/remote-env-device.d.ts +32 -12
- package/dist/plugins/remote-env-device.js +18 -17
- package/dist/plugins/remote-env-e2b.d.ts +19 -3
- package/dist/plugins/remote-env-e2b.js +41 -42
- package/dist/plugins/remote-env-file-error.d.ts +2 -0
- package/dist/plugins/remote-env-file-error.js +1 -1
- package/dist/plugins/remote-env-host.d.ts +23 -8
- package/dist/plugins/remote-env-host.js +66 -70
- package/dist/plugins/remote-env-k8s.d.ts +13 -16
- package/dist/plugins/remote-env-k8s.js +61 -54
- package/dist/plugins/remote-env-local-docker.d.ts +30 -5
- package/dist/plugins/remote-env-local-docker.js +46 -59
- package/dist/plugins/remote-env-ssh.d.ts +18 -4
- package/dist/plugins/remote-env-ssh.js +72 -56
- package/dist/plugins/remote-shell.d.ts +100 -3
- package/dist/plugins/remote-shell.js +166 -2
- package/dist/plugins/retention-store-sql.d.ts +23 -8
- package/dist/plugins/retention-store-sql.js +13 -1
- package/dist/plugins/run-store-sql.d.ts +13 -3
- package/dist/plugins/run-store-sql.js +5 -0
- package/dist/plugins/schema-consistency.d.ts +15 -5
- package/dist/plugins/schema-consistency.js +14 -8
- package/dist/plugins/schema-drift-sentence.d.ts +27 -0
- package/dist/plugins/schema-drift-sentence.js +38 -0
- package/dist/plugins/session-fork.d.ts +63 -12
- package/dist/plugins/session-fork.js +38 -21
- package/dist/plugins/session-ownership.d.ts +7 -0
- package/dist/plugins/session-ownership.js +5 -0
- package/dist/plugins/session-row-sql.d.ts +22 -0
- package/dist/plugins/session-row-sql.js +81 -0
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/session-workspace-cell.d.ts +17 -0
- package/dist/plugins/session-workspace-cell.js +38 -0
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-backend.js +26 -14
- package/dist/plugins/store-contracts.d.ts +25 -5
- package/dist/plugins/tidb-pool.d.ts +12 -7
- package/dist/plugins/tidb-pool.js +15 -3
- package/dist/plugins/tidb-session-store.d.ts +74 -28
- package/dist/plugins/tidb-session-store.js +132 -53
- package/dist/plugins/tool-result-store-sql.d.ts +16 -8
- package/dist/plugins/tool-result-store-sql.js +26 -17
- package/dist/plugins/worktree-isolation.d.ts +3 -2
- package/dist/plugins/worktree-isolation.js +1 -7
- package/dist/project-identity.d.ts +1 -14
- package/dist/project-identity.js +0 -4
- package/dist/project-memory.d.ts +9 -15
- package/dist/project-memory.js +5 -4
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +6 -7
- package/dist/request-value-closure.js +1 -3
- package/dist/rules-consent.js +6 -4
- package/dist/run-local.d.ts +6 -0
- package/dist/run-local.js +36 -26
- package/dist/runs.d.ts +9 -2
- package/dist/runs.js +9 -2
- package/dist/sandbox-git-plane.d.ts +2 -2
- package/dist/security.d.ts +38 -12
- package/dist/security.js +8 -2
- package/dist/server-secret-env.d.ts +15 -8
- package/dist/server-secret-env.js +4 -2
- package/dist/session-children-retire.d.ts +27 -9
- package/dist/session-children-retire.js +9 -5
- package/dist/session-incarnation.d.ts +19 -10
- package/dist/session-incarnation.js +5 -3
- package/dist/session-row.d.ts +114 -5
- package/dist/session-row.js +55 -5
- package/dist/session-titler.d.ts +2 -2
- package/dist/session-workspace.d.ts +130 -0
- package/dist/session-workspace.js +83 -0
- package/dist/spec-fields.d.ts +1 -1
- package/dist/standing-answer-grant.d.ts +2 -13
- package/dist/standing-answer-grant.js +0 -7
- package/dist/task-a2a.d.ts +44 -14
- package/dist/task-a2a.js +46 -20
- package/dist/task-cwd.d.ts +6 -1
- package/dist/task-mcp.d.ts +33 -20
- package/dist/task-mcp.js +46 -22
- package/dist/tool-approval.d.ts +38 -8
- package/dist/tool-approval.js +57 -13
- package/dist/trace/core-keyset-guard.d.ts +33 -18
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +20 -1
- package/dist/trace/exit-seat.js +3 -0
- package/dist/trace/extension-call-face.d.ts +33 -0
- package/dist/trace/extension-call-face.js +39 -0
- package/dist/trace/project.d.ts +26 -7
- package/dist/trace/project.js +30 -3
- package/dist/trace/redact.js +42 -13
- package/dist/trace/self-minted-wire.d.ts +240 -12
- package/dist/trace/self-minted-wire.js +100 -4
- package/dist/trace/wire-projection-faces.d.ts +6 -6
- package/dist/trace/wire-projection-faces.js +6 -5
- package/dist/url-refusal.d.ts +1 -1
- package/dist/user-authored-text.d.ts +2 -2
- package/dist/user-authored-text.js +16 -1
- package/dist/web-admin-client/apply-effective.d.ts +17 -3
- package/dist/web-admin-client/apply-effective.js +20 -10
- package/dist/web-admin-client/center-request.d.ts +3 -2
- package/dist/web-admin-client/center-request.js +2 -3
- package/dist/web-admin-client/skills-mcp.d.ts +8 -4
- package/dist/web-admin-client/skills-mcp.js +32 -18
- package/dist/web-admin-client/types.d.ts +11 -0
- package/package.json +3 -3
- package/wire-changes/server@7.138.0.json +446 -0
- package/wire-changes/server@7.139.0.json +348 -0
- package/dist/http/device-placement.d.ts +0 -175
- package/dist/http/device-placement.js +0 -145
- package/dist/http/mcp-remote-text.d.ts +0 -5
- package/dist/http/mcp-remote-text.js +0 -7
|
@@ -1,99 +1,97 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, readdir, rename, rmdir, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { carriesCredential, syncMemoryScope, } from "@sema-agent/core";
|
|
5
5
|
import { replicationCovers } from "./memory-scope.js";
|
|
6
6
|
import { parseProjectLayerKey } from "./project-layer-key.js";
|
|
7
|
-
|
|
7
|
+
import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
|
|
8
|
+
import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./plugins/one-time-migrations.js";
|
|
9
|
+
import { pathBaseUrlRefusal } from "./web-admin-client/center-request.js";
|
|
10
|
+
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
11
|
+
export function memorySyncPeerOf(url) {
|
|
12
|
+
const refusal = pathBaseUrlRefusal("memory sync: MEMORY_SYNC_URL", url);
|
|
13
|
+
if (refusal !== undefined)
|
|
14
|
+
throw new Error(refusal);
|
|
15
|
+
const u = new URL(url);
|
|
16
|
+
const identity = `${u.origin}${u.pathname.replace(/\/+$/, "")}`;
|
|
17
|
+
if (carriesCredential(identity)) {
|
|
18
|
+
throw new Error("memory sync: MEMORY_SYNC_URL holds a credential (a token-shaped part of its path) — the sync peer identity travels verbatim in every request body and " +
|
|
19
|
+
"cursor, so it is refused, never cleaned: put the token in MEMORY_SYNC_TOKEN and set MEMORY_SYNC_URL to the central base address itself");
|
|
20
|
+
}
|
|
21
|
+
if (identity.length > MEMORY_SYNC_PEER_COLUMN_CHARS) {
|
|
22
|
+
throw new Error(`memory sync: MEMORY_SYNC_URL normalizes to ${identity.length} characters — the peer identity is the central request's peer, which holds at most ${MEMORY_SYNC_PEER_COLUMN_CHARS}: use a shorter base URL`);
|
|
23
|
+
}
|
|
24
|
+
return identity;
|
|
25
|
+
}
|
|
26
|
+
export function buildMemorySyncEnabledFields(peer, scope, cursorPath) {
|
|
27
|
+
return { peer: buildDisplaySafeUrl(peer), scope, cursorPath };
|
|
28
|
+
}
|
|
8
29
|
export function createMemorySyncTransport(opts) {
|
|
9
|
-
const
|
|
30
|
+
const peer = memorySyncPeerOf(opts.url);
|
|
10
31
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
11
32
|
const timeoutMs = opts.timeoutMs ?? 60_000;
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
return Object.freeze({
|
|
34
|
+
peer,
|
|
35
|
+
async post(path, body) {
|
|
36
|
+
const res = await fetchImpl(`${peer}${path}`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { authorization: `Bearer ${opts.token}`, "content-type": "application/json" },
|
|
39
|
+
body: JSON.stringify(body),
|
|
40
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
41
|
+
});
|
|
42
|
+
const text = await res.text();
|
|
43
|
+
if (!res.ok)
|
|
44
|
+
throw new Error(`memory sync POST ${path}: HTTP ${res.status} ${text.slice(0, 200)}`);
|
|
45
|
+
return JSON.parse(text);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
24
48
|
}
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
const SYNC_CURSOR_DIR = "sync-cursors";
|
|
50
|
+
function scopeDigest(scope) {
|
|
51
|
+
return createHash("sha256").update(scope, "utf8").digest("hex").slice(0, 32);
|
|
27
52
|
}
|
|
28
|
-
function
|
|
29
|
-
return join(memoryRoot,
|
|
53
|
+
export function memorySyncCursorPath(memoryRoot, scope) {
|
|
54
|
+
return join(memoryRoot, SYNC_CURSOR_DIR, `${scopeDigest(scope)}.json`);
|
|
30
55
|
}
|
|
31
|
-
|
|
56
|
+
function unusableCursor(scope, path, why) {
|
|
57
|
+
return new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — the cursor file ${path} cannot be read as a sync cursor (${why}); the layer stays unsynced until the operator picks one of two ways out: ` +
|
|
58
|
+
"(1) restore the file from a backup; (2) delete it to accept re-convergence from an empty baseline — entries changed on both sides are reported as conflicts, " +
|
|
59
|
+
"and entries deleted on one side since the last round come back from the other side");
|
|
60
|
+
}
|
|
61
|
+
function cursorOf(parsed) {
|
|
62
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
63
|
+
return "not a JSON object";
|
|
64
|
+
const c = parsed;
|
|
65
|
+
const baseRevs = c["baseRevs"];
|
|
66
|
+
if (baseRevs === null || typeof baseRevs !== "object" || Array.isArray(baseRevs) || !Object.values(baseRevs).every((v) => typeof v === "string" && v.length > 0)) {
|
|
67
|
+
return "baseRevs is not a map of entry id to a non-empty rev";
|
|
68
|
+
}
|
|
69
|
+
const updatedAtMs = c["updatedAtMs"];
|
|
70
|
+
if (typeof updatedAtMs !== "number" || !Number.isFinite(updatedAtMs))
|
|
71
|
+
return "updatedAtMs is not a finite number";
|
|
72
|
+
return { scope: c["scope"], peer: c["peer"], store: c["store"], baseRevs: { ...baseRevs }, updatedAtMs };
|
|
73
|
+
}
|
|
74
|
+
async function loadLayerBaseline(path, scope) {
|
|
32
75
|
let raw;
|
|
33
76
|
try {
|
|
34
77
|
raw = await readFile(path, "utf8");
|
|
35
78
|
}
|
|
36
79
|
catch (err) {
|
|
37
|
-
if (err.code
|
|
38
|
-
|
|
39
|
-
|
|
80
|
+
if (err.code === "ENOENT")
|
|
81
|
+
return { kind: "none" };
|
|
82
|
+
throw new Error(`memory sync: layer ${JSON.stringify(scope)} not synced — cannot read the cursor file ${path} (${String(err)}); the next round reads it again once it is readable`);
|
|
40
83
|
}
|
|
41
84
|
let parsed;
|
|
42
85
|
try {
|
|
43
86
|
parsed = JSON.parse(raw);
|
|
44
87
|
}
|
|
45
|
-
catch
|
|
46
|
-
|
|
47
|
-
return undefined;
|
|
88
|
+
catch {
|
|
89
|
+
throw unusableCursor(scope, path, "not JSON");
|
|
48
90
|
}
|
|
49
|
-
const cursor = cursorOf(parsed
|
|
50
|
-
if (cursor
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
function cursorOf(parsed, scope, peer) {
|
|
56
|
-
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
57
|
-
const c = parsed;
|
|
58
|
-
const baseRevsRaw = c["baseRevs"];
|
|
59
|
-
if (c["scope"] === scope &&
|
|
60
|
-
c["peer"] === peer &&
|
|
61
|
-
typeof c["updatedAtMs"] === "number" &&
|
|
62
|
-
Number.isFinite(c["updatedAtMs"]) &&
|
|
63
|
-
baseRevsRaw !== null &&
|
|
64
|
-
typeof baseRevsRaw === "object" &&
|
|
65
|
-
!Array.isArray(baseRevsRaw) &&
|
|
66
|
-
Object.values(baseRevsRaw).every((v) => typeof v === "string" && v.length > 0)) {
|
|
67
|
-
return { scope, peer, baseRevs: { ...baseRevsRaw }, updatedAtMs: c["updatedAtMs"] };
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
async function adoptLegacyCursor(memoryRoot, scope, peer, log) {
|
|
73
|
-
const target = memorySyncCursorPath(memoryRoot, peer, scope);
|
|
74
|
-
const legacy = legacyMemorySyncCursorPath(memoryRoot, peer);
|
|
75
|
-
const facts = await Promise.all([readFile(target, "utf8").then(() => "present", (err) => (err.code === "ENOENT" ? "absent" : "present")), readFile(legacy, "utf8").then((text) => ({ text }), (err) => ({ err }))]);
|
|
76
|
-
const [targetFact, legacyRead] = facts;
|
|
77
|
-
if (targetFact === "present")
|
|
78
|
-
return;
|
|
79
|
-
if ("err" in legacyRead) {
|
|
80
|
-
if (legacyRead.err.code !== "ENOENT")
|
|
81
|
-
log.warn("memory_sync_cursor_read_failed", { path: legacy, err: String(legacyRead.err), note: "legacy cursor not adopted — this layer starts from its own cursor" });
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
let parsed;
|
|
85
|
-
try {
|
|
86
|
-
parsed = JSON.parse(legacyRead.text);
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
log.warn("memory_sync_cursor_corrupt", { path: legacy, err: String(err), note: "legacy cursor not adopted" });
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (cursorOf(parsed, scope, peer) === undefined)
|
|
93
|
-
return;
|
|
94
|
-
await mkdir(dirname(target), { recursive: true });
|
|
95
|
-
await rename(legacy, target);
|
|
96
|
-
log.info("memory_sync_cursor_adopted", { from: legacy, to: target, scope, peer });
|
|
91
|
+
const cursor = cursorOf(parsed);
|
|
92
|
+
if (typeof cursor === "string")
|
|
93
|
+
throw unusableCursor(scope, path, cursor);
|
|
94
|
+
return { kind: "cursor", cursor };
|
|
97
95
|
}
|
|
98
96
|
async function saveCursor(path, cursor) {
|
|
99
97
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -101,30 +99,129 @@ async function saveCursor(path, cursor) {
|
|
|
101
99
|
await writeFile(tmp, JSON.stringify(cursor), "utf8");
|
|
102
100
|
await rename(tmp, path);
|
|
103
101
|
}
|
|
102
|
+
const LEGACY_PEER_SEGMENT = "central";
|
|
103
|
+
const LEGACY_ROOT_CURSOR_FILE = "sync-cursor-central.json";
|
|
104
|
+
function m31Window() {
|
|
105
|
+
const row = ONE_TIME_MIGRATIONS.find((w) => w.id === "M31");
|
|
106
|
+
if (row === undefined)
|
|
107
|
+
throw new Error("memory-sync-client: the one-time migration registry has no M31 row — this migration outlived its registry row; delete it with that row");
|
|
108
|
+
return row;
|
|
109
|
+
}
|
|
110
|
+
function scopeOfLegacyCursor(raw) {
|
|
111
|
+
let parsed;
|
|
112
|
+
try {
|
|
113
|
+
parsed = JSON.parse(raw);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
119
|
+
return undefined;
|
|
120
|
+
const scope = parsed["scope"];
|
|
121
|
+
return typeof scope === "string" && scope.length > 0 ? scope : undefined;
|
|
122
|
+
}
|
|
123
|
+
export async function relocateSyncCursorsM31(memoryRoot, log, opts) {
|
|
124
|
+
if (!migrationWindowOpen(opts.window ?? m31Window(), opts.today))
|
|
125
|
+
return { window: "closed", moved: 0, left: 0 };
|
|
126
|
+
let moved = 0;
|
|
127
|
+
let left = 0;
|
|
128
|
+
const codeOf = (err) => err?.code;
|
|
129
|
+
const exists = async (path) => {
|
|
130
|
+
try {
|
|
131
|
+
await stat(path);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (codeOf(err) === "ENOENT")
|
|
136
|
+
return false;
|
|
137
|
+
throw err;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const move = async (from, to) => {
|
|
141
|
+
if (await exists(to)) {
|
|
142
|
+
left += 1;
|
|
143
|
+
log.warn("memory_sync_cursor_not_relocated", { path: from, to, note: "this layer already has a cursor at its path, so the file written by an earlier version stays where it is and no round reads it; delete it" });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await mkdir(dirname(to), { recursive: true });
|
|
147
|
+
try {
|
|
148
|
+
await rename(from, to);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (codeOf(err) === "ENOENT")
|
|
152
|
+
return;
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
moved += 1;
|
|
156
|
+
log.info("memory_sync_cursor_relocated", { from, to });
|
|
157
|
+
};
|
|
158
|
+
const legacyDir = join(memoryRoot, SYNC_CURSOR_DIR, LEGACY_PEER_SEGMENT);
|
|
159
|
+
let names = [];
|
|
160
|
+
try {
|
|
161
|
+
names = await readdir(legacyDir);
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
if (codeOf(err) !== "ENOENT")
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
for (const name of [...names].sort()) {
|
|
168
|
+
if (/^[0-9a-f]{32}\.json$/.test(name))
|
|
169
|
+
await move(join(legacyDir, name), join(memoryRoot, SYNC_CURSOR_DIR, name));
|
|
170
|
+
}
|
|
171
|
+
if (names.length > 0) {
|
|
172
|
+
try {
|
|
173
|
+
await rmdir(legacyDir);
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
if (codeOf(err) !== "ENOENT" && codeOf(err) !== "ENOTEMPTY" && codeOf(err) !== "EEXIST")
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const legacyFile = join(memoryRoot, LEGACY_ROOT_CURSOR_FILE);
|
|
181
|
+
let raw;
|
|
182
|
+
try {
|
|
183
|
+
raw = await readFile(legacyFile, "utf8");
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (codeOf(err) !== "ENOENT")
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
if (raw !== undefined) {
|
|
190
|
+
const scope = scopeOfLegacyCursor(raw);
|
|
191
|
+
if (scope === undefined) {
|
|
192
|
+
left += 1;
|
|
193
|
+
log.warn("memory_sync_cursor_not_relocated", { path: legacyFile, note: "a cursor file written by an earlier version that is not a JSON object naming its scope, so no layer can take it as its baseline; restore it from a backup and restart, or delete it" });
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
await move(legacyFile, memorySyncCursorPath(memoryRoot, scope));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { window: "open", moved, left };
|
|
200
|
+
}
|
|
104
201
|
export const MAX_SYNC_ROUNDS = 32;
|
|
105
202
|
function enumerateScopes(backend) {
|
|
106
203
|
const face = backend.listScopes;
|
|
107
204
|
return typeof face === "function" ? face.call(backend) : { supported: false, reason: "the memory backend has no listScopes face" };
|
|
108
205
|
}
|
|
109
206
|
export function createMemorySyncRunner(opts) {
|
|
110
|
-
const
|
|
207
|
+
const now = opts.now ?? Date.now;
|
|
111
208
|
const coverage = { kind: "layers", layers: [opts.scope] };
|
|
112
209
|
const tracked = new Set();
|
|
113
|
-
let legacyCursorChecked = false;
|
|
114
210
|
let enumerationWarned = false;
|
|
115
211
|
const isCoveredProjectLayer = (scope) => scope !== opts.scope && parseProjectLayerKey(scope) !== undefined && replicationCovers(coverage, scope);
|
|
116
212
|
const layersOfRound = () => {
|
|
117
213
|
const local = enumerateScopes(opts.backend);
|
|
118
214
|
if (!local.supported && !enumerationWarned) {
|
|
119
215
|
enumerationWarned = true;
|
|
120
|
-
opts.log.warn("memory_sync_layer_enumeration_unsupported", { scope: opts.scope,
|
|
216
|
+
opts.log.warn("memory_sync_layer_enumeration_unsupported", { scope: opts.scope, reason: local.reason, note: "only the sync layer and tracked project layers are synced" });
|
|
121
217
|
}
|
|
122
218
|
const projectLayers = new Set([...(local.supported ? local.scopes : []), ...tracked].filter(isCoveredProjectLayer));
|
|
123
219
|
return [opts.scope, ...[...projectLayers].sort()];
|
|
124
220
|
};
|
|
125
221
|
const syncLayer = async (scope, reason) => {
|
|
126
|
-
const cursorPath = memorySyncCursorPath(opts.memoryRoot,
|
|
127
|
-
|
|
222
|
+
const cursorPath = memorySyncCursorPath(opts.memoryRoot, scope);
|
|
223
|
+
const baseline = await loadLayerBaseline(cursorPath, scope);
|
|
224
|
+
let cursor = baseline.kind === "cursor" ? baseline.cursor : undefined;
|
|
128
225
|
let result;
|
|
129
226
|
let subRound = 0;
|
|
130
227
|
for (;;) {
|
|
@@ -133,18 +230,17 @@ export function createMemorySyncRunner(opts) {
|
|
|
133
230
|
backend: opts.backend,
|
|
134
231
|
scope,
|
|
135
232
|
transport: opts.transport,
|
|
136
|
-
peer,
|
|
137
233
|
...(cursor !== undefined ? { cursor } : {}),
|
|
138
234
|
...(opts.now !== undefined ? { now: opts.now } : {}),
|
|
139
235
|
...(opts.maxPushEntries !== undefined ? { maxPushEntries: opts.maxPushEntries } : {}),
|
|
140
236
|
...(opts.maxPullEntries !== undefined ? { maxPullEntries: opts.maxPullEntries } : {}),
|
|
141
237
|
});
|
|
142
238
|
for (const w of result.warnings ?? [])
|
|
143
|
-
opts.log.warn("memory_sync_warning", { scope,
|
|
239
|
+
opts.log.warn("memory_sync_warning", { scope, subRound, warning: w });
|
|
144
240
|
if (!(result.pushTruncated === true || result.pullTruncated === true))
|
|
145
241
|
break;
|
|
146
242
|
if (subRound >= MAX_SYNC_ROUNDS) {
|
|
147
|
-
opts.log.warn("memory_sync_rounds_exhausted", { scope,
|
|
243
|
+
opts.log.warn("memory_sync_rounds_exhausted", { scope, rounds: subRound, note: "truncation flag still set at the round cap — remaining items ride the next trigger" });
|
|
148
244
|
break;
|
|
149
245
|
}
|
|
150
246
|
if (result.cursorAdvanced && result.cursor !== undefined) {
|
|
@@ -156,12 +252,11 @@ export function createMemorySyncRunner(opts) {
|
|
|
156
252
|
}
|
|
157
253
|
}
|
|
158
254
|
cursor = result.cursor ?? cursor;
|
|
159
|
-
opts.log.info("memory_sync_continuation", { reason, scope,
|
|
255
|
+
opts.log.info("memory_sync_continuation", { reason, scope, subRound, pushTruncated: result.pushTruncated === true, pullTruncated: result.pullTruncated === true });
|
|
160
256
|
}
|
|
161
257
|
for (const c of result.conflicts) {
|
|
162
258
|
opts.log.warn("memory_sync_conflict", {
|
|
163
259
|
scope,
|
|
164
|
-
peer,
|
|
165
260
|
side: c.side,
|
|
166
261
|
id: c.id,
|
|
167
262
|
reason: c.reason,
|
|
@@ -180,7 +275,6 @@ export function createMemorySyncRunner(opts) {
|
|
|
180
275
|
opts.log.info("memory_sync_round", {
|
|
181
276
|
reason,
|
|
182
277
|
scope,
|
|
183
|
-
peer,
|
|
184
278
|
ok: result.ok,
|
|
185
279
|
cursorAdvanced: result.cursorAdvanced,
|
|
186
280
|
pushedEntries: result.pushed.entries.length,
|
|
@@ -191,10 +285,11 @@ export function createMemorySyncRunner(opts) {
|
|
|
191
285
|
});
|
|
192
286
|
return result;
|
|
193
287
|
};
|
|
288
|
+
let relocated = false;
|
|
194
289
|
const round = async (reason) => {
|
|
195
|
-
if (!
|
|
196
|
-
|
|
197
|
-
|
|
290
|
+
if (!relocated) {
|
|
291
|
+
await relocateSyncCursorsM31(opts.memoryRoot, opts.log, { today: new Date(now()).toISOString().slice(0, 10) });
|
|
292
|
+
relocated = true;
|
|
198
293
|
}
|
|
199
294
|
const layers = [];
|
|
200
295
|
const failures = [];
|
|
@@ -204,7 +299,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
204
299
|
}
|
|
205
300
|
catch (err) {
|
|
206
301
|
failures.push({ scope, err });
|
|
207
|
-
opts.log.warn("memory_sync_layer_failed", { reason, scope,
|
|
302
|
+
opts.log.warn("memory_sync_layer_failed", { reason, scope, cursorPath: memorySyncCursorPath(opts.memoryRoot, scope), err: String(err) });
|
|
208
303
|
}
|
|
209
304
|
}
|
|
210
305
|
if (failures.length > 0) {
|
|
@@ -227,7 +322,7 @@ export function createMemorySyncRunner(opts) {
|
|
|
227
322
|
const syncOnce = (reason) => exclusive(() => round(reason));
|
|
228
323
|
const queueRound = (reason) => {
|
|
229
324
|
void syncOnce(reason).catch((err) => {
|
|
230
|
-
opts.log.warn("memory_sync_failed", { reason, scope: opts.scope,
|
|
325
|
+
opts.log.warn("memory_sync_failed", { reason, scope: opts.scope, err: String(err) });
|
|
231
326
|
});
|
|
232
327
|
};
|
|
233
328
|
const trigger = (reason) => {
|
|
@@ -244,6 +339,6 @@ export function createMemorySyncRunner(opts) {
|
|
|
244
339
|
tracked.add(scope);
|
|
245
340
|
queueRound("track");
|
|
246
341
|
};
|
|
247
|
-
return { syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(opts.memoryRoot,
|
|
342
|
+
return { syncOnce, trigger, track, exclusive, cursorPathFor: (scope) => memorySyncCursorPath(opts.memoryRoot, scope), pending: () => pending };
|
|
248
343
|
}
|
|
249
344
|
//# sourceMappingURL=memory-sync-client.js.map
|
package/dist/memory-sync.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type MemoryBackend, type MemoryEntry, type PatchReport } from "@sema-ag
|
|
|
2
2
|
import type { MemorySyncStore } from "./plugins/memory-sync-store-pg.js";
|
|
3
3
|
/** 一轮同步请求(验型后的干净形状——`parseMemorySyncRequest` 是唯一铸造点)。 */
|
|
4
4
|
export interface MemorySyncRequest {
|
|
5
|
-
/** 对端标识(
|
|
5
|
+
/** 对端标识(`CentralSyncCursor.peer`,per-(scope,peer) 游标分区键)= 客户端 transport 的身份(本仓的客户端腿:规范化的中心基址,`memory-sync-client.ts` `memorySyncPeerOf`)。 */
|
|
6
6
|
peer: string;
|
|
7
7
|
/** 客户端游标的共同基线:id → rev at last completed sync round(空 map = 首轮)。 */
|
|
8
8
|
baseRevs: Record<string, string>;
|
package/dist/memory-sync.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { computeEntryRev, MEMORY_ORIGIN_CAUSES, nextSyncBaseline, reconcileMemoryEntries, screenInboundEntries, } from "@sema-agent/core";
|
|
2
2
|
import { exportMemoryScope } from "./memory-export.js";
|
|
3
|
+
import { MEMORY_SYNC_PEER_COLUMN_CHARS } from "./plugins/memory-key-guards.js";
|
|
3
4
|
export const MEMORY_SYNC_MAX_ENTRIES = 500;
|
|
4
5
|
export const MEMORY_SYNC_MAX_DELETES = 2000;
|
|
5
6
|
export function parseMemorySyncRequest(body, scope) {
|
|
@@ -7,8 +8,8 @@ export function parseMemorySyncRequest(body, scope) {
|
|
|
7
8
|
return { ok: false, error: "body must be a JSON object { peer, baseRevs, entries, deletes }" };
|
|
8
9
|
const b = body;
|
|
9
10
|
const peer = b["peer"];
|
|
10
|
-
if (typeof peer !== "string" || peer.length === 0 || peer.length >
|
|
11
|
-
return { ok: false, error:
|
|
11
|
+
if (typeof peer !== "string" || peer.length === 0 || peer.length > MEMORY_SYNC_PEER_COLUMN_CHARS)
|
|
12
|
+
return { ok: false, error: `peer must be a non-empty string of at most ${MEMORY_SYNC_PEER_COLUMN_CHARS} characters` };
|
|
12
13
|
const rawBase = b["baseRevs"];
|
|
13
14
|
if (rawBase === null || typeof rawBase !== "object" || Array.isArray(rawBase))
|
|
14
15
|
return { ok: false, error: "baseRevs must be an object mapping entry id → rev (may be empty on the first round)" };
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* **env 模型路由读数的唯一读口**:`readModelRouteEnv(env)`、env 腿的条目表 `envLaneTable`,与车道值级端口 `laneAndEndpointFor`。
|
|
3
|
-
*
|
|
4
|
-
* ## 形(一只读口、一处调用)
|
|
5
|
-
* 「env 里哪几把键是模型路由、空串 = 未设、provider 的显式 / 推断 / 缺省、env 腿铸哪几个条目槽」的读法只在这里:
|
|
6
|
-
* · {@link readModelRouteEnv}:`parseModelDomain`(`config.ts`,起服铸 `ServiceConfig` 的路由字段)吃它的读数(`process.env` 与测试快照是同一个入参位);
|
|
7
|
-
* · {@link envLaneTable}:env 腿的条目名与键序(`parseModelDomain` 铸真 `Model` 表经它);
|
|
8
|
-
* · {@link laneAndEndpointFor}:值级端口 —— 「这只模型在这张路由表上走哪条车道、这条车道的端点 / 凭据取哪一对 env 键」。车道只经 `brainLaneFor`
|
|
9
|
-
* (core `brainLaneOf` 对路由表的答案)取,键名取 {@link MODEL_ROUTE_ENV_KEYS};**只交键名不交值**。
|
|
10
|
-
* 路由判据本身(「将要服务的那张表里每条 openai 兼容路由是否解析得到端点」)不在这里:唯一属主是 `model-route-endpoint.ts` 的 `judgeModelRouteEndpoint`,
|
|
11
|
-
* 起服段与 `GET /v1/models.modelPlane` 经 `judgeServedModelRoute` 问它 —— 它们判的是**将要服务的那张表**(env 表或目录表),不是本文件读出的 env 视图。
|
|
12
|
-
*
|
|
13
|
-
* ## 对外
|
|
14
|
-
* 本模块的精选再导出是子路径 `@sema-agent/server/model-route-env`(`src/model-route-env.ts`):引擎 core 是 BYOM(只吃调用方给的路由表,不读这些键),
|
|
15
|
-
* 别仓要在引擎起之前回答「走哪条车道、端点 / 凭据取哪一对键」,读这一只,不各抄一份键名与车道判。
|
|
16
|
-
*
|
|
17
|
-
* ## 纯
|
|
18
|
-
* 不读 `process.env`、不读文件、不枚举快照 —— 只按 {@link MODEL_ROUTE_ENV_KEYS} 点名取值(依赖闭包里没有 `node:*` / `process`,
|
|
19
|
-
* `test/model-route-env-reading.test.ts` 钉)。同入同出;返回值是新对象。provider 词域外的值在这里按引擎派发的口径读作网关腿(`brainLaneFor`:域外词的路由表里没有那个键 ⇒ 引擎路由器落网关 fallback),不在此拒(域外拒启是 `model-provider-word` 那条不变量的事)。
|
|
20
|
-
*/
|
|
21
1
|
import { type ModelBrainLane } from "./model-provider.js";
|
|
22
2
|
/**
|
|
23
3
|
* 路由读数读的九把 env 键(**闭表**;{@link readModelRouteEnv} 只碰这几把,测试用代理钉「读集 ⊆ 本表」)。
|
|
@@ -59,8 +39,9 @@ export interface ModelRouteEnvReading {
|
|
|
59
39
|
/** `MODEL_GATEWAY_FALLBACK_URLS` 逐项 trim、丢空项。 */
|
|
60
40
|
readonly gatewayFallbackUrls: string[];
|
|
61
41
|
/**
|
|
62
|
-
* `MODEL_API_KEY`(网关腿的部署级凭据)
|
|
42
|
+
* `MODEL_API_KEY`(网关腿的部署级凭据):读数**原样**交出 —— 与其余各把不同,空串**不**折成未设:
|
|
63
43
|
* 起服铸的 `ServiceConfig.gatewayApiKey` 逐字保留空串(compose 的 `${VAR:-}` 模板与空 Secret 挂载天天产它);读数改折叠会改变那一位。
|
|
44
|
+
* 「这把凭据设了没有」的问法不在读数里折,而是消费方问 {@link declaredCredential}(引擎的 `isBlankCredential`:空串与只含空白都 = 没有)。
|
|
64
45
|
* 它只与网关端点配对(没有 {@link gatewayBaseUrl} 就不装载),配对由消费方判。
|
|
65
46
|
*/
|
|
66
47
|
readonly gatewayApiKey: string | undefined;
|
|
@@ -85,6 +66,9 @@ export interface ModelRouteEnvReading {
|
|
|
85
66
|
/** cheap 槽铸不铸(见 {@link EnvCheapSlot})。 */
|
|
86
67
|
readonly cheapSlot: EnvCheapSlot;
|
|
87
68
|
}
|
|
69
|
+
/** 一把凭据键「设了没有」的唯一问法(两把 anthropic 键与网关键 `MODEL_API_KEY` 同读):引擎的 `isBlankCredential`(整串 trim 后为空 ⇒ 没有凭据)—— 与引擎对同一个值的读法同一只,
|
|
70
|
+
* 不自拼第二种「空」:只含空白的 key 旁边的 token 是 bearer(引擎让那只头作凭据照发),不是同设冲突;两把都空白 = 缺席、路由不挂;只含空白的网关 key = 没有部署凭据。 */
|
|
71
|
+
export declare const declaredCredential: (v: string | undefined) => string | undefined;
|
|
88
72
|
/** 逗号串读法的归一点:逐项 trim、丢空项(缺席 = 空串 = `[]`)。本文件的 CSV 路由键与 `config.ts` 的逗号串旋钮(按名薄壳 `csv(name)`)同读这一只;
|
|
89
73
|
* 目录模块的回显分词与 `SERVICE_AUTH_TOKENS` 的 `k=v` 对分词是同形的另两处,尚未改调(清账版收,本函数届时挪到中性模块)。 */
|
|
90
74
|
export declare const csvOf: (raw: string | undefined) => string[];
|
|
@@ -98,7 +82,7 @@ export declare function readModelRouteEnv(env: ModelRouteEnv): ModelRouteEnvRead
|
|
|
98
82
|
* · `ANTHROPIC_API_KEY` ⇒ `x-api-key`(只发它);
|
|
99
83
|
* · `ANTHROPIC_AUTH_TOKEN` ⇒ `Authorization: Bearer`(只发它);
|
|
100
84
|
* · 两个都设 ⇒ `conflict`:两个名字说的是两种头,哪个赢都是替运维猜(7.135.0 及以前是「AUTH_TOKEN 优先」,x-api-key 被静默丢弃)⇒ 起服拒启,拒句 {@link anthropicCredentialRefusal}。
|
|
101
|
-
*
|
|
85
|
+
* 「设了没有」按引擎的 `isBlankCredential` 读(空串与只含空白都 = 未设;与 {@link declaredCredential} 同一只)。入参是两个名字各自的值 —— env 读数的两位或 `config.anthropic` 的 `apiKey` / `authToken` —— 不读 `process.env`。
|
|
102
86
|
* 读者:起服(`config.ts` 的拒启锚与跨键不变量表 `anthropic-credential-exclusive` 同一句)· brain 装配(交引擎哪种头)· hook 直连腿(哪种头、交引擎配对判官的那份值)·
|
|
103
87
|
* 车道端口 {@link laneAndEndpointFor}(哪把键)· 起服的 Bearer 部署诊断(`boot/web-admin.ts`)。
|
|
104
88
|
*/
|
package/dist/model-route-gap.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBlankCredential } from "@sema-agent/core";
|
|
1
2
|
import { brainLaneFor, declaredRootUrl } from "./model-provider.js";
|
|
2
3
|
import { DEFAULT_MODEL_REF } from "./model-select.js";
|
|
3
4
|
export const MODEL_ROUTE_ENV_KEYS = {
|
|
@@ -22,14 +23,15 @@ export function mintedCheapId(slot) {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
const declaredValue = (v) => (v !== undefined && v !== "" ? v : undefined);
|
|
26
|
+
export const declaredCredential = (v) => (v !== undefined && !isBlankCredential(v) ? v : undefined);
|
|
25
27
|
export const csvOf = (raw) => (raw ?? "")
|
|
26
28
|
.split(",")
|
|
27
29
|
.map((s) => s.trim())
|
|
28
30
|
.filter(Boolean);
|
|
29
31
|
export function readModelRouteEnv(env) {
|
|
30
32
|
const K = MODEL_ROUTE_ENV_KEYS;
|
|
31
|
-
const anthropicApiKey =
|
|
32
|
-
const anthropicAuthToken =
|
|
33
|
+
const anthropicApiKey = declaredCredential(env[K.anthropicApiKey]);
|
|
34
|
+
const anthropicAuthToken = declaredCredential(env[K.anthropicAuthToken]);
|
|
33
35
|
const anthropicBaseUrl = declaredValue(env[K.anthropicBaseUrl]);
|
|
34
36
|
const anthropicRouteMounted = anthropicApiKey !== undefined || anthropicAuthToken !== undefined;
|
|
35
37
|
const explicitProvider = declaredValue(env[K.provider]);
|
|
@@ -58,8 +60,8 @@ export function readModelRouteEnv(env) {
|
|
|
58
60
|
}
|
|
59
61
|
export function judgeAnthropicCredential(names) {
|
|
60
62
|
const K = MODEL_ROUTE_ENV_KEYS;
|
|
61
|
-
const apiKey =
|
|
62
|
-
const authToken =
|
|
63
|
+
const apiKey = declaredCredential(names.apiKey);
|
|
64
|
+
const authToken = declaredCredential(names.authToken);
|
|
63
65
|
if (apiKey !== undefined && authToken !== undefined)
|
|
64
66
|
return { kind: "conflict" };
|
|
65
67
|
if (apiKey !== undefined)
|
|
@@ -111,7 +113,7 @@ export function laneAndEndpointFor(env, model, routes) {
|
|
|
111
113
|
};
|
|
112
114
|
case "gateway": {
|
|
113
115
|
const endpointDeclared = declaredRootUrl(reading.gatewayBaseUrl) !== undefined;
|
|
114
|
-
const credentialLoaded = endpointDeclared && reading.gatewayApiKey !== undefined
|
|
116
|
+
const credentialLoaded = endpointDeclared && declaredCredential(reading.gatewayApiKey) !== undefined;
|
|
115
117
|
return { lane, endpointEnvName: K.gatewayBaseUrl, endpointDeclared, apiKeyEnvName: credentialLoaded ? K.gatewayApiKey : undefined };
|
|
116
118
|
}
|
|
117
119
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 部署声明的 mod 包 —— `MODS_ROOT` + `MODS_PACKAGES` 的**唯一读法**,以及部署座装配(`boot/deployment-mods.ts`)全部失败的**唯一词表与拒句**。
|
|
3
|
+
*
|
|
4
|
+
* ## 规则(一句)
|
|
5
|
+
* mod 包只从运维的部署声明装载;声明只说四件宿主的事实 —— 装哪个目录(`dir`)、叫什么(`name`,须与包自己的名逐字同)、站哪一档(`tier`)、
|
|
6
|
+
* 交给它什么(`options`);包里的三件文件只有引擎一只读者(`loadModPackage`),本仓**不读包内任何文件、不算摘要**。
|
|
7
|
+
*
|
|
8
|
+
* ## 声明的形
|
|
9
|
+
* · `MODS_ROOT` —— 绝对路径;声明的每只包解析真实路径之后必须**严格**落在它(的真实路径)之下(按路径分量判,不是前缀;根本身不是包目录)。
|
|
10
|
+
* 单设它而不声明包 = 无事(形照判)。
|
|
11
|
+
* · `MODS_PACKAGES` —— JSON 数组,每项闭集四键 `{ name, dir, tier, options? }`:`name` 非空串;`dir` 绝对路径;`tier` ∈ `prepend | user | append`
|
|
12
|
+
* (宿主必填 —— 包不自报档;站不站得住由引擎的座 × 档表判,部署座三档都许);`options` 任意 JSON 值,原样交包的 `register`。
|
|
13
|
+
* 缺席 / 空串 = 不声明(部署座只有本仓自铸的注册,与没有这两根旋钮的版本逐字节同)。
|
|
14
|
+
* · `MODS_PACKAGES` 是一份声明、不是凭据值:配置目录只回在场位与长度(不回显);`options` 不是凭据通道 —— 本服务不替包扣凭据(声明与包自己读的环境变量
|
|
15
|
+
* 都不在自有凭据声明里,宿主车道 `inheritEnv: "all"` 下模型的子进程照样继承)。
|
|
16
|
+
*
|
|
17
|
+
* ## 失败(两类,一张闭集词表,一种拒句)
|
|
18
|
+
* · **声明 / 文件系统**(本仓判;任何一只包被装载之前,整张声明全部判完):
|
|
19
|
+
* `config_invalid`(某一席的形不对 —— 整段 JSON、某一项、`MODS_ROOT`)· `root_missing`(声明了包而 `MODS_ROOT` 没设)·
|
|
20
|
+
* `name_missing` · `duplicate_name` · `root_unreadable` · `dir_unreadable` · `outside_root` · `not_a_directory`;
|
|
21
|
+
* · **读包者 / 准入**(引擎判,本仓只透传):`package_refused`(读包者拒了这只包)· `name_mismatch`(包自己的名与声明的不同)·
|
|
22
|
+
* `admission_refused`(整列交引擎准入被拒)。
|
|
23
|
+
* 任一失败 = 拒启。拒句一行:`deployment mods refused: <词>(<主语>)[: <细节>]`,整行经出口座 `boot-refusal-line`(剥控制与双向字符、凭据形换记号、
|
|
24
|
+
* ≤ 512 码元含截断记号)—— 读包者 / 准入的机器码原样留在主语括号里;本仓写的句子不回显声明的值(路径、`options`),主语只点名是哪一席(`MODS_PACKAGES[2]`)
|
|
25
|
+
* 或哪只包;读包者 / 准入 / 包自己交回的话原样透传(过出口座),其中可能点名包目录 —— 那是运维要的诊断,包本身是受信代码。
|
|
26
|
+
*/
|
|
27
|
+
import type { ModRegistrationTier } from "@sema-agent/core";
|
|
28
|
+
/** 声明的一只包(宿主的四件事实)。 */
|
|
29
|
+
export interface DeclaredModPackage {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
readonly dir: string;
|
|
32
|
+
readonly tier: ModRegistrationTier;
|
|
33
|
+
/** 缺席 = 声明里没写这一键(交给 `register` 的就是 `undefined`)。 */
|
|
34
|
+
readonly options?: unknown;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 部署声明(`loadConfig` 读一次;两只起服入口的装配都读这一份)。两种形,形即律:
|
|
38
|
+
* · **没声明包** —— 没有 `packages` 这一席(变量缺席 / 空串 / 显式 `[]` 同一形);`root` 只在单设了 `MODS_ROOT` 时在(形照判、无事,配置目录照回);
|
|
39
|
+
* · **声明了包** —— 至少一只,且**必有根**:「有包无根」「有根空表」都不是可表示的值(`root_missing` 只在本模块的读法里判,装配口没有这一臂)。
|
|
40
|
+
* 读者按 `d.packages === undefined` 分两形(`packages` 是判别位:没声明那一形上它只能缺席)。
|
|
41
|
+
*/
|
|
42
|
+
export type ModsDeclaration = {
|
|
43
|
+
readonly root?: string;
|
|
44
|
+
readonly packages?: never;
|
|
45
|
+
} | {
|
|
46
|
+
readonly root: string;
|
|
47
|
+
readonly packages: readonly [DeclaredModPackage, ...DeclaredModPackage[]];
|
|
48
|
+
};
|
|
49
|
+
/** 声明里的一席:整根旋钮,或 `MODS_PACKAGES` 的第几项。 */
|
|
50
|
+
export type DeclarationSeat = "MODS_ROOT" | "MODS_PACKAGES" | {
|
|
51
|
+
readonly entry: number;
|
|
52
|
+
};
|
|
53
|
+
/** 一次失败(词 + 主语 + 细节)。细节只有两种来源:本仓写死的句子,或读包者 / 准入交回的话(过出口座)。**失败词的唯一来源是本联合各成员的 `word`。** */
|
|
54
|
+
export type DeploymentModsRefusal = {
|
|
55
|
+
readonly word: "config_invalid";
|
|
56
|
+
readonly seat: DeclarationSeat;
|
|
57
|
+
readonly why: string;
|
|
58
|
+
} | {
|
|
59
|
+
readonly word: "root_missing";
|
|
60
|
+
} | {
|
|
61
|
+
readonly word: "name_missing";
|
|
62
|
+
readonly entry: number;
|
|
63
|
+
} | {
|
|
64
|
+
readonly word: "duplicate_name";
|
|
65
|
+
readonly entry: number;
|
|
66
|
+
readonly first: number;
|
|
67
|
+
} | {
|
|
68
|
+
readonly word: "root_unreadable";
|
|
69
|
+
readonly cause: string;
|
|
70
|
+
} | {
|
|
71
|
+
readonly word: "dir_unreadable";
|
|
72
|
+
readonly entry: number;
|
|
73
|
+
readonly cause: string;
|
|
74
|
+
} | {
|
|
75
|
+
readonly word: "outside_root";
|
|
76
|
+
readonly entry: number;
|
|
77
|
+
} | {
|
|
78
|
+
readonly word: "not_a_directory";
|
|
79
|
+
readonly entry: number;
|
|
80
|
+
} | {
|
|
81
|
+
readonly word: "package_refused";
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly code: string | undefined;
|
|
84
|
+
readonly reason: string;
|
|
85
|
+
} | {
|
|
86
|
+
readonly word: "name_mismatch";
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly packageName: string;
|
|
89
|
+
} | {
|
|
90
|
+
readonly word: "admission_refused";
|
|
91
|
+
readonly code: string | undefined;
|
|
92
|
+
readonly reason: string;
|
|
93
|
+
};
|
|
94
|
+
/** 失败词(拒句的 `<词>`)= 联合成员的 `word`,不另写一份。渲染是穷尽 `switch`(加成员不加臂 = 编译红)。 */
|
|
95
|
+
export type DeploymentModsRefusalWord = DeploymentModsRefusal["word"];
|
|
96
|
+
/** 失败词闭集(运行期全集;「每词至少一格」的钉读它)。 */
|
|
97
|
+
export declare const DEPLOYMENT_MODS_REFUSALS: readonly DeploymentModsRefusalWord[];
|
|
98
|
+
/** 一次失败的拒句(一行;整行经出口座 `boot-refusal-line`:剥控制与双向字符、凭据形换记号、≤ 512 码元含截断记号)。 */
|
|
99
|
+
export declare function buildDeploymentModsRefusalLine(r: DeploymentModsRefusal): string;
|
|
100
|
+
/** 部署座装配的拒启(两只起服入口同一只类型;`refusal` 是机读的那一份,`message` 是它的拒句)。 */
|
|
101
|
+
export declare class DeploymentModsRefusedError extends Error {
|
|
102
|
+
readonly refusal: DeploymentModsRefusal;
|
|
103
|
+
constructor(refusal: DeploymentModsRefusal);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 声明的形(纯函数,`loadConfig` 调;文件系统那一半在装配里):`MODS_ROOT` 的形 → `MODS_PACKAGES` 是不是 JSON 数组 → 逐项(序号升序;每项:是对象 →
|
|
107
|
+
* 键闭集 → `name` → `dir` → `tier` → 与前面的项重名)→ 声明了包而没设根。第一处不过即拒,拒句不回显声明的值。
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseModsDeclaration(rootRaw: string | undefined, packagesRaw: string | undefined): ModsDeclaration;
|
|
110
|
+
//# sourceMappingURL=mods-declaration.d.ts.map
|