@sema-agent/server 7.127.2 → 7.129.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 +130 -0
- package/USAGE.md +137 -20
- package/dist/approval-card.d.ts +53 -26
- package/dist/approval-card.js +40 -11
- package/dist/approval-crash-convergence.js +4 -4
- package/dist/audit.js +4 -1
- package/dist/bake-runner/main.js +2 -1
- package/dist/boot/execution-lane.d.ts +11 -0
- package/dist/boot/execution-lane.js +6 -0
- package/dist/boot/memory-consolidation.d.ts +46 -16
- package/dist/boot/memory-consolidation.js +15 -8
- package/dist/boot/resolve-spec.js +3 -1
- package/dist/boot/side-query-lane.d.ts +7 -20
- package/dist/boot/side-query-lane.js +0 -24
- package/dist/boot/stage-01-config.js +3 -0
- package/dist/boot/stage-06-runners.js +1 -1
- package/dist/boot/stores.js +2 -2
- package/dist/boot/web-admin.js +31 -20
- package/dist/boot/webfetch-summarize-lane.d.ts +10 -7
- package/dist/boot/webfetch-summarize-lane.js +2 -3
- package/dist/brain.js +6 -9
- package/dist/capabilities/builtin-tools.d.ts +2 -2
- package/dist/capabilities/engine-capabilities.d.ts +14 -0
- package/dist/capabilities/engine-capabilities.js +12 -0
- package/dist/capabilities/mcp-probe.js +11 -5
- package/dist/capability-facts.d.ts +3 -1
- package/dist/capability-facts.js +1 -0
- package/dist/config-catalog.js +1 -1
- package/dist/config-provider.d.ts +17 -4
- package/dist/config-provider.js +8 -6
- package/dist/config-types.d.ts +6 -5
- package/dist/egress-redirect.d.ts +12 -0
- package/dist/egress-redirect.js +7 -0
- package/dist/entry-mode-clamp-notice.d.ts +30 -0
- package/dist/entry-mode-clamp-notice.js +13 -0
- package/dist/fleet/fleet-bus.d.ts +7 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-llm.js +6 -2
- package/dist/hooks/hook-runner.d.ts +1 -0
- package/dist/hooks/hook-runner.js +10 -4
- package/dist/host-lsp-manager.d.ts +3 -3
- package/dist/http/admission.js +5 -2
- package/dist/http/resume-legs.js +46 -27
- package/dist/http/route-ctx.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +6 -4
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/session-sync.js +3 -4
- package/dist/http/routes/sessions.js +4 -17
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +4 -3
- package/dist/http/wire-types.d.ts +14 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/main.js +2 -0
- package/dist/mcp-assertion-header.d.ts +89 -0
- package/dist/mcp-assertion-header.js +49 -0
- package/dist/observability/fail-open.d.ts +7 -3
- package/dist/observability/fail-open.js +7 -3
- package/dist/observability/secret-env-scrub.d.ts +37 -3
- package/dist/observability/secret-env-scrub.js +2 -2
- package/dist/parked-decide.js +5 -2
- package/dist/plugins/host-platform.d.ts +43 -13
- package/dist/plugins/host-platform.js +67 -18
- package/dist/plugins/memory-embedder.js +5 -0
- package/dist/plugins/one-time-migrations.d.ts +180 -9
- package/dist/plugins/one-time-migrations.js +315 -3
- package/dist/plugins/pg-pool.js +3 -1
- package/dist/plugins/remote-env-host.d.ts +17 -20
- package/dist/plugins/remote-env-host.js +16 -28
- package/dist/plugins/session-policy-store-sql.d.ts +16 -4
- package/dist/plugins/session-policy-store-sql.js +17 -20
- package/dist/plugins/session-rules-column.d.ts +16 -0
- package/dist/plugins/session-rules-column.js +6 -0
- package/dist/plugins/tidb-pool.js +4 -1
- package/dist/read-face-posture.d.ts +2 -1
- package/dist/request-value-closure.js +1 -1
- package/dist/retired-card-words.d.ts +17 -0
- package/dist/retired-card-words.js +49 -0
- package/dist/rule-entry-list.js +2 -0
- package/dist/run-local.js +15 -6
- package/dist/server-secret-env.d.ts +77 -85
- package/dist/server-secret-env.js +40 -26
- package/dist/session-policy-wire.d.ts +61 -124
- package/dist/session-policy-wire.js +59 -121
- package/dist/session-sync.d.ts +8 -6
- package/dist/session-sync.js +6 -5
- package/dist/spec-fields.d.ts +3 -2
- package/dist/standing-answer-grant.d.ts +17 -9
- package/dist/standing-answer-grant.js +11 -8
- package/dist/task-mcp.d.ts +31 -3
- package/dist/task-mcp.js +42 -4
- package/dist/task-settings.d.ts +6 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +14 -7
- package/dist/tool-approval.js +23 -18
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/project.d.ts +7 -4
- package/dist/trace/project.js +7 -76
- package/dist/trace/tool-output-media.d.ts +10 -4
- package/dist/trace/tool-output-media.js +7 -29
- package/dist/web-admin-client/apply-effective.js +28 -11
- package/dist/web-admin-client/facade.d.ts +2 -2
- package/dist/web-admin-client/facade.js +2 -2
- package/dist/web-admin-client/http-client.d.ts +2 -2
- package/dist/web-admin-client/read-face.d.ts +73 -20
- package/dist/web-admin-client/read-face.js +76 -36
- package/dist/web-admin-client/skills-mcp.d.ts +20 -17
- package/dist/web-admin-client/skills-mcp.js +27 -8
- package/dist/web-admin-client/types.d.ts +18 -0
- package/package.json +3 -3
|
@@ -26,9 +26,10 @@
|
|
|
26
26
|
* 降级成**结构性事实**(这条路上根本没有 wrapper);
|
|
27
27
|
* · `headers` 半边从此有出路(wrapper 形只注得进 `apiKey`,core 的座位 `{apiKey, headers?}` 两半都收);
|
|
28
28
|
* · 判据(`gatewayBaseUrl`)与 resolver 一样**按调用现取** —— 见下面 `createSideQueryLane` 的注。
|
|
29
|
-
* `createPerModelKeyBrain`
|
|
30
|
-
*
|
|
31
|
-
* (
|
|
29
|
+
* `createPerModelKeyBrain` 当时**没删**:它还是 WebFetch 摘要面(`boot/webfetch-summarize-lane.ts`)的 key plane ——
|
|
30
|
+
* core 的 `WebFetchSummarizerOptions` 那时只有 `maxContentChars`。core 7.51.0 给摘要器补上了同一签名的座
|
|
31
|
+
* (`WebFetchSummarizerOptions.getApiKeyAndHeaders`)⇒ 包装退役,四面(任务 / side-query / 记忆整理 / WebFetch 摘要)
|
|
32
|
+
* 都经 core 的座位供给 per-model 凭据,本仓不再在任何一面包 brain。
|
|
32
33
|
*
|
|
33
34
|
* 🔴 **poison(声明的钥匙拿不到)上抛,绝不静默回落网关 key** —— `resolveModelApiKey` 抛
|
|
34
35
|
* `ModelKeyPoisonedError` 是「这个模型配了自己的钥匙(托管密钥解不开 / S-896 起 `apiKeyEnv` 变量缺席)却拿不到」的响亮态;吞成 undefined 等于用共享
|
|
@@ -46,8 +47,9 @@
|
|
|
46
47
|
* 是配对法的单一实现,三只一方 brain(openai / anthropic / open-responses)在 `buildRequest` 里逐次
|
|
47
48
|
* 复判 —— 部署声明了 `config.baseUrl` 而 entry URL 不在那个根上、凭据又只有部署级那一份 ⇒
|
|
48
49
|
* `route.credential_mismatch` 响亮拒;连部署级凭据都没有 ⇒ `route.credential_missing`。**三面同批**
|
|
49
|
-
* (主推理链 / side-query / WebFetch 摘要走的是同一只 brain 栈),旋钮 =
|
|
50
|
-
* `config.baseUrl
|
|
50
|
+
* (主推理链 / side-query / WebFetch 摘要走的是同一只 brain 栈),旋钮 = 给模型自己的凭据(当年还有第二条:不声明
|
|
51
|
+
* `config.baseUrl`,走 core 的 `"unpinned"` 快速起步姿态 —— core 7.51.0 #1531 起该姿态退役,一份部署凭据只配一个根)。
|
|
52
|
+
* ⇒ [ref] 要的「三面同批 + 旋钮」由上游一次交齐。
|
|
51
53
|
*
|
|
52
54
|
* 于是本席的 `isOffRouteBaseUrl` + `recordFailOpen("server.model-key.off-route-model-uses-gateway-key")`
|
|
53
55
|
* 两件**一并撤线**:那条计数的语义是「这一次调用真把网关凭据发给了别家」,而在 5.57 上那次调用根本
|
|
@@ -97,21 +99,6 @@ export interface SideQueryLaneCtx {
|
|
|
97
99
|
export declare function createPerModelAuthSeat(getKeyResolver: () => KeyResolver, opts?: {
|
|
98
100
|
readonly upstream?: SideQuerySpec["getApiKeyAndHeaders"];
|
|
99
101
|
}): PerModelAuthSeat;
|
|
100
|
-
/**
|
|
101
|
-
* per-model key 注入 brain wrapper —— **[ref] 后只服务 WebFetch 摘要面**
|
|
102
|
-
* (`boot/webfetch-summarize-lane.ts`)。side-query 面已换到 core 的 spec 座位(见顶注「[ref] 换装」);
|
|
103
|
-
* core 的 `WebFetchSummarizerOptions` 至今只有 `maxContentChars`(亲验 5.46.0 `dist/tools/web.d.ts`),
|
|
104
|
-
* 那一面**没有座位可换**,只能继续在 brain 层补凭据。
|
|
105
|
-
*
|
|
106
|
-
* 语义(与 `createPerModelAuthSeat` 同一张表,只是注入位在 `options.apiKey`):
|
|
107
|
-
* · `options.apiKey` **已在场** ⇒ 原样透传(上游/装饰器已经定了凭据,本层不抢);
|
|
108
|
-
* · resolver 缺席 / 返回 undefined ⇒ 不注入 ⇒ 网关 key(additive 契约);
|
|
109
|
-
* · resolver 抛(sealed key 中毒)⇒ **上抛**,本次调用整体失败,一个字节都不发。
|
|
110
|
-
*
|
|
111
|
-
* `complete` 在场时同样包一层:core 的 `Brain.complete` 是 compaction/摘要腿的直调口(WebFetch 摘要器
|
|
112
|
-
* 正是**优先**走它),漏包等于给同一个病留第二条路。
|
|
113
|
-
*/
|
|
114
|
-
export declare function createPerModelKeyBrain(brain: Brain, getKeyResolver: () => KeyResolver): Brain;
|
|
115
102
|
/**
|
|
116
103
|
* 装配 side-query 执行席 = 真 `runSideQuery` + core 的 per-model auth 座,models/roles 与 Runner 同源。
|
|
117
104
|
*
|
|
@@ -8,30 +8,6 @@ export function createPerModelAuthSeat(getKeyResolver, opts = {}) {
|
|
|
8
8
|
return resolve ? await resolve({ name: model.name }) : undefined;
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
-
export function createPerModelKeyBrain(brain, getKeyResolver) {
|
|
12
|
-
const seat = createPerModelAuthSeat(getKeyResolver);
|
|
13
|
-
const keyFor = async (model, given) => {
|
|
14
|
-
if (given !== undefined)
|
|
15
|
-
return given;
|
|
16
|
-
return (await seat(model))?.apiKey;
|
|
17
|
-
};
|
|
18
|
-
const stream = async (model, context, options) => {
|
|
19
|
-
const apiKey = await keyFor(model, options?.apiKey);
|
|
20
|
-
return brain.stream(model, context, apiKey === undefined ? options : { ...options, apiKey });
|
|
21
|
-
};
|
|
22
|
-
const complete = brain.complete?.bind(brain);
|
|
23
|
-
return {
|
|
24
|
-
stream,
|
|
25
|
-
...(complete
|
|
26
|
-
? {
|
|
27
|
-
complete: async (model, context, options) => {
|
|
28
|
-
const apiKey = await keyFor(model, options?.apiKey);
|
|
29
|
-
return complete(model, context, apiKey === undefined ? options : { ...options, apiKey });
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
: {}),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
11
|
export function createSideQueryLane(ctx) {
|
|
36
12
|
return (spec) => runSideQuery({
|
|
37
13
|
...spec,
|
|
@@ -14,8 +14,11 @@ import { setRunTerminalLogObserver } from "../observability/run-terminal.js";
|
|
|
14
14
|
import { setRedactionObserver } from "../trace/redact.js";
|
|
15
15
|
import { installOutboundDispatcher } from "../outbound-dispatcher.js";
|
|
16
16
|
import { provisionSemaConfigDir } from "../sema-config-dir.js";
|
|
17
|
+
import { bindOwnCredentialsConfig, declareServerWithheldEnv } from "../server-secret-env.js";
|
|
17
18
|
export function runStage01() {
|
|
19
|
+
declareServerWithheldEnv();
|
|
18
20
|
const config = loadConfig();
|
|
21
|
+
bindOwnCredentialsConfig(config);
|
|
19
22
|
const runStaleFailure = crossInvariant("run-stale-heartbeat").check({ live: config, next: config });
|
|
20
23
|
if (runStaleFailure !== undefined) {
|
|
21
24
|
throw new Error(runStaleFailure);
|
|
@@ -76,7 +76,7 @@ export function runStage06(ctx) {
|
|
|
76
76
|
onIncident: (err) => logger.warn("memory_consolidation_incident", { code: err.code ?? null, detail: err.message }),
|
|
77
77
|
onStopDetail: (audit) => logger.warn("memory_consolidation_stop_detail", audit),
|
|
78
78
|
...(sessionCaptureRecordStore !== undefined ? { captureRecordStore: sessionCaptureRecordStore } : {}),
|
|
79
|
-
writeJudge: createOperatorValveWriteJudge(),
|
|
79
|
+
writeJudge: createOperatorValveWriteJudge({ ruleStoreProvider: permissionRuleProvider, config }),
|
|
80
80
|
onNotice: createEngineNoticeForwarder(logger, defaultEngineNoticeRouter, metrics),
|
|
81
81
|
})
|
|
82
82
|
: undefined;
|
package/dist/boot/stores.js
CHANGED
|
@@ -18,7 +18,7 @@ import { PgRosterStore, TiDBRosterStore, ensurePgRosterSchema, ensureTiDBRosterS
|
|
|
18
18
|
import { LocalTaskAttachmentStore } from "../plugins/local-task-attachment-store.js";
|
|
19
19
|
import { PgTaskAttachmentStore, TiDBTaskAttachmentStore, ensurePgTaskAttachmentSchema, ensureTiDBTaskAttachmentSchema } from "../plugins/task-attachment-store.js";
|
|
20
20
|
import { createSessionStore } from "../plugins/session-store.js";
|
|
21
|
-
import {
|
|
21
|
+
import { runAndLogOneTimeMigrations } from "../plugins/one-time-migrations.js";
|
|
22
22
|
import { createTaskListLane } from "./task-list-lane.js";
|
|
23
23
|
import { ensurePgTaskListSchema, ensureTiDBTaskListSchema } from "../plugins/task-list-store-sql.js";
|
|
24
24
|
import { assertDeviceAuditRebindEventSchema, ensurePgDeviceSchema, ensureTiDBDeviceSchema } from "../plugins/device-store-sql.js";
|
|
@@ -100,7 +100,7 @@ export async function openStores(ctx) {
|
|
|
100
100
|
await assertFileHistorySchema(async (sql) => ({ rows: (await mysqlPool.query(sql))[0] }), "tidb");
|
|
101
101
|
}
|
|
102
102
|
if (backend)
|
|
103
|
-
|
|
103
|
+
await runAndLogOneTimeMigrations(logger, backend);
|
|
104
104
|
if ((process.env.REWIND_SNAPSHOT_MAX_MB ?? "") !== "") {
|
|
105
105
|
logger.warn("rewind_snapshot_max_mb_retired", {
|
|
106
106
|
note: "REWIND_SNAPSHOT_MAX_MB no longer has an effect: the whole-tree snapshot epoch is retired (S-15); per-edited-file history captures only files the agent touches, with no tree walk and no size gate. Remove the env var. Per-blob SQL sizing is governed by SNAPSHOT_BLOB_SQL_MAX_BYTES / object storage as before.",
|
package/dist/boot/web-admin.js
CHANGED
|
@@ -19,13 +19,14 @@ import { centerManagedConfigKeys } from "../web-admin-client/apply-effective.js"
|
|
|
19
19
|
import { createConfigProvider, raceBootFetch, serviceConfigSourceKind, BOOT_FETCH_DEFERRED } from "../config-provider.js";
|
|
20
20
|
import { createKeyResolver } from "../key-resolver.js";
|
|
21
21
|
import { buildSealedKeyPublicFace, ensureSealedKeyStore, reportExecutionPublicKey } from "../sealed-key.js";
|
|
22
|
-
import { applyEffective, applyCenterReadFace, mutateInPlace, logEffectiveDiff,
|
|
22
|
+
import { applyEffective, applyCenterReadFace, buildReadFaceBaseline, buildReadFaceSwapCandidate, restoreReadFaceBaseline, mutateInPlace, logEffectiveDiff, applySkillsManifest, resolveMcpServers, resolveA2aPeers, restartReasons, modelPlaneChanged, withModelPlaneOf, fullyLandedForLkg, fetchPromptArtifact } from "../web-admin-client/facade.js";
|
|
23
23
|
import { buildConfigHandoff, buildRestartSignal, configHandoffMissing } from "../fleet/engine-signal.js";
|
|
24
24
|
import { createMcpRevocationLedger, enabledMcpEntries } from "../web-admin-client/mcp-revocation.js";
|
|
25
25
|
import { buildUnreadConfigFileFields } from "../web-admin-client/read-warnings.js";
|
|
26
26
|
import { CONFIG_DOMAIN_INVALID_NOTE, buildCandidateRejectedFields, candidateRejectionOf, judgeConfigCandidate } from "../web-admin-client/candidate-gate.js";
|
|
27
27
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
28
28
|
import { BOOT_REJUDGE_OF_REFUSED_GROUP, createConfigApplyLedger, modelPlaneCommitted, refusedGroups } from "../web-admin-client/apply-ledger.js";
|
|
29
|
+
import { buildDeploymentPostureSeats } from "./runner-deps.js";
|
|
29
30
|
function lkgReadNote(scope) {
|
|
30
31
|
switch (scope) {
|
|
31
32
|
case "whole":
|
|
@@ -133,6 +134,7 @@ export async function createWebAdminRuntime(ctx) {
|
|
|
133
134
|
...(config.configProvider ? { provider: config.configProvider } : {}),
|
|
134
135
|
...(cc ? { webAdmin: { baseUrl: cc.baseUrl, credential: cc.credential, worker: cc.worker, dryRun: cc.dryRun } } : {}),
|
|
135
136
|
localDir: localRoot,
|
|
137
|
+
...(config.configLkgEnabled ? { skillBodyCacheDir: defaultSkillCacheDir(config) } : {}),
|
|
136
138
|
})
|
|
137
139
|
: undefined;
|
|
138
140
|
const localDirField = configProvider?.kind === "local" ? { dir: configProvider.root } : {};
|
|
@@ -153,16 +155,16 @@ export async function createWebAdminRuntime(ctx) {
|
|
|
153
155
|
});
|
|
154
156
|
}
|
|
155
157
|
};
|
|
156
|
-
const
|
|
158
|
+
const candidateSource = () => {
|
|
157
159
|
if (configProvider === undefined)
|
|
158
|
-
throw new Error("config candidate
|
|
159
|
-
return configProvider
|
|
160
|
+
throw new Error("config candidate handled without a config provider (no pipeline ⇒ nothing was pulled)");
|
|
161
|
+
return configProvider;
|
|
160
162
|
};
|
|
161
163
|
const announceCandidateRejected = (r, verdict, leg, ordinal) => {
|
|
162
164
|
const domains = verdict.domains.join(",");
|
|
163
165
|
if (candidateRejection?.version === r.effective.version && candidateRejection.domains.join(",") === domains)
|
|
164
166
|
return;
|
|
165
|
-
noteCandidateVerdict(ordinal, candidateRejectionOf(r, verdict,
|
|
167
|
+
noteCandidateVerdict(ordinal, candidateRejectionOf(r, verdict, candidateSource().kind, Date.now()));
|
|
166
168
|
logger.warn("config_candidate_rejected", buildCandidateRejectedFields(r, verdict, leg, { lkgVersion: latestEffective?.version ?? "(env)", ...localDirField }));
|
|
167
169
|
};
|
|
168
170
|
const selfReportListeners = [];
|
|
@@ -372,11 +374,15 @@ export async function createWebAdminRuntime(ctx) {
|
|
|
372
374
|
}
|
|
373
375
|
const lkgPath = lkgPathFor(identity);
|
|
374
376
|
try {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
+
const source = candidateSource();
|
|
378
|
+
const cacheDir = source.skillBodyCacheDir;
|
|
379
|
+
if (cacheDir === undefined) {
|
|
380
|
+
logger.warn("config_lkg_save_failed", { err: "the config source declares no skill body cache", note: "LKG NOT written (skill bodies on disk are part of the handoff contract and this source keeps none) — handoff NOT confirmed" });
|
|
381
|
+
return false;
|
|
377
382
|
}
|
|
383
|
+
if (effectiveObj.skills)
|
|
384
|
+
await applySkillsManifest([], effectiveObj.skills, source, logger);
|
|
378
385
|
const enabledSkills = (effectiveObj.skills?.skills ?? []).filter((m) => m.enabled !== false);
|
|
379
|
-
const cacheDir = defaultSkillCacheDir(config);
|
|
380
386
|
for (const m of enabledSkills) {
|
|
381
387
|
const hex = m.contentHash.replace(/^sha256:/, "").toLowerCase();
|
|
382
388
|
if (!SHA256_HEX_RE.test(hex))
|
|
@@ -626,7 +632,7 @@ export async function createWebAdminRuntime(ctx) {
|
|
|
626
632
|
if (!dryRun && cc !== undefined && currentIdentity() !== promptStateIdentity)
|
|
627
633
|
await rebasePromptState(currentIdentity(), "boot-end");
|
|
628
634
|
}
|
|
629
|
-
const readFaceBaseline =
|
|
635
|
+
const readFaceBaseline = buildReadFaceBaseline(config);
|
|
630
636
|
const readFaceEnvHeld = applyCenterReadFace(config, effective ?? { version: 0, updatedAt: "", models: { models: [], roles: {} } }, effective ? logger : undefined).envHeld;
|
|
631
637
|
if (!modelReadyState.ready)
|
|
632
638
|
logger.warn("model_roster_pending", {
|
|
@@ -643,18 +649,21 @@ export async function createWebAdminRuntime(ctx) {
|
|
|
643
649
|
...(mcpRevocationWired && mcpRevocations.hasBaseline() ? { mcpRevocationLive: true } : {}),
|
|
644
650
|
});
|
|
645
651
|
const applyLiveReadFace = (eff, swap, phase) => {
|
|
646
|
-
const before = config
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
if (!faceChanged)
|
|
652
|
+
const before = buildReadFaceBaseline(config);
|
|
653
|
+
const { changedSeats } = applyCenterReadFace(config, eff, logger, { phase: "live", baseline: readFaceBaseline });
|
|
654
|
+
if (changedSeats.length === 0)
|
|
650
655
|
return;
|
|
651
656
|
try {
|
|
652
|
-
swap(
|
|
653
|
-
logger.info("read_face_swapped", {
|
|
657
|
+
swap(buildReadFaceSwapCandidate(buildDeploymentPostureSeats(config), changedSeats));
|
|
658
|
+
logger.info("read_face_swapped", {
|
|
659
|
+
seats: changedSeats,
|
|
660
|
+
...(changedSeats.includes("readFace") ? { from: before.readFace ?? null, to: config.readFace ?? null } : {}),
|
|
661
|
+
phase,
|
|
662
|
+
note: "hot seats (core swapDeps) — legs that already prepared keep the read posture they prepared under",
|
|
663
|
+
});
|
|
654
664
|
}
|
|
655
665
|
catch (err) {
|
|
656
|
-
config
|
|
657
|
-
config.readFaceSource = beforeSource;
|
|
666
|
+
restoreReadFaceBaseline(config, before);
|
|
658
667
|
throw err;
|
|
659
668
|
}
|
|
660
669
|
};
|
|
@@ -692,9 +701,8 @@ export async function createWebAdminRuntime(ctx) {
|
|
|
692
701
|
return (await manualRefresh()) === "failed" ? "failed" : "triggered";
|
|
693
702
|
},
|
|
694
703
|
async applyCenterCapabilities(skills) {
|
|
695
|
-
if (effective?.skills
|
|
696
|
-
skills = await
|
|
697
|
-
}
|
|
704
|
+
if (effective?.skills)
|
|
705
|
+
skills = await applySkillsManifest(skills, effective.skills, candidateSource(), logger);
|
|
698
706
|
if (effective?.plugins !== undefined) {
|
|
699
707
|
const pluginOut = await applyCenterPlugins(skills, effective, {
|
|
700
708
|
cacheRoot: join(config.localDataRoot ?? join(homedir(), ".ai-agent"), "plugin-cache"),
|
|
@@ -972,6 +980,9 @@ export async function createWebAdminRuntime(ctx) {
|
|
|
972
980
|
if (configProvider.kind === "remote")
|
|
973
981
|
lastPull = "ok";
|
|
974
982
|
}
|
|
983
|
+
catch (err) {
|
|
984
|
+
logger.warn("web_admin_refresh_failed", { err: String(err), leg: "boot-deferred", note: "etag NOT advanced — the refresh cadence re-fetches and re-applies the same candidate" });
|
|
985
|
+
}
|
|
975
986
|
finally {
|
|
976
987
|
refreshInFlight = false;
|
|
977
988
|
}
|
|
@@ -21,13 +21,16 @@
|
|
|
21
21
|
*
|
|
22
22
|
* ## 修形 = 照 [ref] 的配方接第四面(不是新造语义)
|
|
23
23
|
*
|
|
24
|
-
* brain
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* 当年 core 的摘要器没有凭据座,只能在 brain 外面包一层注入 `options.apiKey`(`createPerModelKeyBrain`)。
|
|
25
|
+
* core 7.51.0 起 `WebFetchSummarizerOptions.getApiKeyAndHeaders` 是与 `TaskSpec` / `SideQuerySpec` 同一签名的座
|
|
26
|
+
* (每次摘要按摘要器的模型解一次,`{ apiKey, headers }` 两半都收)⇒ 那层包装退役:`RunnerDeps.brain` 原样交给
|
|
27
|
+
* 摘要器,座位由与 side-query / 记忆整理**同一只**座位工厂(`createPerModelAuthSeat`)填充:
|
|
28
|
+
* · 有 per-model key ⇒ 请求带模型自己的凭据;
|
|
29
|
+
* · 无 ⇒ 座位回 `undefined` ⇒ 交回 core 按它自己的 key↔URL 配对法判(部署根上的模型带网关 key;
|
|
30
|
+
* entry URL 不在部署根上 ⇒ `route.credential_mismatch` 响亮拒 —— core 7.51.0 起没有「未声明部署根」的
|
|
31
|
+
* `unpinned` 快速起步姿态了,一份部署凭据只配一个根。[ref] 的具名残余与其 P-DEBT 腿已在源头清偿,
|
|
32
|
+
* 见 side-query-lane.ts 顶注);
|
|
29
33
|
* · 毒丸 ⇒ **上抛**,一个字节都不发(CLAUDE.md [ref] 安全轴 fail-closed)。
|
|
30
|
-
* wrapper 只包本席这一只引用,`RunnerDeps.brain` 零扰动(与 side-query 席同形)。
|
|
31
34
|
*
|
|
32
35
|
* ## 为什么住在 `boot/` 而不是继续躺在 `main()` 的闭包里
|
|
33
36
|
*
|
|
@@ -49,7 +52,7 @@ import { type KeyResolver } from "./side-query-lane.js";
|
|
|
49
52
|
/** WebFetch 摘要执行席(= `ScenarioDeps.webFetchSummarize`,core 的 `WebFetchConfig.summarize` 逐字同形)。 */
|
|
50
53
|
export type WebFetchSummarizeLane = NonNullable<NonNullable<WebFetchConfig["summarize"]>>;
|
|
51
54
|
export interface WebFetchSummarizeLaneCtx {
|
|
52
|
-
/** 与 `RunnerDeps.brain`
|
|
55
|
+
/** 与 `RunnerDeps.brain` **同一只**实例,原样交给摘要器(凭据走摘要器的座位,不包 brain)。 */
|
|
53
56
|
brain: Brain;
|
|
54
57
|
/** 活配置引用:目录/角色表被 web-admin 就地热应用(`mutateInPlace`),按调用取值才跟得上。 */
|
|
55
58
|
config: ServiceConfig;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createWebFetchSummarizer, resolveTaskModel as coreResolveTaskModel } from "@sema-agent/core";
|
|
2
|
-
import {
|
|
2
|
+
import { createPerModelAuthSeat } from "./side-query-lane.js";
|
|
3
3
|
export function createWebFetchSummarizeLane(ctx) {
|
|
4
|
-
const brain = createPerModelKeyBrain(ctx.brain, ctx.getKeyResolver);
|
|
5
4
|
return async (content, prompt, signal) => {
|
|
6
5
|
const model = (() => {
|
|
7
6
|
try {
|
|
@@ -14,7 +13,7 @@ export function createWebFetchSummarizeLane(ctx) {
|
|
|
14
13
|
})();
|
|
15
14
|
if (!model)
|
|
16
15
|
throw new Error("summarize model unresolved for this deployment");
|
|
17
|
-
return createWebFetchSummarizer(brain, model)(content, prompt, signal);
|
|
16
|
+
return createWebFetchSummarizer(ctx.brain, model, { getApiKeyAndHeaders: createPerModelAuthSeat(ctx.getKeyResolver) })(content, prompt, signal);
|
|
18
17
|
};
|
|
19
18
|
}
|
|
20
19
|
//# sourceMappingURL=webfetch-summarize-lane.js.map
|
package/dist/brain.js
CHANGED
|
@@ -54,11 +54,10 @@ export function createBrainAssembly(config, deps = {}) {
|
|
|
54
54
|
: {};
|
|
55
55
|
const routes = deploymentGatewayRoots(config);
|
|
56
56
|
const openaiBrains = routes.map((baseUrl, i) => {
|
|
57
|
+
const gatewayKey = deploymentGatewayKey(config);
|
|
57
58
|
const brain = constructRouteBrain(i === 0 ? "gateway route" : `gateway fallback route[${i - 1}]`, [baseUrl], () => createOpenAIBrain({
|
|
58
59
|
baseUrl,
|
|
59
|
-
|
|
60
|
-
return deploymentGatewayKey(config);
|
|
61
|
-
},
|
|
60
|
+
...(gatewayKey !== undefined ? { apiKey: gatewayKey } : {}),
|
|
62
61
|
...retryCap,
|
|
63
62
|
fetchImpl,
|
|
64
63
|
...timeouts,
|
|
@@ -106,16 +105,14 @@ export function createBrainAssembly(config, deps = {}) {
|
|
|
106
105
|
const ownSealed = credential.sealed !== undefined ? { [fallbackModel.name]: credential.sealed } : {};
|
|
107
106
|
const fbOwnApiKey = () => resolveModelApiKey(fallbackModel.name, ownEnvRef, process.env, ownSealed);
|
|
108
107
|
const anthropicLaneHop = dispatchBrainLane(fallbackModel.provider, anthropicBrain !== undefined) === "anthropic";
|
|
108
|
+
const fbDeclaredRoot = degradeDeclaredRoot(fallbackModel.baseUrl, config);
|
|
109
|
+
const fbDeploymentKey = deploymentGatewayKey(config);
|
|
109
110
|
const fallback = anthropicLaneHop && anthropicBrain
|
|
110
111
|
?
|
|
111
112
|
anthropicBrain
|
|
112
113
|
: constructRouteBrain(`degrade target "${degradeTo}"`, [fallbackModel.baseUrl, config.gatewayBaseUrl], () => createOpenAIBrain({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
},
|
|
116
|
-
get apiKey() {
|
|
117
|
-
return deploymentGatewayKey(config) || undefined;
|
|
118
|
-
},
|
|
114
|
+
...(fbDeclaredRoot !== undefined ? { baseUrl: fbDeclaredRoot } : {}),
|
|
115
|
+
...(fbDeploymentKey ? { apiKey: fbDeploymentKey } : {}),
|
|
119
116
|
...(config.gatewayMaxRetries !== undefined ? { maxRetries: config.gatewayMaxRetries } : {}),
|
|
120
117
|
fetchImpl,
|
|
121
118
|
...timeouts,
|
|
@@ -27,8 +27,8 @@ export interface SubagentExtraToolOptions {
|
|
|
27
27
|
*
|
|
28
28
|
* core 7.46.0 #1432 —— **工厂与名册一个来源**:返回委派工具的两键,`extraTools`(每次 spawn 交出 `forSpawn` 选中的那几只)与
|
|
29
29
|
* `extraToolNames`(全部可能交出的那几只的名字 / 别名 / 内容来源,从同一批 `ToolSpec` 读出)。名册在场 ⇒ 引擎重新能列出子代的工具池:
|
|
30
|
-
* 定义级 / 工具级 / `TaskSpec.agents`
|
|
31
|
-
*
|
|
30
|
+
* 定义级 / 工具级 / `TaskSpec.agents` 清单里没有任何工具答应的名字由引擎按名册判(core 7.51.0 #1736 起不拒 run:条目原样保留、告知一次并带该写的拼法;
|
|
31
|
+
* 7.46.0–7.50.x 曾开跑前拒 `config.agent.unknown_tool`),记忆内容来源读者按声明读这几只。
|
|
32
32
|
* 修前(core 7.32.0–7.45.x)工厂恒在场 ⇒ 池不可静态知 ⇒ 那一族拼法只告不拒、委派结果一律按「外部」读。
|
|
33
33
|
* 工厂**不抛**:各组的 `tools` 是装配期就建好的对象;部署 `forSpawn` 的抛由 core 收成「这一 spawn 什么都不继承 + 模型面披露句」(onExtraToolsError 记 warn)。
|
|
34
34
|
* 一种拼法出现在两只工具上(部署工具与 `Now` 撞名 / 别名撞)⇒ core 在建委派工具处抛 `config.extra_tool_names`(`shape`),不静默取其一。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 引擎能力词 → `GET /v1/capabilities` 顶层布尔键(core 7.51.1 `ENGINE_CAPABILITIES`;[ref]:词由引擎定,投影形由本仓定)。
|
|
3
|
+
*
|
|
4
|
+
* 形:引擎的能力词**原样**作顶层键(`"inherit_env.own_credentials_withheld": true`),值 = 该词在**本进程**为真的事实。键集由 `ENGINE_CAPABILITIES`
|
|
5
|
+
* 机器派生(一只投影循环,不逐词写键);事实表 {@link ENGINE_CAPABILITY_FACTS} 是 `Record<EngineCapability, …>` 型 —— 引擎加词而本表没跟 = tsc 红,
|
|
6
|
+
* 本表多一个引擎没有的词 = tsc 红(闭集的双向门),所以「这台引擎说它能,本服务也真接上了」不靠人记得登记。
|
|
7
|
+
* 壳按词闸(cli 读顶层布尔 `true`),不按版本号猜;契约附录 B.1 与 `test/capabilities-doc-appendix.test.ts` 把这些键与引擎词集对拍。
|
|
8
|
+
*/
|
|
9
|
+
import { type EngineCapability } from "@sema-agent/core";
|
|
10
|
+
/** 投影:引擎词集的每一词 → 本进程的事实(键集恒 = `ENGINE_CAPABILITIES`)。 */
|
|
11
|
+
export declare function engineCapabilityBits(): {
|
|
12
|
+
readonly [W in EngineCapability]: boolean;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=engine-capabilities.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ENGINE_CAPABILITIES } from "@sema-agent/core";
|
|
2
|
+
import { ownCredentialsWithheld } from "../server-secret-env.js";
|
|
3
|
+
const ENGINE_CAPABILITY_FACTS = {
|
|
4
|
+
"inherit_env.own_credentials_withheld": ownCredentialsWithheld,
|
|
5
|
+
};
|
|
6
|
+
export function engineCapabilityBits() {
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const word of ENGINE_CAPABILITIES)
|
|
9
|
+
out[word] = ENGINE_CAPABILITY_FACTS[word]();
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=engine-capabilities.js.map
|
|
@@ -4,7 +4,8 @@ import { createFixedWindowRateLimiter } from "../device-enrollment.js";
|
|
|
4
4
|
import { canonicalJson } from "../canonical-json.js";
|
|
5
5
|
import { echoKeyName, keyClosureClause, scanKeyClosure } from "../request-key-closure.js";
|
|
6
6
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "../mcp-content-origin.js";
|
|
7
|
-
import { HTTP_MCP_TRANSPORT_KEYS, MAX_REQUEST_MCP_SERVERS, MCP_ENTRY_EMPTY_ARRAY_KEYS, MCP_ENTRY_EMPTY_RECORD_KEYS, MCP_SERVER_ENTRY_KEYS, MCP_TRANSPORT_EMPTY_ARRAY_KEYS, MCP_TRANSPORT_EMPTY_RECORD_KEYS, STDIO_MCP_TRANSPORT_KEYS, validateRequestMcp, } from "../task-mcp.js";
|
|
7
|
+
import { HTTP_MCP_TRANSPORT_KEYS, MAX_REQUEST_MCP_SERVERS, MCP_ENTRY_EMPTY_ARRAY_KEYS, MCP_ENTRY_EMPTY_RECORD_KEYS, MCP_SERVER_ENTRY_KEYS, MCP_TRANSPORT_EMPTY_ARRAY_KEYS, MCP_TRANSPORT_EMPTY_RECORD_KEYS, STDIO_MCP_TRANSPORT_KEYS, requestEntryIdentityRefusal, validateRequestMcp, } from "../task-mcp.js";
|
|
8
|
+
import { toCoreMcpServerSpec } from "../mcp-assertion-header.js";
|
|
8
9
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
9
10
|
import { projectWiringManifestMcpRows } from "../trace/project.js";
|
|
10
11
|
export const MCP_PROBE_REFUSAL_CODES = [
|
|
@@ -51,8 +52,12 @@ function entryKeyIssue(entry, at) {
|
|
|
51
52
|
if (keys !== undefined)
|
|
52
53
|
scanKeyClosure(t, `${at}.transport.`, new Set(keys), NO_TOLERATED_KEYS, buckets);
|
|
53
54
|
}
|
|
54
|
-
if (buckets.unknown.length === 0)
|
|
55
|
-
|
|
55
|
+
if (buckets.unknown.length === 0) {
|
|
56
|
+
const refusal = requestEntryIdentityRefusal(entry);
|
|
57
|
+
if (refusal === undefined)
|
|
58
|
+
return null;
|
|
59
|
+
return refusal.errorCode === "request.body_shape" ? shapeRefusal(refusal.message) : fieldRefusal(refusal.message);
|
|
60
|
+
}
|
|
56
61
|
buckets.unknown.sort();
|
|
57
62
|
return shapeRefusal(`body: unrecognized key(s) on an MCP declaration — refused rather than dropped, because a dropped key reads as "in effect" to the caller. ` +
|
|
58
63
|
`${keyClosureClause(buckets.unknown, "unknown (not an mcpServers[] key — check the spelling)")}. ` +
|
|
@@ -71,7 +76,8 @@ function entryValueIssue(entry, at) {
|
|
|
71
76
|
if (read.badContentOrigin.length > 0) {
|
|
72
77
|
return fieldRefusal(`${at}.contentOrigin must be one of ${CONTENT_ORIGIN_WORDS.map((w) => `"${w}"`).join(" | ")} — got ${describeBadContentOrigin(entry.contentOrigin)} (omit the key for the default: this server's tools classify as external content)`);
|
|
73
78
|
}
|
|
74
|
-
|
|
79
|
+
const read0 = read.ok[0];
|
|
80
|
+
return declarationDowngradeIssue(entry, read0 === undefined ? undefined : toCoreMcpServerSpec(read0), at);
|
|
75
81
|
}
|
|
76
82
|
function declarationDowngradeIssue(entry, spec, at) {
|
|
77
83
|
if (spec === undefined)
|
|
@@ -171,7 +177,7 @@ export function readMcpProbeBody(raw) {
|
|
|
171
177
|
" so nothing in your body needs changing — if you see it, this deployment has a bug worth reporting"),
|
|
172
178
|
};
|
|
173
179
|
}
|
|
174
|
-
return { ok: true, specs: validated.ok };
|
|
180
|
+
return { ok: true, specs: validated.ok.map(toCoreMcpServerSpec) };
|
|
175
181
|
}
|
|
176
182
|
export function buildMcpProbeIdentity(principal) {
|
|
177
183
|
return principal !== undefined && principal.length > 0 ? principal : "local";
|
|
@@ -328,5 +328,7 @@ export declare function workspaceCache(c: Pick<ServiceConfig, "remoteExec">): Fa
|
|
|
328
328
|
}>;
|
|
329
329
|
/** 自唤醒调度器挂在宿主 env 上:`SCHEDULER_ENABLED` ∧ 单用户 ∧ host 车道。装配(`boot/execution-env.ts`)与能力位同调。 */
|
|
330
330
|
export declare function hostScheduler(c: Pick<ServiceConfig, "schedulerEnabled" | "requirePrincipal" | "remoteExec">): Fact<"scheduler_disabled" | "multi_tenant_deployment" | "not_host_lane">;
|
|
331
|
-
|
|
331
|
+
/** 引擎能力词的事实(core `ENGINE_CAPABILITIES` 每词一只谓词,值 = 本进程的事实;`/v1/capabilities` 以顶层布尔键展开它们)—— 住在
|
|
332
|
+
* `capabilities/engine-capabilities.ts`,这里只是把它纳入「能力位只投影事实」的同一张读面,静态门认 `facts.engineCapabilityBits()` 这一只展开。 */
|
|
333
|
+
export { engineCapabilityBits } from "./capabilities/engine-capabilities.js";
|
|
332
334
|
//# sourceMappingURL=capability-facts.d.ts.map
|
package/dist/capability-facts.js
CHANGED
package/dist/config-catalog.js
CHANGED
|
@@ -432,7 +432,7 @@ export const CONFIG_CATALOG = [
|
|
|
432
432
|
r("SESSION_SNAPSHOT_PREFIX", "store", "string", "快照 blob key 前缀", { derivedDefaultNote: "缺席 ⇒ blobs/(MinioBlobBackend 默认)" }),
|
|
433
433
|
r("SESSION_SNAPSHOT_TTL_SEC", "store", "number", "快照 presign TTL(秒;坏值省略走默认,绝不 NaN 签名)", { derivedDefaultNote: "缺席 ⇒ 3600(MinioBlobBackend 默认)" }),
|
|
434
434
|
r("SIGHUP_IDLE_GRACE_MS", "limitsHttp", "number", "SIGHUP 空闲关停窗(ms;共享引擎形防误杀)", { staticDefault: "120000", configKey: "sighupIdleGraceMs" }),
|
|
435
|
-
r("SKILLS_DIR", "integrations", "path", "出厂 skill 目录", { derivedDefaultNote: "缺席 ⇒ 包内 skills/(相对模块解析,不是 cwd)", configKey: "skillsDir" }),
|
|
435
|
+
r("SKILLS_DIR", "integrations", "path", "出厂 skill 目录(设了就整个替换包内 skills/,不叠加;配置源的技能再按名盖上)", { derivedDefaultNote: "缺席 ⇒ 包内 skills/(相对模块解析,不是 cwd)", configKey: "skillsDir" }),
|
|
436
436
|
r("SNAPSHOT_BLOB_ALLOW_SQL_BYTES", "store", "boolean", "云形缺 MinIO 时显式接受 SQL 店放快照字节(严格 'true';安全姿势开关不收宽松真值)", { staticDefault: "false", danger: DATA, configKey: "snapshotBlobAllowSql" }),
|
|
437
437
|
r("SNAPSHOT_BLOB_SQL_MAX_BYTES", "store", "number", "SQL 店单 blob 帽(字节)", { derivedDefaultNote: "缺席:mysql 引擎 ⇒ 内建默认帽;pg ⇒ 不设", configKey: "snapshotBlobSqlMaxBytes" }),
|
|
438
438
|
r("SSH_HOST", "orchestration", "string", "ssh 车道目标主机(必需件)"),
|
|
@@ -78,8 +78,9 @@ export declare function raceBootFetch<T>(fetch: Promise<T>, budgetMs: number): P
|
|
|
78
78
|
/**
|
|
79
79
|
* The two transport calls the service makes to obtain config, behind a backend-selectable seam. Both
|
|
80
80
|
* signatures and return shapes match `facade.fetchEffective` / `facade.fetchSkillContent`
|
|
81
|
-
* EXACTLY so
|
|
82
|
-
*
|
|
81
|
+
* EXACTLY, so every consumer (boot / refresh / late boot in `boot/web-admin.ts`, and `run-local`) runs one code
|
|
82
|
+
* path whichever backend answers: candidates through `fetchEffective`, skill bodies through `fetchSkillContent`
|
|
83
|
+
* (the skills read port `applySkillsManifest`), the body cache wherever the backend declares it.
|
|
83
84
|
*/
|
|
84
85
|
export interface ConfigProvider {
|
|
85
86
|
/** "remote" = web-admin HTTP; "local" = on-disk FileConfigStore. */
|
|
@@ -92,11 +93,18 @@ export interface ConfigProvider {
|
|
|
92
93
|
fetchEffective(etag: string | undefined): Promise<FetchEffectiveResult>;
|
|
93
94
|
/**
|
|
94
95
|
* Fetch one skill body by content hash (`sha256:<hex>`). Remote: GET the content endpoint + verify the
|
|
95
|
-
* hash. Local: content-addressed lookup over the
|
|
96
|
+
* hash. Local: content-addressed lookup over the skills of the candidate this provider last served (the
|
|
97
|
+
* same read that produced the manifest — S-1556). Returns `undefined` when the hash
|
|
96
98
|
* is unknown (remote 404 / local miss) so the caller keeps its baseline; throws only on a hard error
|
|
97
99
|
* (remote non-404 HTTP / hash mismatch).
|
|
98
100
|
*/
|
|
99
101
|
fetchSkillContent(contentHash: string): Promise<string | undefined>;
|
|
102
|
+
/**
|
|
103
|
+
* S-1556:这只配置源给技能正文留的**盘缓存目录**(content-addressed;`applySkillsManifest` 的读序 内存 → 盘 → 本配置源,读到即回写)。
|
|
104
|
+
* 远程:开着 LKG(`CONFIG_LKG_ENABLED`)时 = 配置根下的 `skill-cache` —— 中心不可达时的 LKG 起服不卡在正文拉取上,LKG 落盘的正文完整性门
|
|
105
|
+
* 也验这同一个目录;关着 = 缺席(不落盘)。本地:恒缺席 —— 正文就内联在本机盘上的 config.d 里,再拷一份是第二份真源。
|
|
106
|
+
*/
|
|
107
|
+
readonly skillBodyCacheDir: string | undefined;
|
|
100
108
|
}
|
|
101
109
|
/** The config-provider knobs read off ServiceConfig (a structural subset — avoids importing the whole
|
|
102
110
|
* ServiceConfig type and keeps this module testable with a plain object). */
|
|
@@ -117,6 +125,8 @@ export interface ConfigProviderInput {
|
|
|
117
125
|
* `process.cwd()`,而工厂只在 `CONFIG_PROVIDER === "local"` 字面上递它 ⇒ 另一条走到 local 的路上配置源读 cwd、
|
|
118
126
|
* 合成步读 localRoot,两个根分家。一个远程源也收它:`remote` 而无中心 URL 的形照样落回 local。 */
|
|
119
127
|
localDir: string;
|
|
128
|
+
/** S-1556:远程配置源的技能正文盘缓存目录(见 {@link ConfigProvider.skillBodyCacheDir});本地配置源不认它(正文本就在盘上)。缺席 = 不落盘。 */
|
|
129
|
+
skillBodyCacheDir?: string;
|
|
120
130
|
}
|
|
121
131
|
/**
|
|
122
132
|
* RemoteConfigProvider — DELEGATES to the exported web-admin HTTP functions. No HTTP is reimplemented;
|
|
@@ -127,13 +137,14 @@ export interface ConfigProviderInput {
|
|
|
127
137
|
export declare class RemoteConfigProvider implements ConfigProvider {
|
|
128
138
|
private readonly cc;
|
|
129
139
|
private readonly deps;
|
|
140
|
+
readonly skillBodyCacheDir: string | undefined;
|
|
130
141
|
readonly kind: "remote";
|
|
131
142
|
constructor(cc: CenterEndpoint & {
|
|
132
143
|
worker?: string;
|
|
133
144
|
}, deps?: {
|
|
134
145
|
fetchEffective?: typeof remoteFetchEffective;
|
|
135
146
|
fetchSkillContent?: typeof remoteFetchSkillContent;
|
|
136
|
-
});
|
|
147
|
+
}, skillBodyCacheDir?: string | undefined);
|
|
137
148
|
fetchEffective(etag: string | undefined): Promise<FetchEffectiveResult>;
|
|
138
149
|
fetchSkillContent(contentHash: string): Promise<string | undefined>;
|
|
139
150
|
}
|
|
@@ -146,6 +157,8 @@ export declare class RemoteConfigProvider implements ConfigProvider {
|
|
|
146
157
|
export declare class LocalConfigProvider implements ConfigProvider {
|
|
147
158
|
#private;
|
|
148
159
|
readonly kind: "local";
|
|
160
|
+
/** 本地配置源不留正文盘缓存:正文就内联在 config.d 里(同一块盘),缓存它是第二份真源。 */
|
|
161
|
+
readonly skillBodyCacheDir: undefined;
|
|
149
162
|
private readonly store;
|
|
150
163
|
/** [ref]b 可观测性:config_loaded 日志要能回答「到底读的哪个目录」——复验实录:只设 LOCAL_DATA_ROOT 时
|
|
151
164
|
* config.d 静默读不到且 0 模型无告警(CONFIG_LOCAL_DIR 才是本 lane 的选目录旋钮)。 */
|
package/dist/config-provider.js
CHANGED
|
@@ -38,10 +38,12 @@ export function raceBootFetch(fetch, budgetMs) {
|
|
|
38
38
|
export class RemoteConfigProvider {
|
|
39
39
|
cc;
|
|
40
40
|
deps;
|
|
41
|
+
skillBodyCacheDir;
|
|
41
42
|
kind = "remote";
|
|
42
|
-
constructor(cc, deps = {}) {
|
|
43
|
+
constructor(cc, deps = {}, skillBodyCacheDir = undefined) {
|
|
43
44
|
this.cc = cc;
|
|
44
45
|
this.deps = deps;
|
|
46
|
+
this.skillBodyCacheDir = skillBodyCacheDir;
|
|
45
47
|
}
|
|
46
48
|
async fetchEffective(etag) {
|
|
47
49
|
const fn = this.deps.fetchEffective ?? remoteFetchEffective;
|
|
@@ -68,6 +70,8 @@ export class RemoteConfigProvider {
|
|
|
68
70
|
}
|
|
69
71
|
export class LocalConfigProvider {
|
|
70
72
|
kind = "local";
|
|
73
|
+
skillBodyCacheDir = undefined;
|
|
74
|
+
#servedSkills = { skills: [] };
|
|
71
75
|
store;
|
|
72
76
|
root;
|
|
73
77
|
#injectedStore;
|
|
@@ -115,6 +119,7 @@ export class LocalConfigProvider {
|
|
|
115
119
|
const parseErrors = folded.domainErrors.map((w) => ({ domain: w.domain, error: redactConfigError(w.error) }));
|
|
116
120
|
const droppedEntries = folded.droppedEntries.map(redactDroppedEntry);
|
|
117
121
|
const errors = [...domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })), ...parseErrors, ...refErrors];
|
|
122
|
+
this.#servedSkills = eff.skills;
|
|
118
123
|
return {
|
|
119
124
|
effective: withDroppedCatalogEntries(mapToServiceEffective(eff, eff.version), folded.droppedCatalogEntries),
|
|
120
125
|
etag: tag,
|
|
@@ -126,10 +131,7 @@ export class LocalConfigProvider {
|
|
|
126
131
|
};
|
|
127
132
|
}
|
|
128
133
|
async fetchSkillContent(contentHash) {
|
|
129
|
-
|
|
130
|
-
if (!skills)
|
|
131
|
-
return undefined;
|
|
132
|
-
return findSkillContent(skills, contentHash);
|
|
134
|
+
return findSkillContent(this.#servedSkills, contentHash);
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
export function mapToServiceEffective(eff, version) {
|
|
@@ -234,7 +236,7 @@ export function mapToServiceEffective(eff, version) {
|
|
|
234
236
|
}
|
|
235
237
|
export function createConfigProvider(input) {
|
|
236
238
|
return configProviderKind(input) === "remote" && input.webAdmin !== undefined
|
|
237
|
-
? new RemoteConfigProvider(input.webAdmin)
|
|
239
|
+
? new RemoteConfigProvider(input.webAdmin, {}, input.skillBodyCacheDir)
|
|
238
240
|
: new LocalConfigProvider(input.localDir);
|
|
239
241
|
}
|
|
240
242
|
export function configLocalRoot(config) {
|
package/dist/config-types.d.ts
CHANGED
|
@@ -274,10 +274,10 @@ export interface ScopedMcpServer {
|
|
|
274
274
|
spec: McpServerSpec;
|
|
275
275
|
}
|
|
276
276
|
/** The deployment's MCP declaration, resolved (`resolveMcpServers`). `servers` = what actually MOUNTED (env-NAME refs
|
|
277
|
-
* resolved, `enabled:false` / missing-ref / malformed-`toolFaces` entries skipped, each with its own loud warn);
|
|
277
|
+
* resolved, `enabled:false` / missing-ref / malformed-`toolFaces` / malformed-`toolAxes` entries skipped, each with its own loud warn);
|
|
278
278
|
* `reservedNames` = EVERY name the deployment declared, mounted or not. A name the deployment declared is CLOSED to
|
|
279
279
|
* request-side injection (`resolveRequestMcp`) whether or not it mounted — otherwise "skipped" would silently mean
|
|
280
|
-
* "free for the caller to refill without the operator's `toolFaces` / `allowTools`" (车CF codex r2 [high]). */
|
|
280
|
+
* "free for the caller to refill without the operator's `toolFaces` / `toolAxes` / `allowTools`" (车CF codex r2 [high]). */
|
|
281
281
|
export interface CenterMcp {
|
|
282
282
|
servers: ScopedMcpServer[];
|
|
283
283
|
reservedNames: readonly string[];
|
|
@@ -471,8 +471,9 @@ export interface ServiceConfigFlat {
|
|
|
471
471
|
* 由 boot(config.ts)与热路径(apply-effective)同一只判据保证。 */
|
|
472
472
|
gatewayBaseUrl?: string;
|
|
473
473
|
/** `MODEL_API_KEY` —— **网关的**凭据,只与网关根配对([ref])。S-623:与 `gatewayBaseUrl` 同进同出 ——
|
|
474
|
-
* 没有网关就不装载(否则 brain
|
|
475
|
-
* openai
|
|
474
|
+
* 没有网关就不装载(否则 brain 的声明根为空:core 7.51.0 起判 `route.credential_mismatch`,每条自带 baseUrl、没有
|
|
475
|
+
* per-model 凭据的 openai 条目被拒;7.51.0 前是 `unpinned`,key 随请求发往那些条目),由 `model_gateway_key_unpaired`
|
|
476
|
+
* 启动通知说出来。S-1561:brain 在构造时读它一次(core 收据律),本仓没有运行期写者 ⇒ 换值 = 重启。 */
|
|
476
477
|
gatewayApiKey?: string;
|
|
477
478
|
/** Secondary openai-compatible gateways for cross-gateway FAILOVER (same protocol + same model id).
|
|
478
479
|
* Tried in order after the primary when a gateway fails before streaming any content. */
|
|
@@ -1759,7 +1760,7 @@ export interface ServiceConfigFlat {
|
|
|
1759
1760
|
* its own meaning (the sandbox lane's opt-in above) and is unaffected. */
|
|
1760
1761
|
lspHostEnabled: boolean;
|
|
1761
1762
|
/** S-699 —— host-lane language-server env **named allowlist** (`LSP_ENV_ALLOW=NAME,NAME`). The language server's env is
|
|
1762
|
-
* `
|
|
1763
|
+
* the engine's `inheritedHostEnv("scrub")` (shape rules + this deployment's declared own credentials); each name here is put back **verbatim** from the service's own env on top of that
|
|
1763
1764
|
* (`allowlistedEnv`, the same "named keys pass" predicate as the host shell's `inheritEnv:[names]`) — the deployer
|
|
1764
1765
|
* explicitly accepts handing that value to code the language server runs. Unnamed secret-shaped keys stay stripped.
|
|
1765
1766
|
* `[]` = nothing put back (the default: unset / empty string). A malformed list (empty segment / not an env-var name /
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本服务**自己**发往配置端点的出站请求(hook 直连模型腿 `hooks/hook-llm.ts`、记忆 embedder `plugins/memory-embedder.ts`)的
|
|
3
|
+
* 重定向律 —— 与 core 7.51.0(#1531)一方 brain 的请求同一条:请求以 `redirect: "manual"` 发出,端点回重定向 ⇒ 拒;
|
|
4
|
+
* 不跟、不重试、拒句不点名目标。
|
|
5
|
+
*
|
|
6
|
+
* 为什么:请求只去被判过的那个根。传输层(undici)跟随 POST 307 / 308 时把请求体(用户内容 / 记忆正文)原样送到
|
|
7
|
+
* `Location`,跨 origin 只剥 `authorization` —— anthropic 形的 `x-api-key` 随行(`test/hook-llm.test.ts` 的 G1 格在
|
|
8
|
+
* 7.128.x 上实测送达重定向目标)。判据与拒句与 core 的 brain 请求门同形(`opaqueredirect` 或 3xx)。
|
|
9
|
+
*/
|
|
10
|
+
/** 端点回的是重定向 ⇒ 拒句(调用方在前面冠上自己的面名);否则 `undefined`。命中时丢弃响应体(不读、不留连接)。 */
|
|
11
|
+
export declare function egressRedirectRefusal(res: Response, fix: string): string | undefined;
|
|
12
|
+
//# sourceMappingURL=egress-redirect.d.ts.map
|