@sema-agent/server 7.96.0 → 7.98.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.
Files changed (89) hide show
  1. package/MIGRATION.md +5 -0
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +2 -2
  4. package/USAGE.md +10 -1
  5. package/deploy/sema-up/cluster-up.sh +8 -4
  6. package/deploy/sema-up/kube-up.sh +40 -15
  7. package/deploy/sema-up/lib/envf.sh +173 -0
  8. package/deploy/sema-up/sema-up.sh +113 -94
  9. package/deploy/sema-up/smoke.sh +8 -1
  10. package/dist/approval.d.ts +28 -0
  11. package/dist/approval.js +24 -0
  12. package/dist/boot/budget-tracing.d.ts +1 -0
  13. package/dist/boot/budget-tracing.js +3 -2
  14. package/dist/boot/config-center.js +4 -7
  15. package/dist/boot/coordinators.js +2 -2
  16. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -2
  17. package/dist/boot/deferred-sandbox-path-env.js +3 -1
  18. package/dist/boot/memory-boundary.d.ts +17 -2
  19. package/dist/boot/memory-boundary.js +11 -0
  20. package/dist/boot/reapers.d.ts +3 -1
  21. package/dist/boot/reapers.js +2 -2
  22. package/dist/boot/resolve-spec.js +15 -10
  23. package/dist/boot/runner-deps.d.ts +2 -1
  24. package/dist/boot/session-faces.d.ts +4 -1
  25. package/dist/boot/session-faces.js +5 -5
  26. package/dist/boot/stage-04-stores.d.ts +1 -0
  27. package/dist/boot/stage-04-stores.js +2 -2
  28. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  29. package/dist/boot/stage-06-runners.d.ts +1 -0
  30. package/dist/boot/stage-07-capability-layer.d.ts +1 -0
  31. package/dist/boot/stage-07-capability-layer.js +10 -14
  32. package/dist/boot/stage-08-reapers.d.ts +2 -3
  33. package/dist/boot/stage-08-reapers.js +2 -2
  34. package/dist/boot/stage-09-leader.d.ts +1 -0
  35. package/dist/boot/stage-09-leader.js +2 -2
  36. package/dist/boot/stage-10-http-server.d.ts +1 -0
  37. package/dist/boot/stores.js +5 -11
  38. package/dist/brain.js +28 -18
  39. package/dist/capabilities/execution-lane.d.ts +5 -7
  40. package/dist/capabilities/execution-lane.js +2 -1
  41. package/dist/capabilities/memory-notice.d.ts +30 -12
  42. package/dist/capabilities/memory-notice.js +8 -1
  43. package/dist/capabilities/skills.d.ts +14 -0
  44. package/dist/capabilities/skills.js +4 -0
  45. package/dist/config-catalog.js +5 -4
  46. package/dist/config-center/apply-effective.js +7 -1
  47. package/dist/config-center/facade.d.ts +2 -1
  48. package/dist/config-invariants.d.ts +2 -2
  49. package/dist/config-invariants.js +9 -1
  50. package/dist/config-types.d.ts +20 -3
  51. package/dist/config.d.ts +9 -0
  52. package/dist/config.js +46 -8
  53. package/dist/env-facts.js +2 -4
  54. package/dist/execution-lane-caps.d.ts +36 -6
  55. package/dist/execution-lane-caps.js +4 -0
  56. package/dist/file-history-capture.d.ts +65 -0
  57. package/dist/file-history-capture.js +21 -0
  58. package/dist/hooks/hook-llm.d.ts +1 -1
  59. package/dist/hooks/hook-llm.js +3 -1
  60. package/dist/hosted-posture.d.ts +1 -1
  61. package/dist/http/resume-legs.d.ts +14 -4
  62. package/dist/http/resume-legs.js +26 -17
  63. package/dist/http/route-ctx.d.ts +35 -9
  64. package/dist/http/routes/approvals-assistant.d.ts +1 -1
  65. package/dist/http/routes/approvals-assistant.js +16 -6
  66. package/dist/http/routes/capabilities.js +2 -0
  67. package/dist/http/routes/runs.js +2 -2
  68. package/dist/http/routes/session-sync.js +6 -1
  69. package/dist/http/server.d.ts +4 -1
  70. package/dist/http/server.js +1 -1
  71. package/dist/http/verify-rounds.d.ts +1 -1
  72. package/dist/memory-scope.d.ts +68 -13
  73. package/dist/memory-scope.js +35 -5
  74. package/dist/model-provider.d.ts +13 -0
  75. package/dist/model-provider.js +3 -0
  76. package/dist/model-route-endpoint.d.ts +82 -0
  77. package/dist/model-route-endpoint.js +55 -0
  78. package/dist/observability/fail-open.d.ts +4 -0
  79. package/dist/observability/fail-open.js +4 -0
  80. package/dist/project-identity.d.ts +29 -0
  81. package/dist/project-identity.js +120 -0
  82. package/dist/run-local.d.ts +14 -4
  83. package/dist/run-local.js +12 -10
  84. package/dist/session-sync.d.ts +4 -1
  85. package/dist/session-sync.js +2 -2
  86. package/dist/task-cwd.d.ts +9 -3
  87. package/dist/task-cwd.js +3 -1
  88. package/dist/trace/redact.d.ts +1 -1
  89. package/package.json +1 -1
@@ -54,6 +54,7 @@
54
54
  * `TaskSpec.memoryPersistenceCapable` 一条路进去(`boot/resolve-spec.ts` 直通),我方显式**不铸**受限会话、
55
55
  * 也**不投**这个读面(硬造一个消费点 = 造给自己看的 wire 键)。哪天真要在诊断面露它,先有消费方需求。
56
56
  */
57
+ import { type ExecutionLane, type RemoteExecProvider } from "../execution-lane-caps.js";
57
58
  /**
58
59
  * [ref] A3 源头修(黑板 [ref]/[ref]/[ref],[ref])—— `memoryPersistenceCapable` 的**装配层有效值**:
59
60
  * operator 显式 env 恒赢;env 缺席时,`REMOTE_EXEC=host` 腿自动补 `true`;其余一律缺席(现行为)。
@@ -93,8 +94,8 @@ export interface MemoryWriteBoundaryFacts {
93
94
  * core 7.16.0([ref])之后写家还要再深一层(`<平面>/<label>`),但 label 归引擎注册簿,boot 期说不出口;
94
95
  * 平面是**包含关系上等价**的那一只(家恒为平面的直接子目录),所以判据停在平面,不猜 label。 */
95
96
  memoryRoot: string | undefined;
