@sema-agent/server 7.98.0 → 7.100.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 +19 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +86 -60
- package/deploy/sema-up/lib/envf.sh +110 -42
- package/deploy/sema-up/sema-up.sh +76 -34
- package/dist/approval-ask-machine.d.ts +10 -0
- package/dist/approval-ask-machine.js +3 -0
- package/dist/approval-card.d.ts +74 -1
- package/dist/approval-card.js +41 -8
- package/dist/approval.d.ts +44 -20
- package/dist/approval.js +14 -6
- package/dist/boot/config-center.d.ts +16 -8
- package/dist/boot/config-center.js +206 -103
- package/dist/boot/execution-env.d.ts +1 -2
- package/dist/boot/execution-env.js +2 -2
- package/dist/boot/leader.js +12 -9
- package/dist/boot/memory-consolidation.d.ts +38 -0
- package/dist/boot/memory-consolidation.js +35 -0
- package/dist/boot/org-memory.d.ts +2 -1
- package/dist/boot/org-memory.js +9 -5
- package/dist/boot/resolve-spec.d.ts +0 -1
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/runtime-caps.js +4 -4
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +6 -6
- package/dist/boot/stage-08-reapers.d.ts +3 -4
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/capabilities/center-plugins.d.ts +14 -2
- package/dist/capabilities/center-plugins.js +13 -5
- package/dist/capabilities/center-prompts.d.ts +3 -0
- package/dist/capabilities/center-prompts.js +1 -1
- package/dist/capabilities/hands-lane.js +2 -1
- package/dist/capabilities/scenarios.js +18 -5
- package/dist/capabilities/tool-defer.d.ts +2 -2
- package/dist/center-credential.d.ts +112 -0
- package/dist/center-credential.js +131 -0
- package/dist/config-catalog.d.ts +2 -2
- package/dist/config-catalog.js +15 -15
- package/dist/config-center/apply-effective.d.ts +34 -39
- package/dist/config-center/apply-effective.js +100 -64
- package/dist/config-center/apply-ledger.d.ts +35 -16
- package/dist/config-center/apply-ledger.js +25 -18
- package/dist/config-center/center-request.d.ts +60 -0
- package/dist/config-center/center-request.js +71 -0
- package/dist/config-center/facade.d.ts +11 -5
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/hot-keys-registry.d.ts +11 -4
- package/dist/config-center/hot-keys-registry.js +11 -7
- package/dist/config-center/http-client.d.ts +17 -9
- package/dist/config-center/http-client.js +27 -36
- package/dist/config-center/restart-signal.d.ts +21 -14
- package/dist/config-center/restart-signal.js +16 -22
- package/dist/config-center/skills-mcp.d.ts +4 -1
- package/dist/config-center/skills-mcp.js +2 -2
- package/dist/config-center/types.d.ts +3 -3
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +0 -8
- package/dist/config-lkg.d.ts +47 -8
- package/dist/config-lkg.js +107 -20
- package/dist/config-provider.d.ts +8 -7
- package/dist/config-provider.js +5 -5
- package/dist/config-types.d.ts +30 -11
- package/dist/config.js +11 -13
- package/dist/cross-session-settings.js +1 -1
- package/dist/fleet-client.d.ts +8 -8
- package/dist/fleet-client.js +7 -7
- package/dist/fleet-lease.d.ts +11 -11
- package/dist/fleet-lease.js +17 -13
- package/dist/host-lsp-manager.d.ts +29 -0
- package/dist/host-lsp-manager.js +14 -0
- package/dist/hosted-posture.d.ts +49 -93
- package/dist/hosted-posture.js +1 -25
- package/dist/http/active-run-conflict.d.ts +2 -12
- package/dist/http/active-run-conflict.js +1 -6
- package/dist/http/admission.js +8 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +14 -10
- package/dist/http/route-ctx.d.ts +2 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +2 -2
- package/dist/http/routes/memory-compliance.js +5 -0
- package/dist/http/routes/memory-origin.js +5 -0
- package/dist/http/routes/sessions.js +6 -0
- package/dist/http/routes/tasks.js +7 -6
- package/dist/http/server.d.ts +6 -5
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/leader/wire.d.ts +20 -12
- package/dist/leader/wire.js +7 -6
- package/dist/memory-layer-preflight.d.ts +46 -0
- package/dist/memory-layer-preflight.js +121 -0
- package/dist/memory-operator-faces.js +17 -1
- package/dist/observability/corrupt-read-seat.d.ts +21 -4
- package/dist/observability/corrupt-read-seat.js +31 -0
- package/dist/observability/fail-open.d.ts +8 -4
- package/dist/observability/fail-open.js +8 -4
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +3 -26
- package/dist/observability/secret-env-scrub.js +3 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
- package/dist/orchestration/workflow-completion-inbox.js +8 -3
- package/dist/plugins/approval-ask-store-file.d.ts +20 -2
- package/dist/plugins/approval-ask-store-file.js +21 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
- package/dist/plugins/approval-ask-store-memory.js +39 -13
- package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
- package/dist/plugins/approval-ask-store-sql.js +27 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
- package/dist/plugins/checkpoint-store-sql.js +19 -2
- package/dist/plugins/local-checkpoint-store.js +2 -0
- package/dist/plugins/mailbox-store-sql.d.ts +7 -5
- package/dist/plugins/mailbox-store-sql.js +6 -4
- package/dist/plugins/permission-rule-store-file.js +3 -2
- package/dist/plugins/remote-env-host.d.ts +3 -1
- package/dist/plugins/remote-env-host.js +3 -2
- package/dist/plugins/sql-json-column.d.ts +16 -3
- package/dist/plugins/sql-json-column.js +15 -4
- package/dist/plugins/web-search.d.ts +42 -22
- package/dist/plugins/web-search.js +141 -49
- package/dist/project-memory.d.ts +28 -1
- package/dist/project-memory.js +22 -22
- package/dist/run-local.js +6 -7
- package/dist/runs.js +6 -5
- package/dist/runtime-caps-resolver.d.ts +8 -8
- package/dist/runtime-caps-resolver.js +16 -14
- package/dist/runtime-governance.d.ts +50 -3
- package/dist/runtime-governance.js +5 -0
- package/dist/sealed-key.js +3 -7
- package/dist/server-secret-env.d.ts +43 -0
- package/dist/server-secret-env.js +18 -0
- package/dist/task-settings.d.ts +1 -1
- package/dist/tool-approval.d.ts +144 -9
- package/dist/tool-approval.js +74 -18
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +22 -5
- package/dist/trace/projection-drop.d.ts +2 -0
- package/dist/trace/projection-drop.js +6 -0
- package/dist/trace/sema-provenance.d.ts +4 -1
- package/dist/trace/sema-provenance.js +1 -1
- package/dist/trace/task-notification-facets.d.ts +65 -0
- package/dist/trace/task-notification-facets.js +31 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +3 -3
- package/package.json +5 -4
package/dist/project-memory.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PlatformPath } from "node:path";
|
|
1
2
|
import type { ProjectMemoryLoad } from "@sema-agent/core";
|
|
2
3
|
interface ProjectMemoryCtx {
|
|
3
4
|
cwd: string;
|
|
@@ -7,9 +8,35 @@ interface ProjectMemoryCtx {
|
|
|
7
8
|
sessionId?: string;
|
|
8
9
|
phase?: "fresh" | "resume" | "post-compact" | "post-clear";
|
|
9
10
|
}
|
|
11
|
+
/** The instruction-file name chain: the CLAUDE family first, the AGENTS family only when no CLAUDE file is
|
|
12
|
+
* present. The FIRST hit is the single declared instruction source.
|
|
13
|
+
*
|
|
14
|
+
* Order follows CC 2.1.280 (clay ruling [ref], 2026-09-23, board [ref] RG-2). It replaces the core 1.302 S-1
|
|
15
|
+
* order, where AGENTS.md won over CLAUDE.md; [ref] supersedes that. In CC 2.1.280:
|
|
16
|
+
* - cli280.js:185162 — the default mode `claude-md-or-agents-md`: "a project with no CLAUDE.md of its own gets its
|
|
17
|
+
* AGENTS.md files instead, loaded exactly where and how CLAUDE.md would be";
|
|
18
|
+
* - cli280.js:245920 — the two name families: AGENTS `["AGENTS.md", ".claude/AGENTS.md"]`,
|
|
19
|
+
* CLAUDE `["CLAUDE.md", ".claude/CLAUDE.md", "CLAUDE.local.md"]`;
|
|
20
|
+
* - cli280.js:226831 — the 2.1.277 changelog entry "in a project with no CLAUDE.md, Claude Code reads AGENTS.md
|
|
21
|
+
* instead".
|
|
22
|
+
* Not followed here (CC divergences, registration drafted in the che-MN closing note): `CLAUDE.local.md` is not read;
|
|
23
|
+
* CC's other `instructionFiles` modes (`claude-md-and-agents-md` loads both, `managed-only`) are not offered; CC loads
|
|
24
|
+
* every CLAUDE file it finds, per directory level up the ancestor chain, whereas this loader reads one root and
|
|
25
|
+
* takes the single first hit. */
|
|
26
|
+
declare const INSTRUCTION_FILE_CHAIN: readonly ["CLAUDE.md", ".claude/CLAUDE.md", "AGENTS.md", ".claude/AGENTS.md"];
|
|
27
|
+
type ChainMember = (typeof INSTRUCTION_FILE_CHAIN)[number];
|
|
28
|
+
/** Recover (root, rel) for a path the LOADER declared — every declared path is `root + one chain member`.
|
|
29
|
+
* The probe needs this to re-run the loader's parent-directory symlink gate each round (self-review: the probe
|
|
30
|
+
* checked only the final component, so swapping `.claude` for a symlink out of the repo MID-RUN made the probe
|
|
31
|
+
* fingerprint an outside file). Exported only for its path-matrix pin (posix / win32 through `p`); the probe
|
|
32
|
+
* calls it with the host `node:path`. */
|
|
33
|
+
export declare function instructionChainCoordinates(absPath: string, p?: PlatformPath): {
|
|
34
|
+
root: string;
|
|
35
|
+
rel: ChainMember;
|
|
36
|
+
} | null;
|
|
10
37
|
/**
|
|
11
38
|
* Build a `loadProjectMemory` for the host lane. Returns null (skip) unless hands are enabled AND cwd is a local
|
|
12
|
-
* directory. Reads the instruction-file chain (
|
|
39
|
+
* directory. Reads the instruction-file chain (CLAUDE.md → .claude/CLAUDE.md → AGENTS.md → .claude/AGENTS.md, [ref]) + (non-subagent)
|
|
13
40
|
* `git log -5` / `git status`, redacts secrets, and caps the total. A subagent gets a trimmed instruction file and
|
|
14
41
|
* no git narrative (cost control, H7). When an instruction file is hit the return is a `ProjectMemoryLoad`
|
|
15
42
|
* declaring it in `instructionSources` (path + sha256 over the raw bounded-read window) so core's
|
package/dist/project-memory.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { promises as fs, constants as fsConstants } from "node:fs";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { createHmac, randomBytes } from "node:crypto";
|
|
4
|
-
import { isAbsolute, join
|
|
5
|
-
import { scrubSecretEnv } from "@sema-agent/core";
|
|
4
|
+
import hostPath, { isAbsolute, join } from "node:path";
|
|
6
5
|
import { recordSecretEnvScrub } from "./observability/secret-env-scrub.js";
|
|
6
|
+
import { scrubServerSecrets } from "./server-secret-env.js";
|
|
7
7
|
import { redactSecrets } from "./trace/redact.js";
|
|
8
|
-
const INSTRUCTION_FILE_CHAIN = ["
|
|
8
|
+
const INSTRUCTION_FILE_CHAIN = ["CLAUDE.md", ".claude/CLAUDE.md", "AGENTS.md", ".claude/AGENTS.md"];
|
|
9
9
|
const INSTRUCTION_READ_CAP = 24 * 1024;
|
|
10
10
|
function capUtf8(s, maxBytes) {
|
|
11
11
|
const buf = Buffer.from(s, "utf8");
|
|
@@ -41,15 +41,16 @@ function instructionHash(raw) {
|
|
|
41
41
|
return `hmac-sha256:${createHmac("sha256", INSTRUCTION_HASH_KEY).update(raw).digest("hex")}`;
|
|
42
42
|
}
|
|
43
43
|
const ABSENT_HASH = null;
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
for (const rel of
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
export function instructionChainCoordinates(absPath, p = hostPath) {
|
|
45
|
+
const candidates = INSTRUCTION_FILE_CHAIN.filter((rel) => absPath.endsWith(p.sep + rel.split("/").join(p.sep))).sort((a, b) => b.length - a.length);
|
|
46
|
+
for (const rel of candidates) {
|
|
47
|
+
let root = absPath;
|
|
48
|
+
for (let i = 0; i < rel.split("/").length; i++)
|
|
49
|
+
root = p.dirname(root);
|
|
50
|
+
if (p.join(root, rel) === absPath)
|
|
51
|
+
return { root, rel };
|
|
51
52
|
}
|
|
52
|
-
return
|
|
53
|
+
return null;
|
|
53
54
|
}
|
|
54
55
|
async function hasSymlinkedParent(root, rel) {
|
|
55
56
|
const parts = rel.split("/").slice(0, -1);
|
|
@@ -62,10 +63,15 @@ async function hasSymlinkedParent(root, rel) {
|
|
|
62
63
|
}
|
|
63
64
|
return false;
|
|
64
65
|
}
|
|
66
|
+
async function readChainMember(root, rel, maxBytes, truncatedFlag) {
|
|
67
|
+
if (rel.includes("/") && (await hasSymlinkedParent(root, rel)))
|
|
68
|
+
return null;
|
|
69
|
+
return readRawCapped(join(root, rel), maxBytes, truncatedFlag);
|
|
70
|
+
}
|
|
65
71
|
function git(root, args, maxBytes) {
|
|
66
72
|
const safeArgs = ["-c", "core.fsmonitor=false", "-c", "core.hooksPath=/dev/null", ...args];
|
|
67
73
|
const scrubFindings = [];
|
|
68
|
-
const scrubbedEnv =
|
|
74
|
+
const scrubbedEnv = scrubServerSecrets(process.env, scrubFindings);
|
|
69
75
|
recordSecretEnvScrub("server.project-memory.git-subprocess", scrubFindings);
|
|
70
76
|
return new Promise((res) => {
|
|
71
77
|
execFile("git", safeArgs, { cwd: root, timeout: 5000, maxBuffer: maxBytes, env: { ...scrubbedEnv, GIT_CONFIG_GLOBAL: "/dev/null", GIT_CONFIG_SYSTEM: "/dev/null" } }, (err, stdout, stderr) => {
|
|
@@ -156,9 +162,7 @@ export function makeLoadProjectMemory(opts) {
|
|
|
156
162
|
hitIndex = -1;
|
|
157
163
|
for (let i = 0; i < INSTRUCTION_FILE_CHAIN.length; i++) {
|
|
158
164
|
const rel = INSTRUCTION_FILE_CHAIN[i];
|
|
159
|
-
|
|
160
|
-
continue;
|
|
161
|
-
const raw = await readRawCapped(join(root, rel), claudeCap, claudeTrunc);
|
|
165
|
+
const raw = await readChainMember(root, rel, claudeCap, claudeTrunc);
|
|
162
166
|
if (raw !== null) {
|
|
163
167
|
claudeRaw = capUtf8(raw.toString("utf8"), claudeCap);
|
|
164
168
|
hitFile = rel;
|
|
@@ -171,8 +175,7 @@ export function makeLoadProjectMemory(opts) {
|
|
|
171
175
|
break;
|
|
172
176
|
let appeared = false;
|
|
173
177
|
for (const rel of INSTRUCTION_FILE_CHAIN.slice(0, hitIndex)) {
|
|
174
|
-
|
|
175
|
-
if (higher && !higher.isSymbolicLink()) {
|
|
178
|
+
if ((await readChainMember(root, rel, claudeCap)) !== null) {
|
|
176
179
|
appeared = true;
|
|
177
180
|
break;
|
|
178
181
|
}
|
|
@@ -229,11 +232,8 @@ export function makeProbeInstructionSources() {
|
|
|
229
232
|
return await Promise.all(sources.map(async ({ path }) => {
|
|
230
233
|
if (!isAbsolute(path))
|
|
231
234
|
return { path, contentHash: null };
|
|
232
|
-
const coords =
|
|
233
|
-
|
|
234
|
-
return { path, contentHash: null };
|
|
235
|
-
}
|
|
236
|
-
const raw = await readRawCapped(path, INSTRUCTION_READ_CAP);
|
|
235
|
+
const coords = instructionChainCoordinates(path);
|
|
236
|
+
const raw = coords ? await readChainMember(coords.root, coords.rel, INSTRUCTION_READ_CAP) : await readRawCapped(path, INSTRUCTION_READ_CAP);
|
|
237
237
|
return { path, contentHash: raw === null ? null : instructionHash(raw) };
|
|
238
238
|
}));
|
|
239
239
|
}
|
package/dist/run-local.js
CHANGED
|
@@ -8,8 +8,8 @@ import { realpathSync } from "node:fs";
|
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { loadRemoteExec } from "@sema-agent/settings-schema/node";
|
|
11
|
-
import { Runner, uuidv7, parseModelMention, FileStorageBackend, NodeExecutionEnv,
|
|
12
|
-
import {
|
|
11
|
+
import { Runner, uuidv7, parseModelMention, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
|
|
12
|
+
import { singleUserAutoAcceptBaselineApplies } from "./approval.js";
|
|
13
13
|
import { createBrain } from "./brain.js";
|
|
14
14
|
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs, } from "./deployment-governance.js";
|
|
15
15
|
import { applyRuntimeGovernance } from "./runtime-governance.js";
|
|
@@ -18,7 +18,6 @@ import { taskWallClockSec } from "./task-workflow.js";
|
|
|
18
18
|
import { applyCatalogToSource } from "./capabilities/center-prompts.js";
|
|
19
19
|
import { validatePromptsDomain, CORE_ENGINE_VERSION } from "./prompts-domain-validate.js";
|
|
20
20
|
import { buildUnpricedCeilingNotice, loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./config.js";
|
|
21
|
-
import { clearHostedEvidenceEnv } from "./hosted-posture.js";
|
|
22
21
|
import { buildStoreOpenRefusalTail } from "./plugins/store-open-refusal.js";
|
|
23
22
|
import { acquireLocalBackendWithWait } from "./plugins/store-lock-wait.js";
|
|
24
23
|
import { createConfigProvider } from "./config-provider.js";
|
|
@@ -49,6 +48,7 @@ import { fatalExit } from "./boot/fatal-exit.js";
|
|
|
49
48
|
import { installProcessExitBoundary } from "./boot/exit-boundary.js";
|
|
50
49
|
import { createMetrics } from "./observability/metrics.js";
|
|
51
50
|
import { installSecretEnvScrubRecorder } from "./observability/secret-env-scrub.js";
|
|
51
|
+
import { createHostLspManager } from "./host-lsp-manager.js";
|
|
52
52
|
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
53
53
|
export function parseArgs(argv) {
|
|
54
54
|
let objective;
|
|
@@ -176,7 +176,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
176
176
|
process.env.CONFIG_PROVIDER = "local";
|
|
177
177
|
process.env.SESSION_BACKEND = "memory";
|
|
178
178
|
process.env.REQUIRE_PRINCIPAL = "false";
|
|
179
|
-
clearHostedEvidenceEnv();
|
|
180
179
|
const root = args.root ?? process.env.CONFIG_LOCAL_DIR ?? process.env.AGENT_DATA_DIR ?? join(homedir(), ".ai-agent");
|
|
181
180
|
if (args.root)
|
|
182
181
|
process.env.CONFIG_LOCAL_DIR = args.root;
|
|
@@ -197,7 +196,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
197
196
|
for (const de of r.domainErrors ?? [])
|
|
198
197
|
logger.warn("config_domain_invalid", { domain: de.domain, error: de.error, note: "domain fell to schema defaults; other domains applied normally" });
|
|
199
198
|
effective = r.effective;
|
|
200
|
-
applyEffective(config, r.effective, logger);
|
|
199
|
+
applyEffective(config, r.effective, logger, { leg: "boot" });
|
|
201
200
|
if (effective?.mcp)
|
|
202
201
|
config.mcpServers = resolveMcpServers(effective.mcp, logger);
|
|
203
202
|
if (effective?.a2a)
|
|
@@ -244,7 +243,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
244
243
|
}
|
|
245
244
|
const ungatedCompensation = buildOnlySensitiveBaselineWarning(config, {
|
|
246
245
|
durableEnabled: false,
|
|
247
|
-
singleUserAutoAcceptBaseline:
|
|
246
|
+
singleUserAutoAcceptBaseline: singleUserAutoAcceptBaselineApplies(config),
|
|
248
247
|
});
|
|
249
248
|
if (ungatedCompensation)
|
|
250
249
|
logger.warn(ungatedCompensation.event, ungatedCompensation.fields);
|
|
@@ -287,7 +286,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
287
286
|
...(hostRx.commandTimeoutMs != null ? { commandTimeoutMs: hostRx.commandTimeoutMs } : {}),
|
|
288
287
|
})
|
|
289
288
|
: undefined;
|
|
290
|
-
const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ?
|
|
289
|
+
const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ? createHostLspManager((event, fields) => logger.info(event, fields)) : undefined;
|
|
291
290
|
const memoryEngine = memoryEngineBackendFor(config, root, memoryEngineIncident);
|
|
292
291
|
let memorySync;
|
|
293
292
|
if (config.memorySync && memoryEngine) {
|
package/dist/runs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { runWithVerification, runCascade, defaultTaskRegistry, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
|
|
1
|
+
import { runWithVerification, runCascade, defaultTaskRegistry, taskNotificationDedupKey, DURABLE_AGENT_HANDLE_RE } from "@sema-agent/core";
|
|
2
2
|
import { failureCodeOf, sealResultForExit, outwardPlane } from "./terminal.js";
|
|
3
3
|
import { GOVERNANCE_HTTP_STATUS } from "./governance-http-status.js";
|
|
4
4
|
import { redactErrorMessage } from "./observability/run-terminal.js";
|
|
@@ -12,7 +12,7 @@ import { CANCELLED_CODE, mintCancelledResult } from "./run-cancel-context.js";
|
|
|
12
12
|
import { createApprovalCardEmitter, resolveApprovalLeg } from "./tool-approval.js";
|
|
13
13
|
import { fleetRunResiduals, isFleetAgentTerminalNotification } from "./fleet/fleet-bus.js";
|
|
14
14
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
15
|
-
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry,
|
|
15
|
+
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "./orchestration/workflow-completion-inbox.js";
|
|
16
16
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
17
17
|
import { errorText as errText } from "./observability/err-text.js";
|
|
18
18
|
import { LIVENESS_HEARTBEAT_MS } from "./config-invariants.js";
|
|
@@ -256,8 +256,9 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
256
256
|
const { type, ...rest } = frame;
|
|
257
257
|
const f = frame;
|
|
258
258
|
const write = append(type, rest);
|
|
259
|
-
|
|
260
|
-
|
|
259
|
+
const fKey = f.type === "task_notification" ? taskNotificationStreamKeyOf(f) : undefined;
|
|
260
|
+
if (fKey !== undefined)
|
|
261
|
+
notifiedKeys.set(fKey, write.then(() => true, () => false));
|
|
261
262
|
return write;
|
|
262
263
|
}, completionDiagLog ? { route: "bg-run-start", connection: "durable-append", log: completionDiagLog } : undefined);
|
|
263
264
|
const runBody = async () => {
|
|
@@ -285,7 +286,7 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
285
286
|
void workflowCompletionInbox.enqueue(taskNotificationInboxEntry(spec.sessionId, elicitOwner ?? owner, n, Date.now(), taskId, opts?.priority)).catch((err) => completionDiagLog?.("park_enqueue_failed", { route: "bg-run-start", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
|
|
286
287
|
}
|
|
287
288
|
else if (legLive) {
|
|
288
|
-
const key =
|
|
289
|
+
const key = taskNotificationDedupKey(n);
|
|
289
290
|
if (notifiedKeys.get(key) === undefined) {
|
|
290
291
|
notifiedKeys.set(key, append("task_notification", taskNotificationEventData({ notification: n, ...(opts?.priority !== undefined ? { priority: opts.priority } : {}) })).then(() => true, () => false));
|
|
291
292
|
}
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
* `allowObservers` 是 `=== true` opt-in,缺席已等于关;`allowMemoryOptOut` 是隐私轴的 FAULT-TRANSIT 契约
|
|
55
55
|
* (core types.d.ts 逐字禁止在 fault 上自造一个显式值)—— 铸 false 就是把故障臂默默换成判决臂。
|
|
56
56
|
*
|
|
57
|
+
* 🔐 [ref] E-15(7.99.0 合并复审 F2):缓存、在飞去重与 ETag 都在**凭证身份的箱**里(`center-credential.ts`
|
|
58
|
+
* `createIdentityBins`,org 记忆目录同一只)——此前只按 principal 键,同机换账号后 TTL 内同一 principal 吃上一个
|
|
59
|
+
* 人的授予、搭上一个人的在飞请求、带上一个人的 etag 去换 304。在飞期间换了人,答复由传输层丢弃
|
|
60
|
+
* (`http-client.ts` `requestPrincipalView`),按这一次取数失败走下面的 fail-closed 臂(降级项落在发起那一箱)。
|
|
61
|
+
*
|
|
57
62
|
* The resolver maps center's `EntitlementRuntimeCaps` → the SUBSET core enforces ({@link RuntimeCaps}): center
|
|
58
63
|
* also carries `allowUltracode` (a shell/UX reasoning-tier cap the engine has no primitive for), which we DROP —
|
|
59
64
|
* this seam feeds the ENGINE, and `allowUltracode` is enforced at the service settings-resolution layer, not here.
|
|
@@ -61,14 +66,15 @@
|
|
|
61
66
|
import type { RuntimeCaps } from "@sema-agent/core";
|
|
62
67
|
import type { EntitlementRuntimeCaps } from "@sema-agent/settings-schema";
|
|
63
68
|
import { fetchPrincipalCaps, type ExecutionRuling } from "./config-center/facade.js";
|
|
69
|
+
import type { CenterEndpoint } from "./config-center/center-request.js";
|
|
64
70
|
import type { ScenarioRuling } from "./capabilities/scenarios.js";
|
|
65
71
|
import type { ServiceConfig } from "./config-types.js";
|
|
66
72
|
import { type Logger } from "./observability/logger.js";
|
|
67
73
|
/** test-only: reset the once-per-process trace so each test observes a fresh "first hit". */
|
|
68
74
|
export declare function resetExecutionLaneDefaultTraceForTest(): void;
|
|
69
75
|
export interface EntitlementsResolverOpts {
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
/** S-651:中心端点(基址 + 凭证**来源**;每次抓取取一次值,不持有启动快照)。 */
|
|
77
|
+
center: CenterEndpoint;
|
|
72
78
|
/** This worker's name (`SEMA_REGISTRY_WORKER`), passed as `?worker=` so a worker-scoped `wpt_<self>` token can
|
|
73
79
|
* resolve its OWN worker's principal caps (caps-only); harmless on a full token (the caps value
|
|
74
80
|
* is worker-independent). Omitted ⇒ no `?worker=` (a full-token, non-fleet deployment). */
|
|
@@ -136,12 +142,6 @@ export declare function toCoreRuntimeCaps(ent: EntitlementRuntimeCaps | null | u
|
|
|
136
142
|
* 所以本谓词为 false **不再**意味着 auto 不武装;「会不会武装」由 `auto-mode-face.ts` 的三项合取答。
|
|
137
143
|
*/
|
|
138
144
|
export declare function centerEntitlementSourceWired(config: Pick<ServiceConfig, "configCenter">): boolean;
|
|
139
|
-
/** A MISCONFIG diagnostic (review MEDIUM): a worker-scoped `wpt_<self>` pull token REQUIRES `?worker=<self>` to
|
|
140
|
-
* resolve per-principal caps — so a `wpt_` token with NO `SEMA_REGISTRY_WORKER` set will 403
|
|
141
|
-
* on every caps fetch → fail-closed DENY ALL workflows for ALL principals (correct degrade, but otherwise silent
|
|
142
|
-
* bar per-task warns). True ⇒ the caller should log a one-shot BOOT warning so the misconfig is diagnosable.
|
|
143
|
-
* (The orchestrator normally injects SEMA_REGISTRY_WORKER alongside a wpt_ token, so this is a hand-misconfig.) */
|
|
144
|
-
export declare function scopedTokenNeedsWorker(token: string, worker: string | undefined): boolean;
|
|
145
145
|
/** The two per-principal faces served off ONE caps fetch + cache. */
|
|
146
146
|
export interface PrincipalEntitlementsClient {
|
|
147
147
|
/** core's `RunnerDeps.runtimeCapsResolver` seam — FAIL-CLOSED on error (deny workflows/fork/autoMode;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fetchPrincipalCaps } from "./config-center/facade.js";
|
|
2
|
+
import { createIdentityBins } from "./center-credential.js";
|
|
2
3
|
import { HttpError } from "./security.js";
|
|
3
4
|
import { createLogger } from "./observability/logger.js";
|
|
4
5
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
@@ -54,9 +55,6 @@ export function toCoreRuntimeCaps(ent) {
|
|
|
54
55
|
export function centerEntitlementSourceWired(config) {
|
|
55
56
|
return config.configCenter !== undefined && !config.configCenter.dryRun;
|
|
56
57
|
}
|
|
57
|
-
export function scopedTokenNeedsWorker(token, worker) {
|
|
58
|
-
return token.startsWith("wpt_") && !worker;
|
|
59
|
-
}
|
|
60
58
|
export function gateExecutionLane(ruling, ownLane, logger = defaultLogger) {
|
|
61
59
|
if (!ruling) {
|
|
62
60
|
traceExecutionLaneDefault("no_ruling", ownLane, logger);
|
|
@@ -80,11 +78,14 @@ export function createPrincipalEntitlementsClient(opts) {
|
|
|
80
78
|
const fetchCaps = opts.fetchCaps ?? fetchPrincipalCaps;
|
|
81
79
|
const now = opts.now ?? (() => Date.now());
|
|
82
80
|
const onError = opts.onError ?? ((err, principal) => defaultLogger.warn("runtime_caps_resolve_failed", { principal, err: String(err) }));
|
|
83
|
-
const
|
|
84
|
-
|
|
81
|
+
const binNow = createIdentityBins(opts.center.credential, (credential) => ({
|
|
82
|
+
center: { baseUrl: opts.center.baseUrl, credential },
|
|
83
|
+
cache: new Map(),
|
|
84
|
+
inflight: new Map(),
|
|
85
|
+
}));
|
|
85
86
|
const readMintedDeny = new WeakSet();
|
|
86
87
|
const MAX_CACHE = 10_000;
|
|
87
|
-
const putCache = (key, entry) => {
|
|
88
|
+
const putCache = (cache, key, entry) => {
|
|
88
89
|
if (cache.size >= MAX_CACHE) {
|
|
89
90
|
const t = now();
|
|
90
91
|
for (const [k, v] of cache)
|
|
@@ -100,15 +101,16 @@ export function createPrincipalEntitlementsClient(opts) {
|
|
|
100
101
|
return entry;
|
|
101
102
|
}
|
|
102
103
|
async function loadRaw(principal, recordDeny) {
|
|
103
|
-
const
|
|
104
|
+
const bin = binNow();
|
|
105
|
+
const cached = bin.cache.get(principal);
|
|
104
106
|
if (cached && cached.expiresAt > now() && !(recordDeny && readMintedDeny.has(cached)))
|
|
105
107
|
return cached;
|
|
106
|
-
const dup = inflight.get(principal);
|
|
108
|
+
const dup = bin.inflight.get(principal);
|
|
107
109
|
if (dup)
|
|
108
110
|
return dup;
|
|
109
111
|
const p = (async () => {
|
|
110
112
|
try {
|
|
111
|
-
const r = await fetchCaps(
|
|
113
|
+
const r = await fetchCaps(bin.center, principal, cached?.etag, fetchImpl, opts.worker);
|
|
112
114
|
if (r === null) {
|
|
113
115
|
const entry = {
|
|
114
116
|
caps: cached?.caps,
|
|
@@ -117,7 +119,7 @@ export function createPrincipalEntitlementsClient(opts) {
|
|
|
117
119
|
...(cached?.etag ? { etag: cached.etag } : {}),
|
|
118
120
|
expiresAt: now() + ttlMs,
|
|
119
121
|
};
|
|
120
|
-
putCache(principal, entry);
|
|
122
|
+
putCache(bin.cache, principal, entry);
|
|
121
123
|
return entry;
|
|
122
124
|
}
|
|
123
125
|
if (r.executionDrift) {
|
|
@@ -133,7 +135,7 @@ export function createPrincipalEntitlementsClient(opts) {
|
|
|
133
135
|
...(r.etag ? { etag: r.etag } : {}),
|
|
134
136
|
expiresAt: now() + ttlMs,
|
|
135
137
|
};
|
|
136
|
-
putCache(principal, entry);
|
|
138
|
+
putCache(bin.cache, principal, entry);
|
|
137
139
|
return entry;
|
|
138
140
|
}
|
|
139
141
|
catch (err) {
|
|
@@ -147,7 +149,7 @@ export function createPrincipalEntitlementsClient(opts) {
|
|
|
147
149
|
const entry = { caps: { allowWorkflows: false, allowFork: false, autoMode: false, autoModeDenySource: "org" }, expiresAt: now() + denyTtlMs };
|
|
148
150
|
if (!recordDeny)
|
|
149
151
|
readMintedDeny.add(entry);
|
|
150
|
-
putCache(principal, entry);
|
|
152
|
+
putCache(bin.cache, principal, entry);
|
|
151
153
|
try {
|
|
152
154
|
onError(e, principal);
|
|
153
155
|
}
|
|
@@ -155,10 +157,10 @@ export function createPrincipalEntitlementsClient(opts) {
|
|
|
155
157
|
return entry;
|
|
156
158
|
}
|
|
157
159
|
finally {
|
|
158
|
-
inflight.delete(principal);
|
|
160
|
+
bin.inflight.delete(principal);
|
|
159
161
|
}
|
|
160
162
|
})();
|
|
161
|
-
inflight.set(principal, p);
|
|
163
|
+
bin.inflight.set(principal, p);
|
|
162
164
|
return p;
|
|
163
165
|
}
|
|
164
166
|
return {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* path-prefixed commands all bypass the argv[0] match (they route to `ask`, fail-closed, but are not blocked by
|
|
24
24
|
* name). Real isolation is the `executionEnv` sandbox. Same caveat core documents on `createCoarseCommandNamePolicy`.
|
|
25
25
|
*/
|
|
26
|
-
import { type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
26
|
+
import { type ShellGateDoctrine, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
27
27
|
import { GovernanceConfig } from "@sema-agent/settings-schema";
|
|
28
28
|
import { type GovernanceAskMarks } from "./governance-ask-marks.js";
|
|
29
29
|
/** 运维 autonomy 四档的**闭集**(同上)。`config.ts` 的 `parseAutonomy` 也从这里读 —— 四档词表全仓一份。 */
|
|
@@ -142,13 +142,60 @@ export declare function autonomyOverrides(autonomy: Autonomy | undefined): Parti
|
|
|
142
142
|
* 并不要求 always),之后运维把 autonomy 热改成 `ask`,那条老行会被标成治理出身 —— 五个前提要**同时**
|
|
143
143
|
* 成立,且该行仍未决。
|
|
144
144
|
* 根治要**在 mint 那一刻把出身写进行里**(core 的 `CheckpointGate` 属主面,不是本仓能单方面做的);
|
|
145
|
-
*
|
|
146
|
-
*
|
|
145
|
+
* 在那之前这一位的定位是**分诊提示**,不参与任何门/CAS/resume 判定 —— 唯一的例外是**只收窄**的那一个读者:
|
|
146
|
+
* 耐久 `/decide` 腿的会话级 remember 可记性(NP-1,7.100.0,`tool-approval.ts` 的 `parkRowStandingAnswerFacts`)。
|
|
147
|
+
* 在那里两个失真方向的代价都不越过修前:在场向 ⇒ 一次 `remember:"session"` 只放行本次、不铸会话免询问(安全
|
|
148
|
+
* 方向,代价 = 下一次再问一遍);缺席向 ⇒ 照旧可记(= NP-1 之前的行为)。它从不**放宽**任何裁决。
|
|
149
|
+
* 登记在此,不加特征化测试(把已知残留钉成契约是另一种病)。
|
|
147
150
|
*/
|
|
148
151
|
export declare function governanceMandatesShellGateAlways(governance: {
|
|
149
152
|
autonomy?: Autonomy;
|
|
150
153
|
manualModeShellGate?: "always" | "classify";
|
|
151
154
|
}): boolean;
|
|
155
|
+
/** [ref] 归因的部署侧入参(两个治理旋钮;判据属主见 {@link governanceMandatesShellGateAlways})。 */
|
|
156
|
+
export interface GovernancePosture {
|
|
157
|
+
autonomy?: Autonomy;
|
|
158
|
+
manualModeShellGate?: "always" | "classify";
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* [ref]:推「这道门是运维治理层下的吗」。判据是**合取**,两半各管一件事:
|
|
162
|
+
*
|
|
163
|
+
* 行上的取证格 `gate.riskDescriptor.shellGateDoctrine === "always"`
|
|
164
|
+
* ∧ 本部署的治理层本来就要求 always({@link governanceMandatesShellGateAlways},那里写着为什么单看行不够 ——
|
|
165
|
+
* SUP 路由姿态同样产 `"always"`)
|
|
166
|
+
*
|
|
167
|
+
* 第一半:core 在 mint 这道门时把「当时活着的 doctrine」如实写进 `riskDescriptor.shellGateDoctrine`
|
|
168
|
+
* (2026-08-05 取证裁定;`prepare-task` 只在**被 shell 门铸**的 ask 上写它),所以持久行自己带着证据 ——
|
|
169
|
+
* park 腿天然跨副本、跨重启,活卡腿那张 ALS 标记表在这里结构上够不着。
|
|
170
|
+
* 第二半:把「有效档 always」收窄到「治理层是真成因」,否则一条 SUP 路由出来的门会被谎报成治理强制。
|
|
171
|
+
*
|
|
172
|
+
* ⚠️ 残留(双向失真的窄在场向 + 根治路径)与「只收窄」的那一个裁决读者,都登记在
|
|
173
|
+
* {@link governanceMandatesShellGateAlways} 的顶注,别在这里复述。
|
|
174
|
+
*
|
|
175
|
+
* 🔴 `"classify"` 档**刻意不标**:那一档由 core 的分类器逐调用裁决,一次 shell ask 可能出自分类器(治理)
|
|
176
|
+
* 也可能出自 `APPROVAL_REQUIRE` 这类别的门,行上无从分辨 —— 与活卡腿同一条「宁缺毋假」纪律。
|
|
177
|
+
*
|
|
178
|
+
* 🔴 **单一属主,四个读者**(不在任何读面另写一份判据 —— 这一位恰恰是靠「行上取证 ∧ 部署姿态」的合取才不
|
|
179
|
+
* 谎报的,两份必漂):409 体的待批门材料(`http/active-run-conflict.ts`)、两条 durable 读面
|
|
180
|
+
* `GET /v1/approvals` / `/v1/approvals/stream` 的 `projectPendingForWire`([ref]②)、耐久 `/decide` 腿的
|
|
181
|
+
* remember 可记性(NP-1,经 `tool-approval.ts` 的 `parkRowStandingAnswerFacts`)。
|
|
182
|
+
* 住在本文件而不是 `http/active-run-conflict.ts` 的理由:它的另一半判据属主就在这里,而第四个读者
|
|
183
|
+
* (`tool-approval.ts`)从 `http/` 那侧值引会闭合一条 ESM 值环(`active-run-conflict` → `checkpoint-store-sql` →
|
|
184
|
+
* `tool-approval`),本文件是三者共同的叶。
|
|
185
|
+
*
|
|
186
|
+
* ⚠️ **askId 为什么不在这里**([ref] 的另一半,如实记账):待批 ask 的 `askId` 只活在 `approval_ask` 行上
|
|
187
|
+
* (checkpoint 行没有这一列,core 的 `CheckpointGate`/`PendingAction`/`Checkpoint` 顶层都不带它)。反向
|
|
188
|
+
* (checkpoint → ask)今天**没有读口**:两条 list 读口都硬过滤 `state='STREAM_PENDING'`,而 park 完的行是
|
|
189
|
+
* `PARKED`;唯一精确的连接键是 ask 行上的 `gate_token`,要按它反查得给店加一个新读口(SQL 面 = 双库集成
|
|
190
|
+
* 门)。派生 `deriveAskId(...)` 也不行:它吃 `legKey`(= 上一腿 resume token 的摘要)与 `parentToolCallId`,
|
|
191
|
+
* 这两维从 checkpoint 行推不出来,二腿/子代形上会算出一个**错**的 id —— 在「等的谁」这条通道上,错 id
|
|
192
|
+
* 比缺席坏得多(`http/active-run-conflict.ts` 的 `resumeEntryForGate` 的 default 臂是同一条纪律)。故诚实缺席。
|
|
193
|
+
*/
|
|
194
|
+
export declare function governanceOriginOf(gate: {
|
|
195
|
+
riskDescriptor?: {
|
|
196
|
+
shellGateDoctrine?: ShellGateDoctrine;
|
|
197
|
+
};
|
|
198
|
+
} | undefined, governance: GovernancePosture | undefined): true | undefined;
|
|
152
199
|
/** spec 序里的秩(0 起,越大越严)。入参收 `string`(fail-loud 边界:schema `trustRank` 同款姿势)——词表外 ⇒
|
|
153
200
|
* RangeError,绝不 undefined/NaN 让比较静默成 false。导出只为对表钉(test/safety-merge-spec-consumption.test.ts,
|
|
154
201
|
* [ref] 件4)——运行时消费仍走上表(键类型闭集)。 */
|
|
@@ -116,6 +116,11 @@ export function autonomyOverrides(autonomy) {
|
|
|
116
116
|
export function governanceMandatesShellGateAlways(governance) {
|
|
117
117
|
return autonomyOverrides(governance.autonomy).shellGate === "always" || governance.manualModeShellGate === "always";
|
|
118
118
|
}
|
|
119
|
+
export function governanceOriginOf(gate, governance) {
|
|
120
|
+
if (gate?.riskDescriptor?.shellGateDoctrine !== "always")
|
|
121
|
+
return undefined;
|
|
122
|
+
return governanceMandatesShellGateAlways(governance ?? {}) ? true : undefined;
|
|
123
|
+
}
|
|
119
124
|
function createGovernanceAskMarkingPolicy(inner, marksOf) {
|
|
120
125
|
const note = (d, req) => {
|
|
121
126
|
marksOf()?.setMarked(req.toolCallId, d.action === "ask");
|
package/dist/sealed-key.js
CHANGED
|
@@ -3,6 +3,8 @@ import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import _sodium from "libsodium-wrappers";
|
|
5
5
|
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
6
|
+
import { createCenterCredentialSource } from "./center-credential.js";
|
|
7
|
+
import { centerFetch } from "./config-center/center-request.js";
|
|
6
8
|
export const SEALED_KEY_ALG = "libsodium-sealedbox-x25519";
|
|
7
9
|
const SEAL_OVERHEAD_BYTES = 48;
|
|
8
10
|
const BASE64_STRICT = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
@@ -114,13 +116,7 @@ export async function reportExecutionPublicKey(opts) {
|
|
|
114
116
|
return;
|
|
115
117
|
}
|
|
116
118
|
try {
|
|
117
|
-
const
|
|
118
|
-
const res = await doFetch(`${baseUrl.replace(/\/$/, "")}/api/config/execution-keys`, {
|
|
119
|
-
method: "POST",
|
|
120
|
-
headers: { authorization: `Bearer ${adminToken}`, "content-type": "application/json" },
|
|
121
|
-
body: JSON.stringify({ publicKey: key.publicKey, publicKeyId: key.publicKeyId, ...(notes ? { notes } : {}) }),
|
|
122
|
-
signal: AbortSignal.timeout(10_000),
|
|
123
|
-
});
|
|
119
|
+
const { res } = await centerFetch({ baseUrl, credential: createCenterCredentialSource({ from: "env", token: adminToken }, undefined) }, { kind: "execution-keys" }, { method: "POST", json: { publicKey: key.publicKey, publicKeyId: key.publicKeyId, ...(notes ? { notes } : {}) }, timeoutMs: 10_000, ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}) });
|
|
124
120
|
if (res.ok) {
|
|
125
121
|
let idempotent = false;
|
|
126
122
|
try {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 7.99.0 合并复审 F7(web-admin [ref] 实测;安全轴;DEBTS S-684)—— 子进程 env 交出去之前的**本仓**密钥剥除。
|
|
3
|
+
*
|
|
4
|
+
* ## 病
|
|
5
|
+
* 本仓两条子进程腿 —— 模型可驱动的 host shell(`inheritEnv:"scrub"` 缺省腿,`plugins/remote-env-host.ts`)与
|
|
6
|
+
* project-memory 的 git 子进程(`project-memory.ts`)—— 此前只调 core 的 `scrubSecretEnv`。它按键名**形状**判
|
|
7
|
+
* (`*_KEY` / `*_TOKEN` / `*_SECRET` …),而本仓自己的凭据键里有形状规则认不出的:`SERVICE_AUTH_TOKENS`(复数,
|
|
8
|
+
* per-system 凭据表)、`APPROVAL_HMAC_KEYS`(直连门 HMAC 密钥集)、`OTEL_EXPORTER_OTLP_HEADERS`(常载鉴权头)⇒
|
|
9
|
+
* 原样进子进程,模型选的一句 `printenv` 就读得到。
|
|
10
|
+
*
|
|
11
|
+
* ## 规则(一句)
|
|
12
|
+
* 本仓**知道**哪些 env 键是凭据 —— 配置目录(`config-catalog.ts`)里 `type:"secret"` 的每一行,从目录机器派生,
|
|
13
|
+
* 不手抄第二份名单 —— 这些键按名剥;其余照旧交 core 按形剥。{@link scrubServerSecrets} = 目录派生剥除 ∘ core
|
|
14
|
+
* `scrubSecretEnv`,本仓三条子进程腿(host shell / git 子进程 / 本地语言服务器,后者见 `host-lsp-manager.ts`)都只调它。
|
|
15
|
+
* core 形状规则的缺口由 core 源头修 —— 7.30.1 #1072 已补 `KEYS|TOKENS|PASSWORDS`(S-616 提货),目录臂独担的只剩
|
|
16
|
+
* `OTEL_EXPORTER_OTLP_HEADERS`(`*_HEADERS` 不在形状规则射程,是边界不是缺陷票)。本层不改 core 的判据,也不自造
|
|
17
|
+
* 第二份形状规则。目录里加一个 secret 型键,三条腿当场就剥它。
|
|
18
|
+
*
|
|
19
|
+
* ## 不变的面
|
|
20
|
+
* `inheritEnv:"all"`(显式全继承)与显式 allowlist 两形不经本函数,行为逐字不变;调用方显式传入的 env
|
|
21
|
+
* (`cfg.env` / 单次命令的 `env`)照旧原样覆盖在剥除结果之上。
|
|
22
|
+
*/
|
|
23
|
+
import { type SecretEnvFinding } from "@sema-agent/core";
|
|
24
|
+
/** 目录派生臂的一条剥除记录(形同 core 的 `SecretEnvFinding`:键名 / 规则 / 置信度 / 来源;值永不携带)。
|
|
25
|
+
* `source` 用本仓自己的词 `server-config-catalog`,与 core 的 `secret-env` 分开 —— 读观测面的人要分得清
|
|
26
|
+
* 「形状规则剥的」与「本仓目录点名剥的」(后者多出来的那部分正是 core 形状规则的缺口)。 */
|
|
27
|
+
export interface CatalogSecretEnvFinding {
|
|
28
|
+
readonly key: string;
|
|
29
|
+
readonly kind: "config-catalog";
|
|
30
|
+
readonly confidence: "high";
|
|
31
|
+
readonly source: "server-config-catalog";
|
|
32
|
+
}
|
|
33
|
+
/** 本仓两条子进程腿的剥除记录:core 的形状臂 ∪ 本仓的目录臂。 */
|
|
34
|
+
export type ServerSecretEnvFinding = SecretEnvFinding | CatalogSecretEnvFinding;
|
|
35
|
+
/** 配置目录里 `type:"secret"` 的 env 名(大写 —— Windows 的 env 名不分大小写,按大写比才不会漏)。 */
|
|
36
|
+
export declare const CATALOG_SECRET_ENV_NAMES: ReadonlySet<string>;
|
|
37
|
+
/**
|
|
38
|
+
* 先交 core 按形剥,再按目录名剥它漏下的。`findings` 是调用方持有的收集器(交给 `recordSecretEnvScrub` 留痕):
|
|
39
|
+
* 形状臂剥掉的记 core 的 finding;目录臂只记**形状臂漏下、被目录点名剥掉**的键 —— 于是既有的形状臂计数不变,
|
|
40
|
+
* 目录臂的计数恰好就是 core 形状规则的缺口读数。
|
|
41
|
+
*/
|
|
42
|
+
export declare function scrubServerSecrets(env: NodeJS.ProcessEnv, findings: ServerSecretEnvFinding[]): NodeJS.ProcessEnv;
|
|
43
|
+
//# sourceMappingURL=server-secret-env.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { scrubSecretEnv } from "@sema-agent/core";
|
|
2
|
+
import { CONFIG_CATALOG } from "./config-catalog.js";
|
|
3
|
+
export const CATALOG_SECRET_ENV_NAMES = new Set(CONFIG_CATALOG.filter((spec) => spec.type === "secret").map((spec) => spec.name.toUpperCase()));
|
|
4
|
+
export function scrubServerSecrets(env, findings) {
|
|
5
|
+
const shapeFindings = [];
|
|
6
|
+
const shaped = scrubSecretEnv(env, shapeFindings);
|
|
7
|
+
findings.push(...shapeFindings);
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const key of Object.keys(shaped)) {
|
|
10
|
+
if (CATALOG_SECRET_ENV_NAMES.has(key.toUpperCase())) {
|
|
11
|
+
findings.push({ key, kind: "config-catalog", confidence: "high", source: "server-config-catalog" });
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
out[key] = shaped[key];
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=server-secret-env.js.map
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -264,7 +264,7 @@ export declare const MAX_SETTINGS_ENV_VALUE_CHARS = 32768;
|
|
|
264
264
|
export declare const MAX_SETTINGS_ENV_KEY_CHARS = 256;
|
|
265
265
|
/** 本 service 真正**消费**的 `settings` 顶层键 —— 闭集。每一个都有消费点:
|
|
266
266
|
* `env`/`hooks`/`model`/`outputStyle`/`permissions`/`ultracode` 与两个 `skillListing*` 在
|
|
267
|
-
* {@link parseTaskSettings};`webSearch` 在 `capabilities/scenarios.ts`(`
|
|
267
|
+
* {@link parseTaskSettings};`webSearch` 在 `capabilities/scenarios.ts`(判决 `judgeRequestWebSearch`,单用户闸;形错在受理面 400,S-199)。
|
|
268
268
|
* 加键必须同时加消费点,否则就是往闭集里塞一个新的静默丢。
|
|
269
269
|
*
|
|
270
270
|
* 🔴 两个 `skillListing*`([ref],7.77.0)= **CC settings 同名键**,值域抄 CC 的 schema 声明
|