@sema-agent/server 7.51.0 → 7.52.1

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 (203) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/USAGE.md +103 -24
  4. package/dist/adoption/plan.d.ts +4 -4
  5. package/dist/adoption/plan.js +11 -0
  6. package/dist/approval-card.d.ts +26 -2
  7. package/dist/approval-card.js +9 -0
  8. package/dist/approval-hmac.d.ts +1 -1
  9. package/dist/approval-reconciler.d.ts +2 -2
  10. package/dist/approval-reconciler.js +1 -1
  11. package/dist/bench/l8/artifact.d.ts +1 -1
  12. package/dist/bench/s1/arms.d.ts +1 -1
  13. package/dist/bench/s1/live-deps.d.ts +1 -1
  14. package/dist/bench/s1/oracle.d.ts +1 -1
  15. package/dist/bench/s1/row.d.ts +1 -1
  16. package/dist/boot/budget-tracing.d.ts +1 -1
  17. package/dist/boot/budget-tracing.js +2 -5
  18. package/dist/boot/coordinators.js +3 -1
  19. package/dist/boot/deferred-sandbox-path-env.d.ts +1 -1
  20. package/dist/boot/device-lane.js +2 -0
  21. package/dist/boot/engine-lease.d.ts +1 -1
  22. package/dist/boot/execution-env.d.ts +1 -1
  23. package/dist/boot/governance-seams.d.ts +2 -2
  24. package/dist/boot/leader.d.ts +1 -1
  25. package/dist/boot/memory-boundary.d.ts +2 -2
  26. package/dist/boot/memory-consolidation.d.ts +6 -6
  27. package/dist/boot/org-memory.d.ts +1 -1
  28. package/dist/boot/parked-revive-gate.d.ts +3 -3
  29. package/dist/boot/reapers.d.ts +2 -2
  30. package/dist/boot/reapers.js +17 -4
  31. package/dist/boot/resolve-spec.d.ts +12 -0
  32. package/dist/boot/resolve-spec.js +29 -7
  33. package/dist/boot/retention-lane.d.ts +2 -2
  34. package/dist/boot/runner-deps.d.ts +17 -7
  35. package/dist/boot/runner-deps.js +4 -2
  36. package/dist/boot/runtime-caps.d.ts +23 -0
  37. package/dist/boot/runtime-caps.js +24 -2
  38. package/dist/boot/session-faces.d.ts +1 -1
  39. package/dist/boot/session-faces.js +14 -3
  40. package/dist/boot/stores.js +31 -1
  41. package/dist/boot/task-list-lane.d.ts +2 -2
  42. package/dist/brain.js +51 -31
  43. package/dist/budget.d.ts +2 -2
  44. package/dist/capabilities/center-plugins.d.ts +1 -1
  45. package/dist/capabilities/collab-workflows.d.ts +1 -1
  46. package/dist/capabilities/hands-lane.d.ts +3 -3
  47. package/dist/capabilities/scenarios.d.ts +3 -3
  48. package/dist/capabilities/skills.d.ts +1 -1
  49. package/dist/config-center/hot-keys-registry.js +5 -0
  50. package/dist/config-center/http-client.d.ts +2 -2
  51. package/dist/config-center/read-face.d.ts +1 -1
  52. package/dist/config-center/restart-signal.d.ts +1 -1
  53. package/dist/config-types.d.ts +100 -25
  54. package/dist/config.d.ts +11 -3
  55. package/dist/config.js +91 -15
  56. package/dist/deployment-governance.d.ts +1 -1
  57. package/dist/device-enrollment.d.ts +2 -2
  58. package/dist/device-store.d.ts +3 -3
  59. package/dist/device-ws-hub.d.ts +4 -2
  60. package/dist/device-ws-hub.js +9 -3
  61. package/dist/device-ws-protocol.d.ts +2 -2
  62. package/dist/env-facts.d.ts +1 -1
  63. package/dist/execution-lane-caps.d.ts +1 -1
  64. package/dist/fleet/fleet-bus.d.ts +5 -2
  65. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  66. package/dist/fleet/fleet-terminal-window.d.ts +1 -1
  67. package/dist/hooks/hook-runner.d.ts +8 -1
  68. package/dist/http/active-run-conflict.d.ts +2 -2
  69. package/dist/http/principal-gate.d.ts +1 -1
  70. package/dist/http/route-ctx.d.ts +27 -5
  71. package/dist/http/routes/admin-memory-optout.d.ts +48 -0
  72. package/dist/http/routes/admin-memory-optout.js +75 -0
  73. package/dist/http/routes/agents-roster.d.ts +1 -1
  74. package/dist/http/routes/approvals-assistant.js +21 -21
  75. package/dist/http/routes/capabilities.js +20 -9
  76. package/dist/http/routes/diagnostics.d.ts +1 -1
  77. package/dist/http/routes/leader.d.ts +1 -1
  78. package/dist/http/routes/memory-bundle.d.ts +1 -1
  79. package/dist/http/routes/memory-bundle.js +2 -1
  80. package/dist/http/routes/memory-compliance.d.ts +4 -4
  81. package/dist/http/routes/memory-origin.d.ts +4 -4
  82. package/dist/http/routes/memory-origin.js +3 -1
  83. package/dist/http/routes/memory-policy.js +6 -2
  84. package/dist/http/routes/retention-ops.d.ts +1 -1
  85. package/dist/http/routes/rules.d.ts +5 -2
  86. package/dist/http/routes/rules.js +84 -5
  87. package/dist/http/routes/run-memory-capture.d.ts +34 -0
  88. package/dist/http/routes/run-memory-capture.js +97 -0
  89. package/dist/http/routes/runs.d.ts +1 -0
  90. package/dist/http/routes/runs.js +212 -6
  91. package/dist/http/routes/session-sync.d.ts +4 -6
  92. package/dist/http/routes/session-sync.js +60 -47
  93. package/dist/http/routes/sessions.js +61 -188
  94. package/dist/http/routes/shared-memory.d.ts +1 -1
  95. package/dist/http/routes/tasks.js +1 -1
  96. package/dist/http/routes/workflows.js +19 -0
  97. package/dist/http/server.d.ts +35 -13
  98. package/dist/http/server.js +110 -14
  99. package/dist/http/verify-rounds.d.ts +1 -1
  100. package/dist/http/wire-types.d.ts +41 -8
  101. package/dist/leader/endpoint.d.ts +1 -1
  102. package/dist/leader/leader.d.ts +1 -1
  103. package/dist/leader/repair-oracle.d.ts +2 -2
  104. package/dist/leader/repair-oracle.js +1 -1
  105. package/dist/main.js +14 -6
  106. package/dist/memory-bundle-engine.d.ts +1 -1
  107. package/dist/memory-operator-faces.d.ts +5 -5
  108. package/dist/memory-origin-fence.d.ts +35 -0
  109. package/dist/memory-origin-fence.js +66 -0
  110. package/dist/memory-posture.d.ts +13 -1
  111. package/dist/memory-posture.js +1 -0
  112. package/dist/memory-scope.d.ts +6 -1
  113. package/dist/memory-scope.js +3 -2
  114. package/dist/model-select.d.ts +2 -2
  115. package/dist/observability/fail-open.d.ts +17 -5
  116. package/dist/observability/fail-open.js +17 -5
  117. package/dist/orchestration/hardened-vm-runner.d.ts +2 -2
  118. package/dist/orchestration/workflow-completion-inbox.d.ts +2 -2
  119. package/dist/plugins/background-shell-support.d.ts +2 -2
  120. package/dist/plugins/blob-backend.d.ts +7 -2
  121. package/dist/plugins/blob-backend.js +22 -18
  122. package/dist/plugins/caching-session-store.d.ts +7 -3
  123. package/dist/plugins/checkpoint-store-sql.d.ts +2 -2
  124. package/dist/plugins/file-history-store-sql.d.ts +170 -0
  125. package/dist/plugins/file-history-store-sql.js +827 -0
  126. package/dist/plugins/file-run-store.d.ts +44 -5
  127. package/dist/plugins/file-run-store.js +85 -13
  128. package/dist/plugins/fork-routing-session-store.d.ts +1 -1
  129. package/dist/plugins/fork-routing-session-store.js +2 -2
  130. package/dist/plugins/leader-run-store-sql.d.ts +1 -1
  131. package/dist/plugins/local-session-store.d.ts +9 -1
  132. package/dist/plugins/memory-embedder-fingerprint.d.ts +1 -1
  133. package/dist/plugins/memory-optout-grant-store-sql.d.ts +139 -0
  134. package/dist/plugins/memory-optout-grant-store-sql.js +157 -0
  135. package/dist/plugins/memory-origin-law.d.ts +2 -2
  136. package/dist/plugins/memory-run-store.d.ts +4 -3
  137. package/dist/plugins/memory-run-store.js +2 -1
  138. package/dist/plugins/permission-rule-store-file.d.ts +16 -6
  139. package/dist/plugins/permission-rule-store-file.js +77 -13
  140. package/dist/plugins/permission-rule-store-sql.d.ts +156 -8
  141. package/dist/plugins/permission-rule-store-sql.js +123 -21
  142. package/dist/plugins/pg-pool.js +59 -0
  143. package/dist/plugins/pg-safe-json.d.ts +1 -1
  144. package/dist/plugins/remote-env-device.d.ts +1 -1
  145. package/dist/plugins/remote-env-k8s.d.ts +1 -1
  146. package/dist/plugins/retention-lane-store-sql.d.ts +3 -3
  147. package/dist/plugins/retention-store-sql.d.ts +7 -7
  148. package/dist/plugins/retention-store-sql.js +6 -0
  149. package/dist/plugins/run-store-sql.d.ts +9 -3
  150. package/dist/plugins/run-store-sql.js +2 -2
  151. package/dist/plugins/send-user-file.d.ts +1 -1
  152. package/dist/plugins/session-placement.d.ts +1 -1
  153. package/dist/plugins/sql-errors.d.ts +1 -1
  154. package/dist/plugins/store-backend.d.ts +59 -29
  155. package/dist/plugins/store-backend.js +75 -15
  156. package/dist/plugins/store-contracts.d.ts +5 -3
  157. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  158. package/dist/plugins/tidb-pool.js +59 -0
  159. package/dist/plugins/web-search.d.ts +1 -1
  160. package/dist/prompts-domain-validate.js +1 -1
  161. package/dist/resource-suspend.d.ts +1 -1
  162. package/dist/resource-window.d.ts +3 -3
  163. package/dist/rules-consent.d.ts +231 -9
  164. package/dist/rules-consent.js +411 -13
  165. package/dist/run-local.js +29 -4
  166. package/dist/runs.d.ts +15 -1
  167. package/dist/runs.js +19 -0
  168. package/dist/runtime-caps-resolver.d.ts +36 -1
  169. package/dist/runtime-caps-resolver.js +25 -0
  170. package/dist/runtime-governance.d.ts +1 -1
  171. package/dist/security.d.ts +5 -5
  172. package/dist/security.js +1 -1
  173. package/dist/session-sync.d.ts +52 -37
  174. package/dist/session-sync.js +19 -16
  175. package/dist/shared-memory-scope-authorizer.d.ts +1 -1
  176. package/dist/sighup-idle.d.ts +1 -1
  177. package/dist/spec-fields.d.ts +22 -1
  178. package/dist/spec-fields.js +11 -0
  179. package/dist/store-live-probe.d.ts +2 -2
  180. package/dist/task-a2a.d.ts +1 -1
  181. package/dist/task-cwd.d.ts +1 -1
  182. package/dist/task-mcp.d.ts +3 -3
  183. package/dist/task-settings.d.ts +3 -3
  184. package/dist/task-workflow.d.ts +1 -1
  185. package/dist/text-bidi.d.ts +2 -2
  186. package/dist/tool-approval.d.ts +191 -9
  187. package/dist/tool-approval.js +248 -29
  188. package/dist/trace/core-keyset-guard.d.ts +3 -3
  189. package/dist/trace/engine-notice-wire.d.ts +6 -4
  190. package/dist/trace/engine-notice-wire.js +2 -0
  191. package/dist/trace/ledger-events.d.ts +1 -1
  192. package/dist/trace/ledger-sink.d.ts +2 -2
  193. package/dist/trace/project.d.ts +8 -2
  194. package/dist/trace/project.js +10 -0
  195. package/dist/turn-activity.d.ts +1 -1
  196. package/dist/wall-clock-jump-guard.d.ts +3 -3
  197. package/package.json +3 -3
  198. package/dist/http/tar.d.ts +0 -17
  199. package/dist/http/tar.js +0 -53
  200. package/dist/http/workspace-content.d.ts +0 -11
  201. package/dist/http/workspace-content.js +0 -20
  202. package/dist/plugins/file-snapshot-store-sql.d.ts +0 -170
  203. package/dist/plugins/file-snapshot-store-sql.js +0 -213