96
- /** 执行车道名,只进文案(`config.remoteExec?.provider ?? "in-process"`)。 */
97
- lane: string;
97
+ /** 执行车道名,只进文案(归一读口 `executionLaneOf` 的答案;S-602 起不在调用点手写 `?? "in-process"`)。 */
98
+ lane: ExecutionLane;
98
99
  /** 该车道的文件工具是否跑在**本机** fs 上(in-process / `REMOTE_EXEC=host`)。false = 沙箱盘,与记忆根分平面。
99
100
  * ⚠️ 这一位管的是**文件平面**,与下面的 `coreRemoteExecutionEnv` 是**两根轴**,别合并(codex round2
100
101
  * [medium] 就是把它们当成一根轴的后果):`host` 车道的文件平面就是本机(这一位 true),但 core 眼里它
@@ -116,6 +117,20 @@ export interface MemoryWriteBoundaryFacts {
116
117
  /** boot 期算得出的文件工具围栏根。空数组 = 算不出(每任务临时目录 / 未配 workspace)⇒ 不判。 */
117
118
  containmentRoots: readonly string[];
118
119
  }
120
+ /**
121
+ * S-602 —— 审计的**车道那四位**由本函数从能力表派生(boot/stores.ts 的装配点只调它,不再就地手写):
122
+ * 改前 stores 里是 `lane === undefined || lane === "host"`(同义折,门 D R3)+ `config.remoteExec !== undefined`
123
+ * (座位上的裸缺席判,R4)+ `lane ?? "in-process"` ×3(R2)。两根轴各读表里各自那一位,不合并:
124
+ * · `hostSemanticsLane` ⇐ `hostFilePlane`(文件平面在不在本机);
125
+ * · `coreRemoteExecutionEnv` ⇐ 同名位(core 拿没拿到执行环境)。
126
+ * 读数逐车道不变(对账:`test/execution-lane-caps.test.ts` 的真装配点联合覆盖格)。`processCwd` 由调用点传
127
+ * (stores 传 `process.cwd()`),本函数保持纯。
128
+ */
129
+ export declare function buildMemoryBoundaryLaneFacts(config: {
130
+ readonly remoteExec?: {
131
+ readonly provider: RemoteExecProvider;
132
+ } | undefined;
133
+ }, processCwd: string): Pick<MemoryWriteBoundaryFacts, "lane" | "hostSemanticsLane" | "coreRemoteExecutionEnv" | "containmentRoots">;
119
134
  /** 一条启动告警:`tag` 进日志事件名(可被运维按名过滤/告警),`detail` 是给人读的整句(含恢复路径)。 */
120
135
  export interface MemoryWriteBoundaryWarning {
121
136
  tag: "memory_root_outside_write_boundary" | "memory_remote_lane_write_instruction_dropped";
@@ -1,9 +1,20 @@
1
1
  import { resolve } from "node:path";
2
+ import { executionLaneCaps, executionLaneOf } from "../execution-lane-caps.js";
2
3
  export function effectiveMemoryPersistenceCapable(config) {
3
4
  if (config.memoryPersistenceCapable !== undefined)
4
5
  return config.memoryPersistenceCapable;
5
6
  return config.remoteExec?.provider === "host" ? true : undefined;
6
7
  }
8
+ export function buildMemoryBoundaryLaneFacts(config, processCwd) {
9
+ const lane = executionLaneOf(config.remoteExec?.provider);
10
+ const caps = executionLaneCaps(lane);
11
+ return {
12
+ lane,
13
+ hostSemanticsLane: caps.hostFilePlane === "supported",
14
+ coreRemoteExecutionEnv: caps.coreRemoteExecutionEnv === "supported",
15
+ containmentRoots: lane === "in-process" ? [processCwd] : [],
16
+ };
17
+ }
7
18
  function isInside(child, root) {
8
19
  const c = resolve(child);
9
20
  const r = resolve(root);
@@ -20,6 +20,7 @@ import type { Logger } from "../observability/logger.js";
20
20
  import type { Metrics } from "../observability/metrics.js";
21
21
  import type { WorkflowNotifyGate, WorkflowNotifyJournalStore } from "../orchestration/workflow-notify-journal.js";
22
22
  import type { TaskAttachmentStore } from "../plugins/task-attachment-store.js";
23
+ import type { FileHistoryErasureFace } from "../file-history-capture.js";
23
24
  import type { ApprovalNonceStore, CheckpointStoreFull, CostQuotaStore, ImageBake, RateLimiterStore, ServiceWorkflowJournalStore, StoreBackend, ToolResultStoreFull } from "../plugins/store-backend.js";
24
25
  import type { ToolApprovalCoordinator } from "../tool-approval.js";
25
26
  /** [ref] / [ref]§五(sema-comms `audits/failopen-governance-176.md` §7 "Z 类 1 条"):default consecutive-failure
@@ -69,7 +70,8 @@ export interface ReapersCtx {
69
70
  rateLimiter: RateLimiter | RateLimiterStore | undefined;
70
71
  costQuota: CostQuota | CostQuotaStore | undefined;
71
72
  toolResultStore: ToolResultStoreFull | undefined;
72
- fileHistoryStore: ReturnType<StoreBackend["fileHistory"]> | undefined;
73
+ /** S-589:文件历史的**擦除脸**(后端在即在,与 `FILE_HISTORY_CAPTURE` 无关)—— 本段只用它的孤儿 blob GC。 */
74
+ fileHistoryErasure: FileHistoryErasureFace | undefined;
73
75
  taskAttachmentStore: TaskAttachmentStore | undefined;
74
76
  imageBakes: ImageBake | undefined;
75
77
  worktreeReap: (() => Promise<void>) | undefined;
@@ -56,7 +56,7 @@ export function createPlacementAwareReapSessions(deps) {
56
56
  };
57
57
  }
58
58
  export function startReapers(ctx) {
59
- const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, rateLimiter, costQuota, toolResultStore, fileHistoryStore, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, } = ctx;
59
+ const { config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore, rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap, workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore, backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore, getRunDenySweep, } = ctx;
60
60
  const leg = (p) => {
61
61
  if (p !== undefined)
62
62
  trackMaintenanceLeg(p);
@@ -227,7 +227,7 @@ export function startReapers(ctx) {
227
227
  leg(toolResultStore
228
228
  ?.reapOlderThan?.(Date.now() - config.toolResultTtlSec * 1000)
229
229
  ?.then(() => toolResultReapOlderThanGuard.onSuccess(), toolResultReapOlderThanGuard.onError));
230
- leg(fileHistoryStore
230
+ leg(fileHistoryErasure
231
231
  ?.sweepOrphanBlobs?.()
232
232
  .then(() => fileHistorySweepOrphanBlobsGuard.onSuccess(), fileHistorySweepOrphanBlobsGuard.onError));
233
233
  leg(backend?.legacySnapshotBlobSweep?.()
@@ -7,7 +7,7 @@ import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilit
7
7
  import { sendUserFileTool } from "../capabilities/send-user-file-tool.js";
8
8
  import { gateScenarioRequest, mergeUserSkills, selectScenario } from "../capabilities/scenarios.js";
9
9
  import { stampHandsLane, toolPolicyForHands } from "../capabilities/hands-lane.js";
10
- import { shouldDiscloseNoPersistentMemory, withNoPersistentMemoryNotice } from "../capabilities/memory-notice.js";
10
+ import { buildNoticeLaneFacts, shouldDiscloseNoPersistentMemory, withNoPersistentMemoryNotice } from "../capabilities/memory-notice.js";
11
11
  import { applyLongtailDefer } from "../capabilities/tool-defer.js";
12
12
  import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs } from "../deployment-governance.js";
13
13
  import { acceptShellScratchpadDir, buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, resumeFactsForLane, scratchpadCoveringTaskRoot } from "../env-facts.js";
@@ -25,7 +25,7 @@ import { isIsolatedExecEnv, routeServiceTask, supPostureOverrides } from "../rou
25
25
  import { gateExecutionLane } from "../runtime-caps-resolver.js";
26
26
  import { applyRuntimeGovernance, stripDelegationTools } from "../runtime-governance.js";
27
27
  import { HttpError, encodeCheckpointScope } from "../security.js";
28
- import { memorySpecForRequest } from "../memory-scope.js";
28
+ import { memoryProjectKeyFor, memorySpecForRequest } from "../memory-scope.js";
29
29
  import { singleMemberOffFieldIssue } from "../request-value-closure.js";
30
30
  import { a2aForScenario, mcpForScenario } from "../config-center/facade.js";
31
31
  import { normalizeAttachments, normalizeResilience, normalizeResumeAtMode, normalizeSuggestNextPrompts, promptProfileFromBody, resolveTaskLimits, retainBackgroundProcessesFromBody, taskAgentsSpecFragment, toolMaterializeStrategyFromBody, toolNameListFromBody } from "../spec-fields.js";
@@ -36,13 +36,19 @@ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSet
36
36
  import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
37
37
  import { redactSecrets } from "../trace/redact.js";
38
38
  import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
39
- import { executionLaneHomeDir } from "../execution-lane-caps.js";
39
+ import { executionLaneCaps, executionLaneHomeDir, executionLaneOf } from "../execution-lane-caps.js";
40
40
  import { effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
41
41
  import { memoryFaceMounted } from "../memory-scope.js";
42
42
  export function createResolveSpec(ctx) {
43
43
  const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, noteSessionShellGate, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, } = ctx;
44
44
  assertGuardPatternsUsable(config);
45
45
  const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, { durableEnabled, singleUserAutoAcceptBaseline });
46
+ const realTaskRootOf = (auth) => {
47
+ if (isSandboxPathAdjudicationLane(config.remoteExec?.provider))
48
+ return undefined;
49
+ const sessionCwd = auth?.sessionId ? effectiveHostWorkspace(perSessionCwd.get(auth.sessionId), {}) : undefined;
50
+ return sessionCwd ?? (config.remoteExec === undefined ? process.cwd() : undefined);
51
+ };
46
52
  if (onlySensitiveBaseline)
47
53
  logger.warn(onlySensitiveBaseline.event, onlySensitiveBaseline.fields);
48
54
  const resolveSpec = async (body, req, auth, opts) => {
@@ -88,7 +94,7 @@ export function createResolveSpec(ctx) {
88
94
  };
89
95
  const gateScenarioAndAppend = async (body, auth, opts) => {
90
96
  const requested = gateScenarioRequest(await principalCaps?.scenarioRuling(auth?.principal), body.scenario, config.defaultScenario, Object.keys(scenarios), logger);
91
- gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), config.remoteExec?.provider ?? "in-process", logger);
97
+ gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), executionLaneOf(config.remoteExec?.provider), logger);
92
98
  const scenarioName = scenarios[requested] ? requested : "default";
93
99
  const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal, auth?.sessionId !== undefined ? { sessionId: auth.sessionId } : {});
94
100
  const centerDecls = getCenterPrompts()?.declarations;
@@ -144,7 +150,7 @@ export function createResolveSpec(ctx) {
144
150
  const bindSettingsCwdEnvAndModel = (body, auth, gated, effMode, opts) => {
145
151
  const { objective, parsedSettings } = gated;
146
152
  const cwdWriteGate = cwdHonored(config) || deviceCwdHonored(config);
147
- const execLane = config.remoteExec?.provider ?? "in-process";
153
+ const execLane = executionLaneOf(config.remoteExec?.provider);
148
154
  if (auth?.sessionId)
149
155
  fenceSessionOwner(auth.sessionId, auth.principal ?? null);
150
156
  if (typeof body.cwd === "string" && body.cwd.length > 0 && auth?.sessionId) {
@@ -391,7 +397,7 @@ export function createResolveSpec(ctx) {
391
397
  })(),
392
398
  ...(config.requirePrincipal !== true ? { backgroundScope: "session" } : {}),
393
399
  memory: memoryEngine
394
- ? memorySpecForRequest(auth?.memoryScope, body.memoryWrite, s4DefaultScopes, { multiTenant: config.requirePrincipal === true }, { memory: body.memory, memoryCapture: body.memoryCapture })
400
+ ? memorySpecForRequest(auth?.memoryScope, memoryProjectKeyFor(config, auth?.memoryScope, realTaskRootOf(auth)), body.memoryWrite, s4DefaultScopes, { multiTenant: config.requirePrincipal === true }, { memory: body.memory, memoryCapture: body.memoryCapture })
395
401
  : undefined,
396
402
  ...((v) => (v !== undefined ? { memoryPersistenceCapable: v } : {}))(effectiveMemoryPersistenceCapable(config)),
397
403
  tools: ((base) => {
@@ -450,8 +456,7 @@ export function createResolveSpec(ctx) {
450
456
  };
451
457
  const pathAdjudication = hostSemanticsLane
452
458
  ? (() => {
453
- const sessionCwd = auth?.sessionId ? effectiveHostWorkspace(perSessionCwd.get(auth.sessionId), {}) : undefined;
454
- const realRoot = sessionCwd ?? (config.remoteExec === undefined ? process.cwd() : undefined);
459
+ const realRoot = realTaskRootOf(auth);
455
460
  const gateCwd = realRoot ?? join(config.localDataRoot ?? localRoot, "fs-write-gate-unrooted");
456
461
  return {
457
462
  env: new NodeExecutionEnv({ cwd: gateCwd }),
@@ -558,7 +563,7 @@ export function createResolveSpec(ctx) {
558
563
  handsReadOnly: governed.handsReadOnly === true,
559
564
  excludeAllTools: governed.excludeAllTools === true,
560
565
  excludeTools: governed.excludeTools,
561
- remoteExecutionEnv: config.remoteExec !== undefined,
566
+ ...buildNoticeLaneFacts(executionLaneOf(config.remoteExec?.provider)),
562
567
  })) {
563
568
  governed = { ...governed, promptProvider: withNoPersistentMemoryNotice(governed.promptProvider) };
564
569
  }
@@ -619,7 +624,7 @@ export function createResolveSpec(ctx) {
619
624
  const isolatedExecEnv = isIsolatedExecEnv(config.remoteExec?.provider, config.remoteExec?.provider === "k8s" ? config.remoteExec.runtimeClass : undefined);
620
625
  const decision = routeServiceTask({
621
626
  isolatedExecEnv,
622
- hasExecutionEnv: config.remoteExec != null,
627
+ hasExecutionEnv: executionLaneCaps(executionLaneOf(config.remoteExec?.provider)).coreRemoteExecutionEnv === "supported",
623
628
  ...(config.autonomy ? { autonomy: config.autonomy } : {}),
624
629
  explicitTeam: body.council === true || body.debate === true || scenarioName === "discuss",
625
630
  });
@@ -191,7 +191,8 @@ export interface RunnerDepsCtx {
191
191
  sessionPolicyStore: ReturnType<StoreBackend["sessionPolicy"]> | undefined;
192
192
  runtimeCapsResolver: RunnerDeps["runtimeCapsResolver"];
193
193
  /** [ref] per-edited-file rewind history (core `RunnerDeps.fileHistoryStore`; replaces the retired E19
194
- * whole-tree snapshot seat — core 94632197 renamed the seat, not a coexistence). WIRING IS THE OPT-IN. */
194
+ * whole-tree snapshot seat — core 94632197 renamed the seat, not a coexistence). WIRING IS THE OPT-IN.
195
+ * S-589:值 = `assembleFileHistory` 的捕获脸(后端 ∧ `FILE_HISTORY_CAPTURE`)。 */
195
196
  fileHistoryStore: ReturnType<StoreBackend["fileHistory"]> | undefined;
196
197
  /** [ref] 车二:持久化权限规则店 provider(core `RunnerDeps.permissionRuleStore`)。缺席 ⇒ 引擎的
197
198
  * `permissionRules.storeWired` 如实报 false、`AskRequest.ruleOffers` 不铸(诚实缺席)。 */
@@ -5,6 +5,7 @@ import type { WorkflowCompletionInbox } from "../orchestration/workflow-completi
5
5
  import { PlanCacheProbe } from "../plan-cache-probe.js";
6
6
  import type { CheckpointStoreFull, StoreBackend, ToolResultStoreFull } from "../plugins/store-backend.js";
7
7
  import type { TaskAttachmentStore } from "../plugins/task-attachment-store.js";
8
+ import type { FileHistoryErasureFace } from "../file-history-capture.js";
8
9
  import type { OwnerAwareSessionStore } from "../security.js";
9
10
  import type { DeviceStore } from "../device-store.js";
10
11
  import { SessionWatchRegistry } from "../session-watch.js";
@@ -25,7 +26,9 @@ export interface SessionFacesCtx {
25
26
  approvalExemptionStore: ReturnType<StoreBackend["approvalExemption"]> | undefined;
26
27
  sessionPolicyStore: ReturnType<StoreBackend["sessionPolicy"]> | undefined;
27
28
  taskAttachmentStore: TaskAttachmentStore | undefined;
28
- fileHistoryStore: ReturnType<StoreBackend["fileHistory"]> | undefined;
29
+ /** S-589:文件历史的**擦除脸**(后端在即在,与 `FILE_HISTORY_CAPTURE` 无关;只有删除类动词)。E21 删除读它,
30
+ * 不读捕获脸 —— 关掉捕获的部署照样要删得掉已捕获的文件字节(旋钮只管写入,从不管擦除)。 */
31
+ fileHistoryErasure: FileHistoryErasureFace | undefined;
29
32
  workflowCompletionInbox: WorkflowCompletionInbox | undefined;
30
33
  /** [ref]:会话级任务清单车道(E21 级联的一条腿)。缺席 = 装配未接本车道 ⇒ 无清单可删。 */
31
34
  taskListLane: TaskListLane | undefined;
@@ -6,7 +6,7 @@ import { setLeafAdvanceListener } from "../session-leaf-bus.js";
6
6
  import { SessionWatchRegistry, posIntEnv } from "../session-watch.js";
7
7
  import { defaultTaskRegistry, peerSessionBoxHandle } from "@sema-agent/core";
8
8
  export function createSessionFaces(ctx) {
9
- const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, mailboxStore, mailboxRecipientLifecycle, } = ctx;
9
+ const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryErasure, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, mailboxStore, mailboxRecipientLifecycle, } = ctx;
10
10
  const mysqlPool = backend?.mysqlPool();
11
11
  const auditPgPool = backend?.pgPool();
12
12
  const auditLocalRoot = backend?.kind === "local" ? (config.localDataRoot ?? localRoot) : undefined;
@@ -66,11 +66,11 @@ export function createSessionFaces(ctx) {
66
66
  if (deviceStore)
67
67
  await deviceStore.deleteByRootSession(sessionId);
68
68
  dropSessionScoped?.(sessionId);
69
- if (fileHistoryStore) {
70
- if (typeof fileHistoryStore.deleteBySession === "function")
71
- await fileHistoryStore.deleteBySession(sessionId);
69
+ if (fileHistoryErasure) {
70
+ if (typeof fileHistoryErasure.deleteBySession === "function")
71
+ await fileHistoryErasure.deleteBySession(sessionId);
72
72
  else {
73
- await fileHistoryStore.reap(sessionId, []);
73
+ await fileHistoryErasure.reap(sessionId, []);
74
74
  logger.warn("session_file_history_baselines_retained", {
75
75
  sessionId,
76
76
  note: "the local file-history store has no whole-scope purge verb; boundaries were reaped but v1 baselines (original file bytes) remain under the data root until the upstream purge seam lands — recorded as an open deletion-rights gap, not silent",
@@ -9,6 +9,7 @@ export declare function runStage04(ctx: Awaited<ReturnType<typeof runStage03>>):
9
9
  prevStreak: number;
10
10
  error?: string;
11
11
  }) => void;
12
+ fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
12
13
  fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
13
14
  fleetLease: import("../fleet-lease.js").FleetLeaseManager | undefined;
14
15
  fleetUsage: import("../fleet-client.js").FleetUsageAccumulator | undefined;
@@ -27,7 +27,7 @@ export async function runStage04(ctx) {
27
27
  if (adoptionBoot.scanned > 0 || adoptionBoot.error !== undefined) {
28
28
  logger.info("adoption_boot_scan", { ...adoptionBoot });
29
29
  }
30
- const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
30
+ const { brain, pricing, counterDegradeHook, costQuota, modelUsageTracker, promptManifestTracker, fleetUsage, fleetLease, tracer, sideQueryAccounting, toolResultStore, sessionPolicyStore, fileHistoryStore, fileHistoryErasure, } = createBudgetAndTracing({ config, logger, metrics, backend, breakerState });
31
31
  const permissionRuleStores = config.permissionRulesEnabled && backend ? backend.permissionRule() : undefined;
32
32
  const permissionRuleProvider = permissionRuleStores
33
33
  ? createPermissionRuleStoreProvider({ durable: permissionRuleStores.durable })
@@ -44,6 +44,6 @@ export async function runStage04(ctx) {
44
44
  });
45
45
  }
46
46
  await auditDormantPermissionRules({ stores: permissionRuleStores, logger, explicit: config.permissionRulesEnabledExplicit });
47
- return { ...ctx, brain, costQuota, counterDegradeHook, fileHistoryStore, fleetLease, fleetUsage, memoryBundleFaces, memoryComplianceFaces, memoryOriginFaces, modelUsageTracker, permissionRuleProvider, permissionRuleStores, pricing, promptManifestTracker, ruleConsent, sessionMemoryStatusFace, sessionPolicyStore, sideQueryAccounting, toolResultStore, tracer };
47
+ return { ...ctx, brain, costQuota, counterDegradeHook, fileHistoryErasure, fileHistoryStore, fleetLease, fleetUsage, memoryBundleFaces, memoryComplianceFaces, memoryOriginFaces, modelUsageTracker, permissionRuleProvider, permissionRuleStores, pricing, promptManifestTracker, ruleConsent, sessionMemoryStatusFace, sessionPolicyStore, sideQueryAccounting, toolResultStore, tracer };
48
48
  }
49
49
  //# sourceMappingURL=stage-04-stores.js.map
@@ -22,6 +22,7 @@ export declare function runStage05(ctx: Awaited<ReturnType<typeof runStage04>>):
22
22
  prevStreak: number;
23
23
  error?: string;
24
24
  }) => void;
25
+ fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
25
26
  fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
26
27
  fleetLease: import("../fleet-lease.js").FleetLeaseManager | undefined;
27
28
  fleetUsage: import("../fleet-client.js").FleetUsageAccumulator | undefined;
@@ -83,6 +83,7 @@ export declare function runStage06(ctx: Awaited<ReturnType<typeof runStage05>>):
83
83
  prevStreak: number;
84
84
  error?: string;
85
85
  }) => void;
86
+ fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
86
87
  fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
87
88
  fleetLease: import("../fleet-lease.js").FleetLeaseManager | undefined;
88
89
  fleetUsage: import("../fleet-client.js").FleetUsageAccumulator | undefined;
@@ -98,6 +98,7 @@ export declare function runStage07(ctx: Awaited<ReturnType<typeof runStage06>>):
98
98
  prevStreak: number;
99
99
  error?: string;
100
100
  }) => void;
101
+ fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
101
102
  fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
102
103
  fleetLease: import("../fleet-lease.js").FleetLeaseManager | undefined;
103
104
  fleetUsage: import("../fleet-client.js").FleetUsageAccumulator | undefined;
@@ -6,9 +6,9 @@ import { parseCapEnv, parseNumOrFailNonNegative } from "../config.js";
6
6
  import { ensureChildSessionDurableWithPromotion } from "../plugins/session-store.js";
7
7
  import { webSearchConfigFromEnv, createWebSearchBackend, shouldProbeWebSearchOnBoot } from "../plugins/web-search.js";
8
8
  import { createAuthorizer } from "../security.js";
9
- import { assertGateIntentServiceable, buildForceDurableGateInertNotice, hasOperatorGateIntent } from "../approval.js";
10
- import { loadSkills } from "../capabilities/skills.js";
11
- import { toolsRunOnThisHost } from "../task-cwd.js";
9
+ import { assertGateIntentServiceable, buildForceDurableGateInertNotice, buildNoResponderBootWarns, hasOperatorGateIntent } from "../approval.js";
10
+ import { executionLaneOf } from "../execution-lane-caps.js";
11
+ import { loadDeploymentSkills } from "../capabilities/skills.js";
12
12
  import { createRepoClient } from "../capabilities/repo-tools.js";
13
13
  import { buildScenarios, builtinScenarioDetails } from "../capabilities/scenarios.js";
14
14
  import { RateLimiter } from "../observability/rate-limit.js";
@@ -73,16 +73,12 @@ export async function runStage07(ctx) {
73
73
  tasksUsable: runStore !== undefined,
74
74
  });
75
75
  }
76
- if ((config.remoteExec === undefined || config.remoteExec.provider === "host") && !toolApproval && !durableEnabled) {
77
- logger.warn("fs_write_gate_no_responder", {
78
- hint: "host-semantics lane with no approval face: permissionMode default/auto Write/Edit asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
79
- });
80
- }
81
- if (config.selfOrchestrationEnabled && config.remoteExec !== undefined && config.remoteExec.provider !== "host" && !toolApproval && !durableEnabled) {
82
- logger.warn("workflow_gate_no_responder", {
83
- hint: "self-orchestration with no approval face: permissionMode default/auto/acceptEdits run_workflow asks will auto-deny (fail-closed); wire TOOL_APPROVAL_ENABLED=true or DURABLE_APPROVAL=true to answer them",
84
- });
85
- }
76
+ for (const w of buildNoResponderBootWarns({
77
+ lane: executionLaneOf(config.remoteExec?.provider),
78
+ selfOrchestrationEnabled: config.selfOrchestrationEnabled,
79
+ approvalFaceWired: toolApproval !== undefined || durableEnabled,
80
+ }))
81
+ logger.warn(w.event, w.fields);
86
82
  if (singleUserAutoAcceptBaseline) {
87
83
  logger.info("tool_policy_single_user_default", {
88
84
  posture: "adjudicated auto-accept — ALL write/hand/MCP tool surfaces auto-allowed (core per-surface UNGATED enumeration suppressed for single-user turnkey)",
@@ -110,7 +106,7 @@ export async function runStage07(ctx) {
110
106
  ? backend.rateLimiter(config.rateLimitPerMin, counterDegradeHook).startRefresh()
111
107
  : new RateLimiter(config.rateLimitPerMin);
112
108
  bindRateLimiter(rateLimiter);
113
- let skills = loadSkills(config.skillsDir, { toolsShareThisFilesystem: toolsRunOnThisHost(config) });
109
+ let skills = loadDeploymentSkills(config);
114
110
  skills = await configCenter.applyCenterCapabilities(skills);
115
111
  const repoClient = config.gitApiBaseUrl ? createRepoClient(config.gitApiKind, config.gitApiBaseUrl, config.gitApiToken) : undefined;
116
112
  const webSearchCfg = webSearchConfigFromEnv();
@@ -109,10 +109,9 @@ export declare function runStage08(ctx: Awaited<ReturnType<typeof runStage07>>):
109
109
  kind: string;
110
110
  streak: number;
111
111
  prevStreak: number;
112
- error
113
- /** 租约 TTL = 2× sweep 间隔(§3)。抢租与每 domain 的续租共用同一个值 —— 两处手抄迟早只改一处。 */
114
- ?: string;
112
+ error?: string;
115
113
  }) => void;
114
+ fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
116
115
  fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
117
116
  fleetLease: import("../fleet-lease.js").FleetLeaseManager | undefined;
118
117
  fleetUsage: import("../fleet-client.js").FleetUsageAccumulator | undefined;
@@ -5,10 +5,10 @@ import { startRetentionLane, RETENTION_LEASE_TTL_FACTOR } from "./retention-lane
5
5
  import { createLimitSync } from "./limit-sync.js";
6
6
  import { runDenySweep } from "./late-binds.js";
7
7
  export function runStage08(ctx) {
8
- const { approvalNonceStore, backend, backgroundAgentStore, ceilingVerdict, checkpointStore, config, configCenter, costQuota, fileHistoryStore, handslessRunner, handslessSubRunner, hookAgentRunner, imageBakes, instanceId, localRoot, logger, mailboxStore, metrics, permissionRuleStores, pricing, rateLimiter, retentionExecutor, retentionLaneStore, rosterStore, runStore, runner, runnerTierFrozen, sqlWorkflowRunStore, subRunner, taskAttachmentStore, toolApproval, toolResultStore, workflowJournalStore, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, worktreeReap } = ctx;
8
+ const { approvalNonceStore, backend, backgroundAgentStore, ceilingVerdict, checkpointStore, config, configCenter, costQuota, fileHistoryErasure, handslessRunner, handslessSubRunner, hookAgentRunner, imageBakes, instanceId, localRoot, logger, mailboxStore, metrics, permissionRuleStores, pricing, rateLimiter, retentionExecutor, retentionLaneStore, rosterStore, runStore, runner, runnerTierFrozen, sqlWorkflowRunStore, subRunner, taskAttachmentStore, toolApproval, toolResultStore, workflowJournalStore, workflowNotifyGate, workflowNotifyJournal, workflowRecoverOpts, worktreeReap } = ctx;
9
9
  const reaper = startReapers({
10
10
  config, logger, metrics, localRoot, backend, subRunner, runStore, checkpointStore,
11
- rateLimiter, costQuota, toolResultStore, fileHistoryStore, taskAttachmentStore, imageBakes, worktreeReap,
11
+ rateLimiter, costQuota, toolResultStore, fileHistoryErasure, taskAttachmentStore, imageBakes, worktreeReap,
12
12
  workflowNotifyGate, workflowRecoverOpts, workflowJournalStore, sqlWorkflowRunStore, workflowNotifyJournal, rosterStore,
13
13
  backgroundAgentStore, mailboxStore, toolApproval, permissionRuleStores, approvalNonceStore,
14
14
  getRunDenySweep: () => runDenySweep,
@@ -145,6 +145,7 @@ export declare function runStage09(ctx: Awaited<ReturnType<typeof runStage08>>):
145
145
  prevStreak: number;
146
146
  error?: string;
147
147
  }) => void;
148
+ fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
148
149
  fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
149
150
  fleetLease: import("../fleet-lease.js").FleetLeaseManager | undefined;
150
151
  fleetUsage: import("../fleet-client.js").FleetUsageAccumulator | undefined;
@@ -6,12 +6,12 @@ import { createLeaderFace } from "./leader.js";
6
6
  import { assertStaticWiringConsistent } from "../http/routes/diagnostics.js";
7
7
  import { buildDisplaySafeUrl } from "../trace/redact.js";
8
8
  export function runStage09(ctx) {
9
- const { approvalExemptionStore, backend, brain, checkpointStore, config, configCenter, deviceLane, dropSessionScoped, durableEnabled, executionEnvFactory, fileHistoryStore, governanceSeams, localRoot, logger, mailboxRecipientLifecycle, mailboxStore, metrics, pricing, resumeAnchorStore, runStore, runnerDeps, sessionPolicyStore, sessionStore, streamApprovalGate, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore, workflowCompletionInbox } = ctx;
9
+ const { approvalExemptionStore, backend, brain, checkpointStore, config, configCenter, deviceLane, dropSessionScoped, durableEnabled, executionEnvFactory, fileHistoryErasure, governanceSeams, localRoot, logger, mailboxRecipientLifecycle, mailboxStore, metrics, pricing, resumeAnchorStore, runStore, runnerDeps, sessionPolicyStore, sessionStore, streamApprovalGate, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore, workflowCompletionInbox } = ctx;
10
10
  const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
11
11
  deviceStore: deviceLane?.store,
12
12
  dropSessionScoped,
13
13
  config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore,
14
- resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore,
14
+ resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryErasure,
15
15
  workflowCompletionInbox, taskListLane, mailboxStore, mailboxRecipientLifecycle,
16
16
  });
17
17
  const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend, tracer, usageWindowStore, promptSource: configCenter.promptSource, onError: runnerDeps.onError });
@@ -145,6 +145,7 @@ export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>):
145
145
  prevStreak: number;