@@ -6,7 +6,7 @@ import { setLeafAdvanceListener } from "../session-leaf-bus.js";
6
6
  import { SessionWatchRegistry, posIntEnv } from "../session-watch.js";
7
7
  import { defaultTaskRegistry } 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, fileSnapshotStore, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, } = ctx;
9
+ const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, } = 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,8 +66,19 @@ export function createSessionFaces(ctx) {
66
66
  if (deviceStore)
67
67
  await deviceStore.deleteByRootSession(sessionId);
68
68
  dropSessionScoped?.(sessionId);
69
- if (fileSnapshotStore?.deleteBySession)
70
- await fileSnapshotStore.deleteBySession(sessionId);
69
+ if (fileHistoryStore) {
70
+ if (typeof fileHistoryStore.deleteBySession === "function")
71
+ await fileHistoryStore.deleteBySession(sessionId);
72
+ else {
73
+ await fileHistoryStore.reap(sessionId, []);
74
+ logger.warn("session_file_history_baselines_retained", {
75
+ sessionId,
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",
77
+ });
78
+ }
79
+ }
80
+ if (backend?.legacySnapshotPurge)
81
+ await backend.legacySnapshotPurge(sessionId);
71
82
  if (workflowCompletionInbox)
72
83
  await workflowCompletionInbox.purge(sessionId, owner ?? null).catch((err) => logger.warn("workflow_completion_inbox_purge_failed", { sessionId, err: String(err) }));
73
84
  await purgeScratchpadDir(config.localDataRoot ?? localRoot, sessionId);
@@ -21,8 +21,9 @@ import { ensurePgDeviceSchema, ensureTiDBDeviceSchema } from "../plugins/device-
21
21
  import { assertCloudSnapshotBlobPosture, openStoreBackendWithFallback } from "../plugins/store-backend.js";
22
22
  import { buildMemoryRemoteLaneWarn, memoryEngineBackendFor, memoryEngineRemoteLanePosture } from "../memory-scope.js";
23
23
  import { assertToolResultProvenanceSchema } from "../plugins/tool-result-store-sql.js";
24
- import { assertPermissionRuleApprovalSchema } from "../plugins/permission-rule-store-sql.js";
24
+ import { assertPermissionRuleApprovalSchema, assertPermissionRuleTicketSchema } from "../plugins/permission-rule-store-sql.js";
25
25
  import { assertApprovalAskRuleMaterialSchema } from "../plugins/approval-ask-store-sql.js";
26
+ import { assertFileHistorySchema } from "../plugins/file-history-store-sql.js";
26
27
  import { resolveStreamApprovalGate } from "../tool-approval.js";
27
28
  import { buildMemoryWriteBoundaryAudit, effectiveMemoryPersistenceCapable } from "./memory-boundary.js";
28
29
  import { buildMemoryPosture } from "../memory-posture.js";
@@ -56,6 +57,10 @@ export async function openStores(ctx) {
56
57
  await assertPermissionRuleApprovalSchema(async (sql) => ({ rows: (await pgPool.query(sql)).rows }), "pg");
57
58
  else if (mysqlPool)
58
59
  await assertPermissionRuleApprovalSchema(async (sql) => ({ rows: (await mysqlPool.query(sql))[0] }), "tidb");
60
+ if (pgPool)
61
+ await assertPermissionRuleTicketSchema(async (sql) => ({ rows: (await pgPool.query(sql)).rows }), "pg");
62
+ else if (mysqlPool)
63
+ await assertPermissionRuleTicketSchema(async (sql) => ({ rows: (await mysqlPool.query(sql))[0] }), "tidb");
59
64
  }
60
65
  if (resolveStreamApprovalGate({
61
66
  toolApprovalEnabled: config.toolApprovalEnabled === true,
@@ -68,6 +73,31 @@ export async function openStores(ctx) {
68
73
  else if (mysqlPool)
69
74
  await assertApprovalAskRuleMaterialSchema(async (sql) => ({ rows: (await mysqlPool.query(sql))[0] }), "tidb");
70
75
  }
76
+ if (pgPool)
77
+ await assertFileHistorySchema(async (sql) => ({ rows: (await pgPool.query(sql)).rows }), "pg");
78
+ else if (mysqlPool)
79
+ await assertFileHistorySchema(async (sql) => ({ rows: (await mysqlPool.query(sql))[0] }), "tidb");
80
+ {
81
+ const legacyProbeSql = "SELECT 1 FROM snapshot_manifest LIMIT 1";
82
+ const hasLegacyRows = await (pgPool
83
+ ? pgPool.query(legacyProbeSql).then((r) => r.rows.length > 0)
84
+ : mysqlPool
85
+ ? mysqlPool.query(legacyProbeSql).then(([rows]) => rows.length > 0)
86
+ : Promise.resolve(false)).catch(() => false);
87
+ if (hasLegacyRows) {
88
+ logger.warn("legacy_snapshot_epoch_rows_present", {
89
+ note: "rows from the RETIRED whole-tree snapshot epoch (snapshot_manifest/snapshot_blob) exist. They are NOT " +
90
+ "migrated (whole-tree manifests and per-file history are structurally different); rewind refusals for " +
91
+ "legacy-era boundaries name the retired epoch via the boundary probe; the retention lane keeps sweeping " +
92
+ "these rows by session, and the maintenance tick GCs their orphaned blob bytes.",
93
+ });
94
+ }
95
+ }
96
+ }
97
+ if ((process.env.REWIND_SNAPSHOT_MAX_MB ?? "") !== "") {
98
+ logger.warn("rewind_snapshot_max_mb_retired", {
99
+ 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.",
100
+ });
71
101
  }
72
102
  {
73
103
  const minioReq = ["MINIO_ENDPOINT", "MINIO_ACCESS_KEY", "MINIO_SECRET_KEY"];
@@ -21,7 +21,7 @@
21
21
  * 高水位(next_id)与插入序(next_sort)都由 meta 行的事务计数器持有,`mutate` 是真后端事务。
22
22
  * - `memory`:无 SQL 后端(local/file 形、纯内存 dev)⇒ **进程级** per-session 内存店。跨 turn
23
23
  * 存活(这就是 F2 的修点),但**跨进程重启丢失**,且 LRU 溢出(> `maxSessions`)时最老的会话
24
- * 会被逐出=那条会话的待办清空。诚实登记在 USAGE(「local 形重启丢待办」),不伪造持久性:
24
+ * 会被逐出=那条会话的待办清空。诚实登记在 USAGE(「local 形重启丢待办」),不自造持久性:
25
25
  * 这与 local 形其余会话态设施(perSessionCwd / TtlSessionStore)是同一姿势。
26
26
  *
27
27
  * ## 为什么 SQL 形也进同一只缓存
@@ -50,7 +50,7 @@ export interface TaskListLane {
50
50
  * 🔴 为什么必须有:会话 id 由调用方自选、会话删除后可被**别人** `register` 重新登记(security.ts
51
51
  * 的 claim-create),而本车道的 listKey 是 `sessionId` 的确定性派生 ⇒ 不级联的话,新化身第一次
52
52
  * 打开清单就读到上一位主人的任务标题/描述,`TaskCreate` 还会从上一位的高水位续号(「这里以前
53
- * 建过 47 条」本身也是泄漏)。同族腿(anchors / approval-exemption / session-policy / attachment /
53
+ * 建过 47 条」本身也是外泄)。同族腿(anchors / approval-exemption / session-policy / attachment /
54
54
  * snapshot / scratchpad)全部走同一条级联,本条与它们同 scoping 理由:路由已在 DELETE 门证过会话
55
55
  * 所有权,故按 sessionId 单键删。
56
56
  *
package/dist/brain.js CHANGED
@@ -1,8 +1,6 @@
1
- import { createAnthropicBrain, createCircuitBreakerBrain, createDegradingBrain, createFailoverBrain, createOpenAIBrain, createRoutingBrain, } from "@sema-agent/core";
1
+ import { adjudicateModelRoute, createAnthropicBrain, createCircuitBreakerBrain, createDegradingBrain, createFailoverBrain, createOpenAIBrain, createRoutingBrain, hasAuthCarrier, resolveRouteCredential, routeRefusalText, } from "@sema-agent/core";
2
2
  import { normalizeBaseUrl } from "@sema-agent/core";
3
3
  import { resolveModelApiKey } from "./key-resolver.js";
4
- import { isSealedKeyPoison } from "./sealed-key.js";
5
- import { recordFailOpen } from "./observability/fail-open.js";
6
4
  function isOffDeploymentRoot(baseUrl, config) {
7
5
  if (!baseUrl)
8
6
  return false;
@@ -11,6 +9,10 @@ function isOffDeploymentRoot(baseUrl, config) {
11
9
  return false;
12
10
  return ![config.gatewayBaseUrl, ...config.gatewayFallbackUrls].some((u) => normalizeBaseUrl(u) === entry);
13
11
  }
12
+ function degradeDeclaredRoot(fallbackBaseUrl, config) {
13
+ const armed = Boolean(config.gatewayApiKey) && isOffDeploymentRoot(fallbackBaseUrl, config);
14
+ return armed ? config.gatewayBaseUrl : (fallbackBaseUrl || config.gatewayBaseUrl);
15
+ }
14
16
  export const FAST_FAIL_MAX_RETRIES = 2;
15
17
  const DEFAULT_RESILIENCE = {
16
18
  connectTimeoutMs: 30_000,
@@ -61,7 +63,7 @@ export function createBrain(config, deps = {}) {
61
63
  ...(config.anthropic.authToken
62
64
  ? { headers: { Authorization: `Bearer ${config.anthropic.authToken}` } }
63
65
  : { apiKey: config.anthropic.apiKey }),
64
- baseUrl: config.anthropic.baseUrl,
66
+ baseUrl: config.anthropic.baseUrl ?? "https://api.anthropic.com",
65
67
  version: config.anthropic.version,
66
68
  cacheBreakpoints: config.anthropic.cacheBreakpoints,
67
69
  ...(config.anthropic.maxRetries !== undefined ? { maxRetries: config.anthropic.maxRetries } : {}),
@@ -76,44 +78,42 @@ export function createBrain(config, deps = {}) {
76
78
  const degradeTo = config.degrade.to;
77
79
  const fallbackModel = config.models[degradeTo];
78
80
  if (fallbackModel) {
79
- const fbApiKey = () => {
80
- const own = resolveModelApiKey(fallbackModel.name, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {}) ??
81
- resolveModelApiKey(degradeTo, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {});
82
- return own ?? config.gatewayApiKey;
83
- };
84
- const hopBorrowsGatewayKey = () => {
85
- const keys = config.modelApiKeys ?? {};
86
- if (isSealedKeyPoison(keys[fallbackModel.name]) || isSealedKeyPoison(keys[degradeTo]))
87
- return false;
88
- const own = resolveModelApiKey(fallbackModel.name, config.modelApiKeyEnv ?? {}, process.env, keys) ??
89
- resolveModelApiKey(degradeTo, config.modelApiKeyEnv ?? {}, process.env, keys);
90
- return own === undefined && config.gatewayApiKey !== undefined && isOffDeploymentRoot(fallbackModel.baseUrl, config);
91
- };
81
+ const fbOwnApiKey = () => resolveModelApiKey(fallbackModel.name, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {}) ??
82
+ resolveModelApiKey(degradeTo, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {});
92
83
  const anthropicLaneHop = fallbackModel.provider === "anthropic" && anthropicBrain !== undefined;
93
84
  const fallback = fallbackModel.provider === "anthropic" && anthropicBrain
94
85
  ?
95
86
  anthropicBrain
96
87
  : createOpenAIBrain({
97
- baseUrl: fallbackModel.baseUrl || config.gatewayBaseUrl,
88
+ get baseUrl() {
89
+ return degradeDeclaredRoot(fallbackModel.baseUrl, config);
90
+ },
98
91
  get apiKey() {
99
- return fbApiKey();
92
+ return config.gatewayApiKey || undefined;
100
93
  },
101
94
  ...(config.gatewayMaxRetries !== undefined ? { maxRetries: config.gatewayMaxRetries } : {}),
102
95
  fetchImpl,
103
96
  ...timeouts,
104
97
  });
105
- const fallbackWithDebtProbe = anthropicLaneHop
106
- ? fallback
107
- : {
108
- stream: (m, c, o) => {
109
- if (hopBorrowsGatewayKey())
110
- recordFailOpen("server.model-key.degrade-hop-borrows-gateway-key", fallbackModel.name);
111
- return fallback.stream(m, c, o);
112
- },
113
- ...(fallback.adjudicateRoute !== undefined ? { adjudicateRoute: (m, a) => fallback.adjudicateRoute?.(m, a) } : {}),
114
- ...(fallback.complete !== undefined ? { complete: (m, c, o) => fallback.complete(m, c, o) } : {}),
115
- };
116
- brain = createDegradingBrain({ primary: brain, fallbacks: [{ brain: fallbackWithDebtProbe, model: fallbackModel }], downgradeOn: config.degrade.downgradeOn });
98
+ const degradeEntryModel = {
99
+ ...fallbackModel,
100
+ get headers() {
101
+ const own = fbOwnApiKey();
102
+ if (own === undefined)
103
+ return fallbackModel.headers;
104
+ const kept = {};
105
+ for (const [k, v] of Object.entries(fallbackModel.headers ?? {})) {
106
+ if (!hasAuthCarrier({ [k]: v }))
107
+ kept[k] = v;
108
+ }
109
+ return { ...kept, authorization: `Bearer ${own}` };
110
+ },
111
+ };
112
+ brain = createDegradingBrain({
113
+ primary: brain,
114
+ fallbacks: [{ brain: fallback, model: anthropicLaneHop ? fallbackModel : degradeEntryModel }],
115
+ downgradeOn: config.degrade.downgradeOn,
116
+ });
117
117
  }
118
118
  }
119
119
  return brain;
@@ -143,6 +143,26 @@ export function brainSummary(config) {
143
143
  reactiveDegradeBaseUrl: config.degrade?.reactive && config.models?.[config.degrade.to]
144
144
  ? config.models[config.degrade.to].baseUrl || config.gatewayBaseUrl
145
145
  : null,
146
+ ...((() => {
147
+ const target = config.degrade?.reactive ? config.models?.[config.degrade.to] : undefined;
148
+ if (!target)
149
+ return { reactiveDegradeRoutePairing: null, reactiveDegradeRouteRefusal: null };
150
+ if (target.provider === "anthropic" && config.anthropic) {
151
+ return { reactiveDegradeRoutePairing: "anthropic-lane", reactiveDegradeRouteRefusal: null };
152
+ }
153
+ const declaresOwnCredential = config.modelApiKeyEnv?.[target.name] !== undefined ||
154
+ config.modelApiKeyEnv?.[config.degrade.to] !== undefined ||
155
+ config.modelApiKeys?.[target.name] !== undefined ||
156
+ config.modelApiKeys?.[config.degrade.to] !== undefined ||
157
+ hasAuthCarrier(target.headers);
158
+ if (declaresOwnCredential) {
159
+ return { reactiveDegradeRoutePairing: "per-model-declared", reactiveDegradeRouteRefusal: null };
160
+ }
161
+ const verdict = adjudicateModelRoute({ id: target.id, baseUrl: target.baseUrl }, resolveRouteCredential({ ...(config.gatewayApiKey ? { configApiKey: config.gatewayApiKey } : {}) }), { baseUrl: degradeDeclaredRoot(target.baseUrl, config) });
162
+ return verdict.ok
163
+ ? { reactiveDegradeRoutePairing: `ok:${verdict.posture}`, reactiveDegradeRouteRefusal: null }
164
+ : { reactiveDegradeRoutePairing: `broken:${verdict.code}`, reactiveDegradeRouteRefusal: routeRefusalText(verdict) };
165
+ })()),
146
166
  };
147
167
  }
148
168
  //# sourceMappingURL=brain.js.map
package/dist/budget.d.ts CHANGED
@@ -27,7 +27,7 @@ export declare function buildPricing(models: Record<string, Model>): Record<stri
27
27
  export declare function isPricingConfigured(pricing: Record<string, ModelPricing>): boolean;
28
28
  /**
29
29
  * Bound the cardinality of a free-form metric label value (BL-11/BL-21). A label fed by untrusted/unbounded
30
- * input — MCP tool names (a misconfigured/malicious server can emit thousands of dynamic names) or center
30
+ * input — MCP tool names (a misconfigured/untrusted server can emit thousands of dynamic names) or center
31
31
  * model ids (a malformed roster) — would otherwise mint an unbounded number of metric series: a slow memory
32
32
  * leak in the registry + a /metrics scrape blow-up. We track the DISTINCT values seen; once the cap is hit,
33
33
  * further NEW values collapse into one `__other__` bucket (already-seen values still report exactly). The
@@ -66,7 +66,7 @@ export declare class ModelUsageTracker {
66
66
  private readonly active;
67
67
  /** Mark a TOP-LEVEL run active so its `brain.call` usage is tracked. **Required** — `record` ignores any taskId
68
68
  * that isn't registered. This is the leak fence: delegated SUB-TASKS (team members,
69
- * adversarial verifiers) run on throwaway core-minted sessionIds that the run lifecycle never registers/drains,
69
+ * independent verifiers) run on throwaway core-minted sessionIds that the run lifecycle never registers/drains,
70
70
  * so without this gate their usage would accumulate under ids nothing ever clears → an unbounded process-lifetime
71
71
  * leak. Sub-task spend is intentionally NOT in the per-run `modelUsage` echo (it lands in `TaskStats.nested`);
72
72
  * `modelUsage` is the TOP-LEVEL run's own per-model usage. Call at run start; {@link clear} unregisters. */
@@ -29,7 +29,7 @@ export type PluginValidation = {
29
29
  };
30
30
  /** 条款 1/2/4:https-only、host 全等白名单、sha 40hex。纯函数,worker 侧纵深复验(不信任 wire 已验过)。 */
31
31
  export declare function validatePluginDecl(decl: PluginDeclWire, allowHosts: readonly string[]): PluginValidation;
32
- /** 条款 3/5:clone 计划(argv 序列 + 隔离 env)。纯数据,让"姿势"可被钉死。 */
32
+ /** 条款 3/5:clone 计划(argv 序列 + 隔离 env)。纯数据,让"姿势"可被锚定。 */
33
33
  export declare function gitClonePlan(o: {
34
34
  url: string;
35
35
  dest: string;
@@ -37,7 +37,7 @@ export declare function registerCollabWorkflows(workflows: Record<string, Collab
37
37
  /** composite WorkflowScriptStore 的 resolveName 前段:collab 命中 ⇒ 内置 team-discussion 脚本+本模板
38
38
  * defaultArgs(同名 SHADOW core 内置,design/140 §6 1c)。🔴 Object.hasOwn — `name` 是模型供给的
39
39
  * (run-workflow-tool 原样传 rawName):裸索引会让 Object.prototype 成员名("constructor"/"toString"/
40
- * "__proto__"…)假命中 ⇒ 零模板注册也能烧真讨论、且同名文件 store 条目被遮蔽(对抗评审 2026-07-11 MED)。 */
40
+ * "__proto__"…)假命中 ⇒ 零模板注册也能烧真讨论、且同名文件 store 条目被遮蔽(交叉评审 2026-07-11 MED)。 */
41
41
  export declare function resolveCollabWorkflow(name: string): {
42
42
  script: string;
43
43
  defaultArgs: unknown;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * #196 场景 hands lane —— 「要手的场景」与「无手的场景」在装配层分家。
3
3
  *
4
- * 病根(设计小票 §1,案源 [3208]/[3209]/[3212]):core 的手带工具面是 **per-Runner 表态**
4
+ * 根本问题(设计小票 §1,案源 [3208]/[3209]/[3212]):core 的手带工具面是 **per-Runner 表态**
5
5
  * (`prepare-task`:`handsEnabled = ownedEnv || deps.executionEnv`,ownedEnv 只要挂了
6
6
  * `executionEnvFactory` 就每任务必铸)。server 此前全场景共享一只 runner + 一只 subRunner,
7
7
  * 于是任何 REMOTE_EXEC 部署里,声明 clone-free 的 `scan`、只有 `run_council` 的评审团、
@@ -37,7 +37,7 @@ export declare function pickHandsRunner(hands: ScenarioHands, pair: HandsRunnerP
37
37
  *
38
38
  * 两段来源,都不是拍脑袋:
39
39
  * · `HAND_TOOL_EFFECTS`(core 导出面,`tools/fs`)—— band 的 effect 表,11 名。
40
- * · 下方钉死补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
40
+ * · 下方锚定补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
41
41
  * test/scenario-hands-lane.test.ts 的对表格:它用真 Runner 跑「有手 roster ∖ 无手 roster」,与本常量逐名对账,
42
42
  * core 加/改 band 成员时红,防漂移)。
43
43
  *
@@ -50,7 +50,7 @@ export declare function pickHandsRunner(hands: ScenarioHands, pair: HandsRunnerP
50
50
  * `hands: none` 三场景(scan/code-review/team)在**所有部署**上零 turn 全灭。core 5.21.1 已删
51
51
  * 那条死条目([3249]),这道 filter 是结构性防御:将来 core 再退役任何 band 名都不会重演
52
52
  * (test/scenario-hands-lane.test.ts 格7/格8 双钉:真 Runner 过审计 + 零交集门)。
53
- * filter 打在**整表**(effect 表 + 钉死补集)之后 —— caret floor 下装置会解析到更新的 core,
53
+ * filter 打在**整表**(effect 表 + 锚定补集)之后 —— caret floor 下装置会解析到更新的 core,
54
54
  * 补集名哪天被退役,只滤前半就是同一次故障换个名字(codex 复审 finding,2026-08-09)。
55
55
  */
56
56
  export declare const HANDS_BAND_TOOL_NAMES: readonly string[];
@@ -65,7 +65,7 @@ export interface ScenarioDeps {
65
65
  skills: LoadedSkill[];
66
66
  repoClient?: RepoReadClient;
67
67
  /** Multi-tenant deployment (REQUIRE_PRINCIPAL). Gates the full-body `WebFetch` tool out of the
68
- * default roster — core says a multi-tenant deployment MUST allowlist hosts (an injected/malicious tenant model
68
+ * default roster — core says a multi-tenant deployment MUST allowlist hosts (an injected/untrusted tenant model
69
69
  * could exfil via an arbitrary public URL; the SSRF floor only blocks internal). Single-user TOC keeps WebFetch. */
70
70
  requirePrincipal?: boolean;
71
71
  /** Deployment-injected WebSearch backend (Brave/Tavily/SearXNG via `WEB_SEARCH_PROVIDER`). When set,
@@ -164,7 +164,7 @@ export declare function semaIdentityProvider(): PromptProvider;
164
164
  * `code` 场景的提示词提供者([849] 造,[891] clay 裁定改名 autonomous→code):role 基底 = core
165
165
  * {@link CODE_SYSTEM_PROMPT}(core 1.298 [891] 改名批正名,旧名 FULL_BODY_SYSTEM_PROMPT=恒等
166
166
  * 别名过渡窗;CODE_AGENT persona + AUTONOMY_SELF_AUDIT + ANTI_VERBOSITY +
167
- * TOOL_PARAM_JSON;[891] 血缘定论 = CC 生产编码提示词的蒸馏去牌版,底本随 CC 版本对表 cc-anchors,
167
+ * TOOL_PARAM_JSON;[891] 谱系定论 = CC 生产编码提示词的蒸馏去牌版,底本随 CC 版本对表 cc-anchors,
168
168
  * 非自研)——与 bench 的请求组装完全同源,引用 core 常量随版本走,绝不复制字符串(tb bench 实测:
169
169
  * 产品默认请求与同引擎 bench 态的差距根因就在这三件 persona/自检/终验)。品牌层与
170
170
  * {@link semaIdentityProvider} 同构:brandIdentity 开时 SEMA_IDENTITY 叠在 role 基底之上;任务自带
@@ -252,7 +252,7 @@ export declare function centerScenarios(deps: ScenarioDeps, specs: CenterScenari
252
252
  * by the HTTP layer (validateUserSkills); this stays pure merge policy.
253
253
  */
254
254
  export declare function mergeUserSkills(scenarioSkills: SkillSpec[] | undefined, userSkills: SkillSpec[] | undefined, logger?: Logger): SkillSpec[] | undefined;
255
- /** center caps view 的解析结果两键(形状钉死)。`scenario` null = 三层皆未配。 */
255
+ /** center caps view 的解析结果两键(形状锚定)。`scenario` null = 三层皆未配。 */
256
256
  export interface ScenarioRuling {
257
257
  scenario: string | null;
258
258
  /** 纯场景名集合(principal/组名不上 wire);空 = 该 principal 无任何指派 = 无治理。 */
@@ -35,7 +35,7 @@ export interface LoadedSkill {
35
35
  * ——那个便利是有意的(`foo.md -> 共享文件`)。`confineTo` 在场(**不可信 lane**:第三方 git checkout,
36
36
  * 见 center-plugins 的 plugin 装载)= 每个被读条目 realpath 后必须仍在该根之内,逃逸即抛。
37
37
  * 🔴 为什么信任级必须是**参数**而不是注释里的假设:本函数的头注原写着「skills dir 全受控」,而
38
- * center-plugins 把它原样复用在 attacker-controlled git checkout 上——git 原样保留仓里的 symlink,
38
+ * center-plugins 把它原样复用在 不可信来源的 git checkout 上——git 原样保留仓里的 symlink,
39
39
  * 于是 checkout 里某个 skill 条目(`任意 .md -> /宿主/任意文件`)会被读进 spec.files 进入提示词。resolvePluginSkillsRoot
40
40
  * 只 realpath 守了 skills **根**,根下逐条目没守。 */
41
41
  export interface LoadSkillsOptions {
@@ -17,6 +17,11 @@ export const BATCH1_LIMIT_KEYS = [
17
17
  ];
18
18
  const B_SETTER_KEYS = ["rateLimitPerMin", "maxPrincipalCostUsd"];
19
19
  export const DEFERRED_LIMIT_KEYS = [
20
+ {
21
+ key: "workspaceFileMaxBytes",
22
+ restartReason: "workspace-face-retired",
23
+ why: "the workspace browse face was retired with the whole-tree snapshot epoch (S-15) — this knob has no consumer left; the value is still validated and recorded for center-push compatibility, but applies to nothing (restart does not change that). Removal follows the settings-schema deprecation lane",
24
+ },
20
25
  {
21
26
  key: "costQuotaWindowSec",
22
27
  restartReason: "cost-quota-window",
@@ -68,7 +68,7 @@ export declare class ConfigCenterHttpError extends Error {
68
68
  }
69
69
  /**
70
70
  * Fetch one skill's body by content hash (`GET /api/config/skills/content/<hash>`, Bearer) and VERIFY its
71
- * sha256 matches the manifest hash (integrity + tamper check). The content is immutable per hash, so the
71
+ * sha256 matches the manifest hash (integrity + rewrite check). The content is immutable per hash, so the
72
72
  * caller caches by hash and never re-fetches an unchanged skill. Throws a {@link ConfigCenterHttpError}
73
73
  * (carrying `.status`) on an HTTP error — INCLUDING 404 — or a plain Error on hash mismatch. Callers that
74
74
  * want "unknown hash → undefined" parity with the local backend inspect `.status === 404` and swallow it;
@@ -77,7 +77,7 @@ export declare class ConfigCenterHttpError extends Error {
77
77
  export declare function fetchSkillContent(baseUrl: string, token: string, contentHash: string, fetchImpl?: typeof fetch): Promise<string>;
78
78
  /** [985]b / [1285]① — §9.4 by-digest 补拉二函(fetchSkillContent 同姿势:HTTP+Bearer+8s 超时+
79
79
  * 消费侧自证)。artifact=envelope JSON(验真=调用方过 core verifyPromptArtifact,canonical 重算——
80
- * 本函数只做传输与形读,不越权解释);blob=段文本 raw-byte(sha256 逐位自证,fetchSkillContent 同款)。
80
+ * 本函数只做传输与形读,不超范围解释);blob=段文本 raw-byte(sha256 逐位自证,fetchSkillContent 同款)。
81
81
  * digest 参数收带前缀/裸 hex 两形([1285] 参数口径)。 */
82
82
  export declare function fetchPromptArtifact(baseUrl: string, token: string, artifactDigest: string, fetchImpl?: typeof fetch): Promise<unknown>;
83
83
  export declare function fetchPromptBlob(baseUrl: string, token: string, contentDigest: string, fetchImpl?: typeof fetch): Promise<string>;
@@ -8,7 +8,7 @@ import type { CenterReadFace, EffectiveConfig } from "./types.js";
8
8
  * 返回值 = **本机 env 显式占住的 center 键名**(与本次发布内容无关的部署事实)。调用方把它喂给
9
9
  * `RestartSliceCtx.readFaceEnvHeld`,`read-face` restart 片于是只指纹「真会 boot-bake 的那部分」——
10
10
  * 否则 center 改一个本机永远用不上的键,整片 fleet 会为一次对自己毫无影响的发布滚动重启一遍
11
- * (codex 对抗复审 finding 2,验真后采纳的那一半)。
11
+ * (codex 交叉复审 finding 2,验真后采纳的那一半)。
12
12
  */
13
13
  export declare function applyCenterReadFace(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger): readonly (keyof CenterReadFace)[];
14
14
  //# sourceMappingURL=read-face.d.ts.map
@@ -7,7 +7,7 @@ export interface RestartSliceCtx {
7
7
  /** `config.degrade.to` when REACTIVE degrade is on (env `MODEL_DEGRADE_TO` + `MODEL_DEGRADE_REACTIVE`);
8
8
  * undefined = the lane is off ⇒ no degrading brain was composed ⇒ nothing is boot-frozen. */
9
9
  reactiveDegradeTo?: string;
10
- /** #A4(codex 对抗复审 finding 2,验真后采纳):本机 env **显式占住**的 readFace 键
10
+ /** #A4(codex 交叉复审 finding 2,验真后采纳):本机 env **显式占住**的 readFace 键
11
11
  * (`applyCenterReadFace` 的返回值,由 boot 存下)。这些键的 center 值在本机**永不 boot-bake**,
12
12
  * 所以它们的变化不该产生 restart 理由 —— 否则 center 改一个本机用不上的键,整片 fleet 会为一次
13
13
  * 对自己毫无影响的发布滚动重启一遍。缺席 = 没有 env 覆盖 ⇒ 整域都算数(与旧行为一致)。 */