146
146
  error?: string;
147
147
  }) => void;
148
+ fileHistoryErasure: import("../file-history-capture.js").FileHistoryErasureFace | undefined;
148
149
  fileHistoryStore: import("../plugins/store-backend.js").ServiceFileHistoryStore | undefined;
149
150
  fleetLease: import("../fleet-lease.js").FleetLeaseManager | undefined;
150
151
  fleetUsage: import("../fleet-client.js").FleetUsageAccumulator | undefined;
@@ -32,7 +32,7 @@ import { assertSchemaMatchesCode } from "../plugins/schema-consistency.js";
32
32
  import { assertCheckpointColumnSchema } from "../plugins/checkpoint-store-sql.js";
33
33
  import { assertFileHistorySchema } from "../plugins/file-history-store-sql.js";
34
34
  import { resolveStreamApprovalGate } from "../tool-approval.js";
35
- import { buildMemoryWriteBoundaryAudit, effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
35
+ import { buildMemoryBoundaryLaneFacts, buildMemoryWriteBoundaryAudit, effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
36
36
  import { buildMemoryPosture } from "../memory-posture.js";
37
37
  import { establishSqlEnginePosture } from "../sql-engine-posture.js";
38
38
  import { buildDisplaySafeUrl } from "../trace/redact.js";
@@ -194,25 +194,19 @@ export async function openStores(ctx) {
194
194
  }
195
195
  }
196
196
  {
197
- const lane = config.remoteExec?.provider;
198
- const hostSemanticsLane = lane === undefined || lane === "host";
199
- const coreRemoteExecutionEnv = config.remoteExec !== undefined;
200
- const containmentRoots = lane === undefined ? [process.cwd()] : [];
197
+ const laneFacts = buildMemoryBoundaryLaneFacts(config, process.cwd());
201
198
  const memoryMountRoot = memoryEngine ? memoryMountRootOf(memoryEngine.backend, memoryEngine.root) : undefined;
202
199
  const audit = buildMemoryWriteBoundaryAudit({
203
200
  memoryRoot: memoryMountRoot,
204
- lane: lane ?? "in-process",
205
- hostSemanticsLane,
206
- coreRemoteExecutionEnv,
201
+ ...laneFacts,
207
202
  declaredCapable: effectiveMemoryPersistenceCapable(config),
208
- containmentRoots,
209
203
  engineBackend: config.memoryEngineBackend,
210
204
  });
211
205
  for (const w of audit.warnings)
212
- logger.warn(w.tag, { detail: w.detail, lane: lane ?? "in-process", root: memoryMountRoot ?? null });
206
+ logger.warn(w.tag, { detail: w.detail, lane: laneFacts.lane, root: memoryMountRoot ?? null });
213
207
  if (config.memoryPersistenceCapable === undefined && effectiveMemoryPersistenceCapable(config) === true) {
214
208
  logger.info?.("memory_persistence_capable_auto_declared", {
215
- lane: lane ?? "in-process",
209
+ lane: laneFacts.lane,
216
210
  detail: "REMOTE_EXEC=host: file tools run on this machine's filesystem, so the assembler declares memoryPersistenceCapable=true (the core-side remote-env withhold would otherwise silently drop the memory WRITE instruction on this lane). Override with MEMORY_PERSISTENCE_CAPABLE=true|false.",
217
211
  });
218
212
  }
package/dist/brain.js CHANGED
@@ -2,7 +2,8 @@ import { adjudicateModelRoute, createAnthropicBrain, createCircuitBreakerBrain,
2
2
  import { routeKeyOf } from "@sema-agent/core";
3
3
  import { buildDefaultResilience } from "./config-types.js";
4
4
  import { resolveModelApiKey } from "./key-resolver.js";
5
- import { catalogBrainLane } from "./model-provider.js";
5
+ import { dispatchBrainLane } from "./model-provider.js";
6
+ import { openaiRouteBaseUrl } from "./model-route-endpoint.js";
6
7
  import { buildDisplaySafeRefusal, buildDisplaySafeUrl } from "./trace/redact.js";
7
8
  function isOffDeploymentRoot(baseUrl, config) {
8
9
  if (!baseUrl)
@@ -10,11 +11,17 @@ function isOffDeploymentRoot(baseUrl, config) {
10
11
  const entry = routeKeyOf(baseUrl);
11
12
  if (entry === "")
12
13
  return false;
13
- return ![config.gatewayBaseUrl, ...config.gatewayFallbackUrls].some((u) => routeKeyOf(u) === entry);
14
+ return !deploymentGatewayRoots(config).some((u) => routeKeyOf(u) === entry);
15
+ }
16
+ function deploymentGatewayRoots(config) {
17
+ return config.gatewayBaseUrl !== undefined ? [config.gatewayBaseUrl, ...config.gatewayFallbackUrls] : [];
18
+ }
19
+ function deploymentGatewayKey(config) {
20
+ return config.gatewayBaseUrl !== undefined ? config.gatewayApiKey : undefined;
14
21
  }
15
22
  function degradeDeclaredRoot(fallbackBaseUrl, config) {
16
- const armed = Boolean(config.gatewayApiKey) && isOffDeploymentRoot(fallbackBaseUrl, config);
17
- return armed ? config.gatewayBaseUrl : (fallbackBaseUrl || config.gatewayBaseUrl);
23
+ const armed = Boolean(deploymentGatewayKey(config)) && isOffDeploymentRoot(fallbackBaseUrl, config);
24
+ return armed ? config.gatewayBaseUrl : openaiRouteBaseUrl(fallbackBaseUrl, config.gatewayBaseUrl);
18
25
  }
19
26
  export const FAST_FAIL_MAX_RETRIES = 2;
20
27
  function constructRouteBrain(label, sourceUrls, construct) {
@@ -41,12 +48,12 @@ export function createBrain(config, deps = {}) {
41
48
  : fastFailTopology
42
49
  ? { maxRetries: FAST_FAIL_MAX_RETRIES }
43
50
  : {};
44
- const routes = [config.gatewayBaseUrl, ...config.gatewayFallbackUrls];
51
+ const routes = deploymentGatewayRoots(config);
45
52
  const openaiBrains = routes.map((baseUrl, i) => {
46
53
  const brain = constructRouteBrain(i === 0 ? "gateway route" : `gateway fallback route[${i - 1}]`, [baseUrl], () => createOpenAIBrain({
47
54
  baseUrl,
48
55
  get apiKey() {
49
- return config.gatewayApiKey;
56
+ return deploymentGatewayKey(config);
50
57
  },
51
58
  ...retryCap,
52
59
  fetchImpl,
@@ -62,7 +69,11 @@ export function createBrain(config, deps = {}) {
62
69
  })
63
70
  : brain;
64
71
  });
65
- const gatewayBrain = openaiBrains.length > 1 ? createFailoverBrain(openaiBrains) : openaiBrains[0];
72
+ const gatewayBrain = openaiBrains.length === 0
73
+ ? constructRouteBrain("openai route (no gateway declared)", [], () => createOpenAIBrain({ ...retryCap, fetchImpl, ...timeouts }))
74
+ : openaiBrains.length > 1
75
+ ? createFailoverBrain(openaiBrains)
76
+ : openaiBrains[0];
66
77
  const anthropicCfg = config.anthropic;
67
78
  const anthropicBrain = anthropicCfg
68
79
  ? constructRouteBrain("anthropic route", [anthropicCfg.baseUrl], () => createAnthropicBrain({
@@ -86,9 +97,8 @@ export function createBrain(config, deps = {}) {
86
97
  if (fallbackModel) {
87
98
  const fbOwnApiKey = () => resolveModelApiKey(fallbackModel.name, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {}) ??
88
99
  resolveModelApiKey(degradeTo, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {});
89
- const fallbackLane = catalogBrainLane(fallbackModel.provider);
90
- const anthropicLaneHop = fallbackLane === "anthropic" && anthropicBrain !== undefined;
91
- const fallback = fallbackLane === "anthropic" && anthropicBrain
100
+ const anthropicLaneHop = dispatchBrainLane(fallbackModel.provider, anthropicBrain !== undefined) === "anthropic";
101
+ const fallback = anthropicLaneHop && anthropicBrain
92
102
  ?
93
103
  anthropicBrain
94
104
  : constructRouteBrain(`degrade target "${degradeTo}"`, [fallbackModel.baseUrl, config.gatewayBaseUrl], () => createOpenAIBrain({
@@ -96,7 +106,7 @@ export function createBrain(config, deps = {}) {
96
106
  return degradeDeclaredRoot(fallbackModel.baseUrl, config);
97
107
  },
98
108
  get apiKey() {
99
- return config.gatewayApiKey || undefined;
109
+ return deploymentGatewayKey(config) || undefined;
100
110
  },
101
111
  ...(config.gatewayMaxRetries !== undefined ? { maxRetries: config.gatewayMaxRetries } : {}),
102
112
  fetchImpl,
@@ -129,8 +139,10 @@ export function brainSummary(config) {
129
139
  const r = config.resilience ?? DEFAULT_RESILIENCE;
130
140
  const hasFailover = config.gatewayFallbackUrls.length > 0;
131
141
  const breakerActive = r.circuitBreaker && hasFailover;
142
+ const degradeTarget = config.degrade?.reactive ? config.models?.[config.degrade.to] : undefined;
143
+ const degradeEndpoint = degradeTarget ? openaiRouteBaseUrl(degradeTarget.baseUrl, config.gatewayBaseUrl) : undefined;
132
144
  return {
133
- gateway: buildDisplaySafeUrl(config.gatewayBaseUrl),
145
+ gateway: buildDisplaySafeUrl(config.gatewayBaseUrl ?? "") || null,
134
146
  gatewayFallbacks: config.gatewayFallbackUrls.length,
135
147
  failover: hasFailover,
136
148
  anthropicRoute: Boolean(config.anthropic),
@@ -147,14 +159,12 @@ export function brainSummary(config) {
147
159
  : "engine-default",
148
160
  circuitBreakerNoop: r.circuitBreaker && !hasFailover ? "needs ≥2 gateway routes" : null,
149
161
  reactiveDegradeTo: config.degrade?.reactive && config.models?.[config.degrade.to] ? config.degrade.to : null,
150
- reactiveDegradeBaseUrl: config.degrade?.reactive && config.models?.[config.degrade.to]
151
- ? buildDisplaySafeUrl(config.models[config.degrade.to].baseUrl || config.gatewayBaseUrl)
152
- : null,
162
+ reactiveDegradeBaseUrl: buildDisplaySafeUrl(degradeEndpoint ?? "") || null,
153
163
  ...((() => {
154
164
  const target = config.degrade?.reactive ? config.models?.[config.degrade.to] : undefined;
155
165
  if (!target)
156
166
  return { reactiveDegradeRoutePairing: null, reactiveDegradeRouteRefusal: null };
157
- if (catalogBrainLane(target.provider) === "anthropic" && config.anthropic) {
167
+ if (dispatchBrainLane(target.provider, config.anthropic !== undefined) === "anthropic") {
158
168
  return { reactiveDegradeRoutePairing: "anthropic-lane", reactiveDegradeRouteRefusal: null };
159
169
  }
160
170
  const declaresOwnCredential = config.modelApiKeyEnv?.[target.name] !== undefined ||
@@ -165,12 +175,12 @@ export function brainSummary(config) {
165
175
  if (declaresOwnCredential) {
166
176
  return { reactiveDegradeRoutePairing: "per-model-declared", reactiveDegradeRouteRefusal: null };
167
177
  }
168
- const verdict = adjudicateModelRoute({ id: target.id, baseUrl: target.baseUrl }, resolveRouteCredential({ ...(config.gatewayApiKey ? { configApiKey: config.gatewayApiKey } : {}) }), { baseUrl: degradeDeclaredRoot(target.baseUrl, config) });
178
+ const verdict = adjudicateModelRoute({ id: target.id, baseUrl: target.baseUrl }, resolveRouteCredential(((key) => (key ? { configApiKey: key } : {}))(deploymentGatewayKey(config))), { baseUrl: degradeDeclaredRoot(target.baseUrl, config) });
169
179
  return verdict.ok
170
180
  ? { reactiveDegradeRoutePairing: `ok:${verdict.posture}`, reactiveDegradeRouteRefusal: null }
171
181
  : {
172
182
  reactiveDegradeRoutePairing: `broken:${verdict.code}`,
173
- reactiveDegradeRouteRefusal: buildDisplaySafeRefusal(routeRefusalText(verdict), [target.baseUrl ?? "", degradeDeclaredRoot(target.baseUrl, config)]),
183
+ reactiveDegradeRouteRefusal: buildDisplaySafeRefusal(routeRefusalText(verdict), [target.baseUrl ?? "", degradeDeclaredRoot(target.baseUrl, config) ?? ""]),
174
184
  };
175
185
  })()),
176
186
  };
@@ -1,18 +1,16 @@
1
- import type { RemoteExecProvider } from "../execution-lane-caps.js";
1
+ import { type ExecutionLane, type RemoteExecProvider } from "../execution-lane-caps.js";
2
2
  export interface ExecutionLaneCapability {
3
- /** 本部署的执行车道词(闭集 = {@link RemoteExecProvider};`REMOTE_EXEC` 未设 ⇒ `"host"`,见头注)。 */
4
- readonly provider: RemoteExecProvider;
3
+ /** 本部署的执行车道词(闭集 = {@link ExecutionLane} 八词;`REMOTE_EXEC` 未设 ⇒ `"in-process"`,见头注 S-602 段)。 */
4
+ readonly provider: ExecutionLane;
5
5
  /** 工具跑的那台机器就是本进程这台吗(= server 盘上的绝对路径对模型的工具有没有意义)。
6
6
  * 属主 = `task-cwd.ts` 的 {@link toolsRunOnThisHost},与 `skills[].baseDir` 的铸点同一只函数。 */
7
7
  readonly toolsOnThisHost: boolean;
8
8
  }
9
9
  /**
10
10
  * 部署席 → 消费端窄投影。形照 `projectWebSearchCapability` / `projectSqlEngineCapability`:
11
- * **只 import 型**,不碰 config.ts,于是本模块是叶。
11
+ * 不碰 config.ts(只从车道表模块取词与归一读口,那是叶),于是本模块是叶。
12
12
  *
13
- * 🔴 **唯一**写缺席折词的地方。`"host"` 这个字面量不入 `api-notice-text-freeze`:它是
14
- * {@link RemoteExecProvider} 的成员,配置联合里那条臂改名 ⇒ 本行当场 tsc 红(S-382 对三个 provider 词
15
- * 免入表的同一条理由 —— 会静默漂的只有**本仓自铸**的词,这里一个都没有)。
13
+ * 🔴 本处**不写**任何缺席折词(S-602 门 D):缺席归一只在 {@link executionLaneOf} 一处。
16
14
  */
17
15
  export declare function projectExecutionLaneCapability(config: {
18
16
  readonly remoteExec?: {