@sema-agent/server 7.125.1 → 7.127.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 (275) hide show
  1. package/MIGRATION.md +229 -2
  2. package/README.md +2 -2
  3. package/README.zh-CN.md +2 -2
  4. package/USAGE.md +346 -145
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/chart/templates/networkpolicy-sandbox.yaml +57 -0
  7. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +32 -1
  8. package/deploy/sema-up/chart/templates/server-v4pro.yaml +3 -2
  9. package/deploy/sema-up/chart/templates/server.yaml +2 -1
  10. package/deploy/sema-up/chart/values.yaml +22 -0
  11. package/deploy/sema-up/cluster-up.sh +10 -10
  12. package/deploy/sema-up/kube-up.sh +20 -13
  13. package/deploy/sema-up/lib/dataroot.sh +1 -1
  14. package/deploy/sema-up/lib/envf.sh +5 -5
  15. package/deploy/sema-up/lib/flagshape.sh +5 -5
  16. package/deploy/sema-up/lib/lanes.py +1 -1
  17. package/deploy/sema-up/lib/node-addrs.sh +42 -0
  18. package/deploy/sema-up/lib/parse-machines.py +2 -2
  19. package/deploy/sema-up/lib/shell-access.sh +1 -1
  20. package/deploy/sema-up/preflight.sh +4 -4
  21. package/deploy/sema-up/sema-up.sh +33 -33
  22. package/deploy/sema-up/smoke.sh +3 -3
  23. package/deploy/sema-up/stack/compose.yaml +1 -1
  24. package/deploy/sema-up/stack/sema-stack-cluster.yaml +26 -3
  25. package/dist/approval-card.d.ts +20 -4
  26. package/dist/approval-content-kind.d.ts +38 -22
  27. package/dist/approval-content-kind.js +20 -4
  28. package/dist/audit.js +3 -0
  29. package/dist/bench/s1/arms.d.ts +9 -37
  30. package/dist/bench/s1/arms.js +4 -61
  31. package/dist/bench/s1/live-deps.d.ts +4 -10
  32. package/dist/bench/s1/live-deps.js +1 -222
  33. package/dist/bench/s1/oracle.d.ts +1 -1
  34. package/dist/bench/s1/reviewer.d.ts +1 -1
  35. package/dist/bench/s1/row.d.ts +5 -7
  36. package/dist/bench/s1/row.js +0 -2
  37. package/dist/bench/s1/run-firm.d.ts +2 -2
  38. package/dist/bench/s1/run-firm.js +1 -4
  39. package/dist/bench/s1/runner-ctx.d.ts +17 -29
  40. package/dist/bench/s1/runner-ctx.js +3 -7
  41. package/dist/bench/s1/tasks.d.ts +9 -46
  42. package/dist/bench/s1/tasks.js +4 -63
  43. package/dist/boot/execution-env.d.ts +38 -26
  44. package/dist/boot/execution-env.js +87 -41
  45. package/dist/boot/leader.d.ts +2 -1
  46. package/dist/boot/memory-boundary.d.ts +1 -1
  47. package/dist/boot/memory-consolidation.d.ts +37 -3
  48. package/dist/boot/memory-consolidation.js +18 -2
  49. package/dist/boot/parked-revive-gate.d.ts +0 -3
  50. package/dist/boot/parked-revive-gate.js +2 -2
  51. package/dist/boot/resolve-spec.d.ts +0 -2
  52. package/dist/boot/resolve-spec.js +12 -17
  53. package/dist/boot/retention-lane.d.ts +12 -1
  54. package/dist/boot/retention-lane.js +2 -0
  55. package/dist/boot/runner-deps.d.ts +7 -6
  56. package/dist/boot/runner-deps.js +5 -16
  57. package/dist/boot/session-faces.d.ts +7 -2
  58. package/dist/boot/session-faces.js +37 -1
  59. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  60. package/dist/boot/stage-03-store-backend.js +2 -2
  61. package/dist/boot/stage-04-stores.d.ts +1 -0
  62. package/dist/boot/stage-05-execution-env.d.ts +2 -2
  63. package/dist/boot/stage-05-execution-env.js +7 -4
  64. package/dist/boot/stage-06-runners.d.ts +2 -3
  65. package/dist/boot/stage-06-runners.js +13 -9
  66. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  67. package/dist/boot/stage-07-capability-layer.js +3 -4
  68. package/dist/boot/stage-08-reapers.d.ts +2 -3
  69. package/dist/boot/stage-08-reapers.js +2 -2
  70. package/dist/boot/stage-09-leader.d.ts +2 -3
  71. package/dist/boot/stage-09-leader.js +2 -1
  72. package/dist/boot/stage-10-http-server.d.ts +2 -3
  73. package/dist/boot/stage-10-http-server.js +2 -2
  74. package/dist/boot/stores.d.ts +1 -0
  75. package/dist/boot/stores.js +4 -3
  76. package/dist/boot/web-admin.d.ts +5 -0
  77. package/dist/boot/web-admin.js +28 -14
  78. package/dist/brain.d.ts +2 -2
  79. package/dist/brain.js +1 -1
  80. package/dist/capabilities/builtin-tools.d.ts +2 -1
  81. package/dist/capabilities/composite-members.d.ts +1 -1
  82. package/dist/capabilities/composite-members.js +1 -1
  83. package/dist/capabilities/hands-lane.d.ts +9 -6
  84. package/dist/capabilities/hands-lane.js +2 -2
  85. package/dist/capabilities/memory-notice.d.ts +27 -22
  86. package/dist/capabilities/memory-notice.js +15 -17
  87. package/dist/capabilities/scenarios.d.ts +9 -14
  88. package/dist/capabilities/scenarios.js +5 -50
  89. package/dist/capability-facts.d.ts +22 -11
  90. package/dist/capability-facts.js +13 -6
  91. package/dist/config-catalog.js +14 -5
  92. package/dist/config-provider.d.ts +5 -1
  93. package/dist/config-provider.js +23 -4
  94. package/dist/config-types.d.ts +19 -2
  95. package/dist/config.js +41 -6
  96. package/dist/deployment-governance.d.ts +19 -21
  97. package/dist/deployment-governance.js +7 -8
  98. package/dist/env-name-allowlist-knobs.d.ts +5 -5
  99. package/dist/env-name-allowlist-knobs.js +1 -0
  100. package/dist/execution-lane-caps.d.ts +12 -0
  101. package/dist/execution-lane-caps.js +18 -4
  102. package/dist/fleet/subagent-tail-bus.js +15 -5
  103. package/dist/hooks/hook-runner.d.ts +84 -30
  104. package/dist/hooks/hook-runner.js +74 -41
  105. package/dist/http/admission.js +17 -10
  106. package/dist/http/durable-run-leg.d.ts +5 -4
  107. package/dist/http/durable-run-leg.js +2 -4
  108. package/dist/http/idempotency.d.ts +28 -6
  109. package/dist/http/idempotency.js +10 -2
  110. package/dist/http/principal-gate.d.ts +14 -0
  111. package/dist/http/principal-gate.js +7 -0
  112. package/dist/http/request-guards.d.ts +2 -1
  113. package/dist/http/request-guards.js +14 -14
  114. package/dist/http/resume-legs.d.ts +2 -2
  115. package/dist/http/resume-legs.js +20 -10
  116. package/dist/http/route-ctx.d.ts +15 -7
  117. package/dist/http/routes/a2a-serve.js +11 -7
  118. package/dist/http/routes/approvals-assistant.js +18 -3
  119. package/dist/http/routes/background.d.ts +1 -1
  120. package/dist/http/routes/background.js +1 -1
  121. package/dist/http/routes/capabilities.js +10 -2
  122. package/dist/http/routes/mcp-reconnect.js +1 -1
  123. package/dist/http/routes/runs.d.ts +4 -3
  124. package/dist/http/routes/runs.js +35 -14
  125. package/dist/http/routes/session-sync.js +14 -11
  126. package/dist/http/routes/sessions.js +9 -9
  127. package/dist/http/routes/side-query.js +6 -19
  128. package/dist/http/routes/tasks.js +29 -19
  129. package/dist/http/routes/workflows.js +8 -0
  130. package/dist/http/server.d.ts +4 -0
  131. package/dist/http/server.js +3 -5
  132. package/dist/http/submit-replay.d.ts +15 -8
  133. package/dist/http/submit-replay.js +6 -0
  134. package/dist/http/wire-types.d.ts +7 -9
  135. package/dist/index.d.ts +1 -1
  136. package/dist/index.js +1 -1
  137. package/dist/internal-run-env.d.ts +30 -0
  138. package/dist/internal-run-env.js +11 -0
  139. package/dist/leader/wire.js +3 -2
  140. package/dist/lsp/manager.js +1 -1
  141. package/dist/memory-scope.d.ts +19 -7
  142. package/dist/memory-scope.js +25 -8
  143. package/dist/observability/fail-open.d.ts +31 -3
  144. package/dist/observability/fail-open.js +31 -3
  145. package/dist/observability/metrics.d.ts +0 -1
  146. package/dist/observability/metrics.js +1 -4
  147. package/dist/observability/run-terminal.js +72 -4
  148. package/dist/permission-rule-vocab.d.ts +3 -2
  149. package/dist/permission-rule-vocab.js +1 -1
  150. package/dist/plan-cache-probe.d.ts +3 -2
  151. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  152. package/dist/plugins/caching-session-store.d.ts +42 -22
  153. package/dist/plugins/caching-session-store.js +19 -9
  154. package/dist/plugins/checkpoint-store-sql.d.ts +42 -28
  155. package/dist/plugins/checkpoint-store-sql.js +57 -11
  156. package/dist/plugins/file-run-store.d.ts +25 -21
  157. package/dist/plugins/file-run-store.js +70 -34
  158. package/dist/plugins/k8s-exec-protocol.js +3 -0
  159. package/dist/plugins/local-checkpoint-store.d.ts +8 -5
  160. package/dist/plugins/local-checkpoint-store.js +5 -15
  161. package/dist/plugins/local-session-store.d.ts +66 -7
  162. package/dist/plugins/local-session-store.js +168 -101
  163. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +1 -1
  164. package/dist/plugins/memory-run-store.d.ts +19 -3
  165. package/dist/plugins/memory-run-store.js +38 -3
  166. package/dist/plugins/one-time-migrations.d.ts +165 -39
  167. package/dist/plugins/one-time-migrations.js +51 -144
  168. package/dist/plugins/pg-pool.js +11 -1
  169. package/dist/plugins/pg-session-storage.d.ts +2 -0
  170. package/dist/plugins/pg-session-storage.js +9 -5
  171. package/dist/plugins/remote-env-adb.d.ts +3 -0
  172. package/dist/plugins/remote-env-adb.js +1 -0
  173. package/dist/plugins/remote-env-device.d.ts +3 -0
  174. package/dist/plugins/remote-env-device.js +1 -0
  175. package/dist/plugins/remote-env-e2b.d.ts +4 -0
  176. package/dist/plugins/remote-env-e2b.js +2 -1
  177. package/dist/plugins/remote-env-host.d.ts +23 -1
  178. package/dist/plugins/remote-env-host.js +20 -2
  179. package/dist/plugins/remote-env-k8s.d.ts +264 -41
  180. package/dist/plugins/remote-env-k8s.js +551 -82
  181. package/dist/plugins/remote-env-local-docker.d.ts +4 -0
  182. package/dist/plugins/remote-env-local-docker.js +2 -1
  183. package/dist/plugins/remote-env-ssh.d.ts +3 -0
  184. package/dist/plugins/remote-env-ssh.js +1 -0
  185. package/dist/plugins/retention-store-sql.d.ts +4 -1
  186. package/dist/plugins/retention-store-sql.js +11 -9
  187. package/dist/plugins/run-store-sql.d.ts +44 -6
  188. package/dist/plugins/run-store-sql.js +92 -27
  189. package/dist/plugins/session-capture-record-store-sql.d.ts +2 -3
  190. package/dist/plugins/session-store.d.ts +4 -3
  191. package/dist/plugins/session-store.js +1 -1
  192. package/dist/plugins/sql-driver.d.ts +2 -1
  193. package/dist/plugins/store-backend.d.ts +12 -5
  194. package/dist/plugins/store-backend.js +13 -4
  195. package/dist/plugins/store-contracts.d.ts +20 -3
  196. package/dist/plugins/store-open-wait.js +3 -0
  197. package/dist/plugins/tidb-pool.js +15 -3
  198. package/dist/plugins/tidb-session-storage.js +3 -2
  199. package/dist/plugins/tidb-session-store.d.ts +2 -0
  200. package/dist/plugins/tidb-session-store.js +6 -2
  201. package/dist/request-key-closure.d.ts +32 -7
  202. package/dist/request-key-closure.js +25 -9
  203. package/dist/router/route-orchestration.d.ts +3 -11
  204. package/dist/router/route-orchestration.js +0 -4
  205. package/dist/rule-entry-list.js +0 -4
  206. package/dist/rules-consent.js +3 -0
  207. package/dist/run-acceptance.d.ts +51 -0
  208. package/dist/run-acceptance.js +27 -0
  209. package/dist/run-local.d.ts +5 -4
  210. package/dist/run-local.js +23 -17
  211. package/dist/runs.d.ts +7 -11
  212. package/dist/runs.js +6 -16
  213. package/dist/sandbox-egress-policy.d.ts +61 -0
  214. package/dist/sandbox-egress-policy.js +235 -0
  215. package/dist/security.d.ts +7 -0
  216. package/dist/security.js +5 -0
  217. package/dist/server-secret-env.d.ts +10 -4
  218. package/dist/server-secret-env.js +4 -1
  219. package/dist/session-error-wire.d.ts +2 -2
  220. package/dist/session-incarnation.d.ts +46 -2
  221. package/dist/session-incarnation.js +14 -3
  222. package/dist/session-sync-kernel.d.ts +10 -3
  223. package/dist/session-titler.d.ts +5 -3
  224. package/dist/spec-fields.d.ts +24 -8
  225. package/dist/spec-fields.js +21 -2
  226. package/dist/task-workflow.d.ts +6 -4
  227. package/dist/task-workflow.js +3 -2
  228. package/dist/tool-approval.d.ts +78 -6
  229. package/dist/tool-approval.js +7 -3
  230. package/dist/trace/core-keyset-guard.d.ts +6 -5
  231. package/dist/trace/engine-notice-wire.d.ts +26 -1
  232. package/dist/trace/engine-notice-wire.js +7 -2
  233. package/dist/trace/ledger-events.d.ts +9 -3
  234. package/dist/trace/ledger-sink.d.ts +7 -0
  235. package/dist/trace/ledger-sink.js +14 -4
  236. package/dist/trace/project.d.ts +57 -11
  237. package/dist/trace/project.js +54 -8
  238. package/dist/trace/self-minted-wire.d.ts +94 -2
  239. package/dist/trace/self-minted-wire.js +28 -0
  240. package/dist/trace/tool-output-media.d.ts +26 -0
  241. package/dist/trace/tool-output-media.js +57 -0
  242. package/dist/trace/wire-projection-faces.d.ts +7 -1
  243. package/dist/trace/wire-projection-faces.js +2 -1
  244. package/dist/web-admin-client/apply-effective.d.ts +13 -7
  245. package/dist/web-admin-client/apply-effective.js +11 -26
  246. package/dist/web-admin-client/apply-ledger.d.ts +2 -2
  247. package/dist/web-admin-client/candidate-gate.d.ts +39 -4
  248. package/dist/web-admin-client/candidate-gate.js +9 -2
  249. package/dist/web-admin-client/facade.d.ts +2 -2
  250. package/dist/web-admin-client/http-client.d.ts +3 -1
  251. package/dist/web-admin-client/http-client.js +4 -3
  252. package/dist/web-admin-client/read-warnings.d.ts +33 -5
  253. package/dist/web-admin-client/read-warnings.js +41 -6
  254. package/dist/web-admin-client/types.d.ts +11 -5
  255. package/dist/workspace-cache.d.ts +45 -0
  256. package/dist/workspace-cache.js +30 -0
  257. package/dist/workspace-space-commit.d.ts +116 -0
  258. package/dist/workspace-space-commit.js +117 -0
  259. package/dist/workspace-space.d.ts +75 -9
  260. package/dist/workspace-space.js +52 -1
  261. package/package.json +4 -4
  262. package/dist/boot/session-incarnation-seat.d.ts +0 -41
  263. package/dist/boot/session-incarnation-seat.js +0 -69
  264. package/dist/capabilities/code-review-council.d.ts +0 -49
  265. package/dist/capabilities/code-review-council.js +0 -111
  266. package/dist/capabilities/collab-wire.d.ts +0 -36
  267. package/dist/capabilities/collab-wire.js +0 -2
  268. package/dist/capabilities/collab-workflows.d.ts +0 -47
  269. package/dist/capabilities/collab-workflows.js +0 -90
  270. package/dist/capabilities/prompts/code-review.d.ts +0 -25
  271. package/dist/capabilities/prompts/code-review.js +0 -39
  272. package/dist/capabilities/prompts/team.d.ts +0 -10
  273. package/dist/capabilities/prompts/team.js +0 -6
  274. package/dist/capabilities/team.d.ts +0 -113
  275. package/dist/capabilities/team.js +0 -70
@@ -2,12 +2,9 @@ import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, WEB_FETCH
2
2
  import { HttpError, createCodedHttpError } from "../security.js";
3
3
  import { createSubagentExtraTools, extraToolsErrorFields, nowTool } from "./builtin-tools.js";
4
4
  import { parseRepo, repoToolsFor } from "./repo-tools.js";
5
- import { createCouncilTool, clampRounds } from "./code-review-council.js";
6
5
  import { stablePrompt } from "./prompt.js";
7
- import { createDiscussionTools, discussionCoordinatorPrompt, getTeam, teamNames } from "./team.js";
8
6
  import { createWebSearchBackend, judgeRequestWebSearch } from "../plugins/web-search.js";
9
7
  import { skillsForScenario } from "./skills.js";
10
- import { pickHandsRunner } from "./hands-lane.js";
11
8
  export { SEMA_IDENTITY } from "./prompts/identity.js";
12
9
  import { SEMA_IDENTITY } from "./prompts/identity.js";
13
10
  export function semaIdentityProvider() {
@@ -28,9 +25,6 @@ export function codePromptProvider(brand) {
28
25
  ],
29
26
  };
30
27
  }
31
- function subRunnerFor(deps, hands) {
32
- return pickHandsRunner(hands, { full: deps.subRunner, handsless: deps.handslessSubRunner });
33
- }
34
28
  function requestWebSearchBackend(verdict, deployment, logger) {
35
29
  switch (verdict.kind) {
36
30
  case "absent":
@@ -61,7 +55,7 @@ export function buildScenarios(deps) {
61
55
  ? { taskListStore: deps.taskListStoreFor(ctx.sessionId) }
62
56
  : {}),
63
57
  subagent: {
64
- runner: subRunnerFor(deps, "full"),
58
+ runner: deps.subRunner,
65
59
  background: {
66
60
  registry: defaultTaskRegistry,
67
61
  owner: "unattributed",
@@ -101,22 +95,6 @@ export function buildScenarios(deps) {
101
95
  skills: skillsForScenario(deps.skills, "scan"),
102
96
  };
103
97
  },
104
- discuss: (req) => {
105
- const name = typeof req.team === "string" ? req.team : "default";
106
- const template = getTeam(name);
107
- if (!template) {
108
- throw new HttpError(404, `unknown discussion roster "${name}" (available: ${teamNames().join(", ")})`);
109
- }
110
- const objective = typeof req.objective === "string" ? req.objective : "Discuss the task.";
111
- const businessContext = typeof req.businessContext === "string" ? req.businessContext : undefined;
112
- const hands = "none";
113
- return {
114
- tools: createDiscussionTools(subRunnerFor(deps, hands), template, { objective, businessContext, ...(deps.logger ? { logger: deps.logger } : {}) }),
115
- hands,
116
- skills: skillsForScenario(deps.skills, "discuss"),
117
- promptProvider: discussionCoordinatorPrompt,
118
- };
119
- },
120
98
  };
121
99
  }
122
100
  export function selectScenario(scenarios, name) {
@@ -131,6 +109,7 @@ function availabilityFields(deps, toolset) {
131
109
  const verdict = scenarioAvailability(deps, toolset);
132
110
  return verdict.available ? { available: true } : { available: false, unavailableReason: verdict.reason };
133
111
  }
112
+ export const BUILTIN_SCENARIO_NAMES = ["code", "default", "code-review", "scan"];
134
113
  export function builtinScenarioDetails(scenarios, deps) {
135
114
  const probe = (name, req, principal, fallback) => {
136
115
  try {
@@ -145,15 +124,15 @@ export function builtinScenarioDetails(scenarios, deps) {
145
124
  name, source: "builtin", builtin: true, summary, toolset, tools, promptSummary, enabled: true,
146
125
  ...availabilityFields(deps, toolset),
147
126
  });
148
- return {
127
+ const details = {
149
128
  code: mk("code", "编码主场景([891] 出厂缺省;CC 编码 persona 蒸馏版,default 全量工具面;终验旋钮独立)", "full-body", probe("code", {}, undefined, ["Now"]), deps.brandIdentity
150
129
  ? "Sema 产品身份 + core 编码完全体提示词(CODE_AGENT persona+自治自检;CC 血统对表)"
151
130
  : "core 编码完全体提示词(CODE_AGENT persona+自治自检;CC 血统对表)"),
152
131
  default: mk("default", "通用/助理场景(域中性 persona,CC agent loop 全量工具面;[891] 起非出厂缺省,显式可选)", "full-body", probe("default", {}, undefined, ["Now"]), deps.brandIdentity ? "Sema 产品身份 + core 默认宪法(结构层由引擎合成)" : "core 默认宪法(中性,无场景附加 persona)"),
153
- "code-review": mk("code-review", "代码评审(clone-free 只读仓库工具;council=true 升多镜头评审团)", "repo-readonly", probe("code-review", { repo: "probe/probe" }, undefined, REPO_TOOLS), "评审员/评审团协调提示词(镜头分工+产出契约)"),
132
+ "code-review": mk("code-review", "代码评审(clone-free 只读仓库工具)", "repo-readonly", probe("code-review", { repo: "probe/probe" }, undefined, REPO_TOOLS), "评审员提示词(产出契约)"),
154
133
  scan: mk("scan", "中性只读仓库扫描(输出契约由 objective+skills 决定)", "repo-readonly", probe("scan", { repo: "probe/probe" }, undefined, REPO_TOOLS), "无场景附加提示词(objective+center skills 拥有输出契约)"),
155
- discuss: mk("discuss", "多智能体讨论圆桌(模板成员+synthesizer,模板可由 registry 配置)", "discuss", probe("discuss", {}, undefined, ["run_discussion"]), "讨论协调者提示词(圆桌纪律+合成流程)"),
156
134
  };
135
+ return details;
157
136
  }
158
137
  export function centerScenarioDetails(specs, builtinNames, deps) {
159
138
  const out = {};
@@ -249,28 +228,7 @@ function codeReview(deps, req) {
249
228
  const coords = parseRepo(req.repo);
250
229
  const repoTools = repoToolsFor(repoClient, coords);
251
230
  const skills = skillsForScenario(deps.skills, "code-review");
252
- const objective = typeof req.objective === "string" ? req.objective : "Review this repository.";
253
231
  const hands = "none";
254
- if (req.lenses !== undefined && typeof req.lenses !== "number") {
255
- throw new HttpError(400, "lenses must be a number (council lens COUNT, 1-6) — a named lens subset is not a request-level knob", { code: "request.field_invalid" });
256
- }
257
- if (req.council === true) {
258
- const rounds = clampRounds(req.rounds);
259
- return {
260
- tools: [
261
- createCouncilTool(subRunnerFor(deps, hands), repoTools, objective, {
262
- metrics: deps.metrics,
263
- logger: deps.logger,
264
- debate: req.debate === true,
265
- ...(typeof req.lenses === "number" ? { lensCount: req.lenses } : {}),
266
- ...(rounds !== undefined ? { rounds } : {}),
267
- }),
268
- ],
269
- hands,
270
- skills,
271
- promptProvider: coordinatorPrompt,
272
- };
273
- }
274
232
  return { tools: [...repoTools, nowTool()], hands, skills, promptProvider: directReviewerPrompt };
275
233
  }
276
234
  const reviewBody = "Produce ONE prioritized review: a short summary, then findings grouped by severity " +
@@ -278,9 +236,6 @@ const reviewBody = "Produce ONE prioritized review: a short summary, then findin
278
236
  const directReviewerPrompt = stablePrompt("You are a senior code reviewer. Call repo_tree to map the repository, then repo_read_file to read " +
279
237
  "the files in scope, then review them yourself. " +
280
238
  reviewBody);
281
- const coordinatorPrompt = stablePrompt("You coordinate a code-review council. Call `run_council` exactly once (no arguments). When it " +
282
- "returns the arbitrated review, output that review as your final answer VERBATIM — do not " +
283
- "summarize, shorten, or rewrite it.");
284
239
  export function mergeUserSkills(scenarioSkills, userSkills, logger) {
285
240
  if (!userSkills || userSkills.length === 0)
286
241
  return scenarioSkills;
@@ -24,6 +24,7 @@ import type { OwnerAwareSessionStore } from "./security.js";
24
24
  import type { ServiceFileHistoryStore } from "./plugins/store-backend.js";
25
25
  import { resolveStreamApprovalGate } from "./tool-approval.js";
26
26
  import { type WorkspaceSyncCapability } from "./workspace-space.js";
27
+ import type { WorkspaceCacheCapability } from "./workspace-cache.js";
27
28
  /** why 闭集:词 → 给运维看的一句义(诊断 / 测试读它;wire 上的拒句由各门自写)。 */
28
29
  export declare const CAPABILITY_FACT_WHY: {
29
30
  readonly run_store_absent: "no durable run store is wired (DB_BACKEND=mysql|pg|local)";
@@ -83,6 +84,7 @@ export declare const CAPABILITY_FACT_WHY: {
83
84
  readonly not_host_lane: "the execution lane is not the single-user host lane (REMOTE_EXEC=host)";
84
85
  readonly not_k8s_lane: "the execution lane is not the k8s pod sandbox (REMOTE_EXEC=k8s)";
85
86
  readonly workspace_snapshot_store_absent: "no workspace snapshot store is configured (MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY)";
87
+ readonly workspace_cache_off: "the build-cache volume per workspace space is off (WORKSPACE_CACHE=off, the default)";
86
88
  };
87
89
  export type CapabilityFactWhy = keyof typeof CAPABILITY_FACT_WHY;
88
90
  /** 不可用臂。 */
@@ -155,11 +157,11 @@ export declare function sessionPolicy(d: Pick<Deps, "sessionPolicyStore" | "sess
155
157
  policyStore: Store<"sessionPolicyStore">;
156
158
  sessionStorage: OwnedSessionStore;
157
159
  }>;
158
- type SyncSessionStore = WithMethods<OwnerAwareSessionStore, "ownerOf" | "exportEntries" | "exportEntriesStream" | "beginImportStaging" | "sessionFaceOf" | "sessionSummaryOf" | "patchSessionMeta">;
159
- type SessionSyncWhy = "store_backend_absent" | "session_owner_face_absent" | "file_history_store_absent" | "session_export_absent" | "session_import_staging_absent" | "session_row_faces_absent" | "policy_purge_absent";
160
- /** 会话对等同步的**对话半族**(manifest / entries / plan / import 两相):后端 ∧ 属主面 ∧ 文件历史店 ∧ 导出两面 ∧
161
- * staged 导入面 ∧ 会话行三口(manifest 带 `title` / `pinned`、导入经唯一写口按导入落定的那一位住客重放它们 —— 会话面读住客)∧
162
- * policy 店能清(overwrite-dst 的 commit 要擦目的端旧 policy 行)。
160
+ type SyncSessionStore = WithMethods<OwnerAwareSessionStore, "exportEntries" | "exportEntriesStream" | "beginImportStaging" | "sessionFaceOf" | "sessionSummaryOf" | "patchSessionMeta">;
161
+ type SessionSyncWhy = "store_backend_absent" | "file_history_store_absent" | "session_export_absent" | "session_import_staging_absent" | "session_row_faces_absent" | "policy_purge_absent";
162
+ /** 会话对等同步的**对话半族**(manifest / entries / plan / import 两相):后端 ∧ 文件历史店 ∧ 导出两面 ∧ staged 导入面 ∧
163
+ * 会话行三口(门读会话面 —— 属主 + 化身一次读,属主判门、化身给 identical 短路腿;manifest 带 `title` / `pinned`;导入经唯一写口按导入落定的
164
+ * 那一位住客重放它们)∧ policy 店能清(overwrite-dst 的 commit 要擦目的端旧 policy 行)。
163
165
  * 文件历史的 blob 搬运是另一只事实。 */
164
166
  export declare function sessionSync(d: Pick<Deps, "backend" | "sessionStorage" | "fileHistoryStore" | "sessionPolicyStore">): Fact<SessionSyncWhy, {
165
167
  backend: Store<"backend">;
@@ -303,18 +305,27 @@ export declare function workflowsJournal(d: Pick<Deps, "workflowRunStore" | "wor
303
305
  journal: Store<"workflowJournalStore">;
304
306
  }>;
305
307
  /**
306
- * k8s 车道工作区跨轮保留的有效策略 —— k8s 车道 ∧ 快照存储在场 ⇒ `{ persist, keep }`(未表态的旋钮取缺省,缺省值属主
307
- * `workspace-space.ts`;`persist` 缺省 `off`,opt-in `space`)。**唯一读法**:能力位 `workspaceSync` 投影它的见证,k8s 适配器工厂(`boot/execution-env.ts`)把同一份
308
+ * k8s 车道工作区跨轮保留的有效策略 —— k8s 车道 ∧ 快照存储在场 ⇒ `{ persist, keep, timeoutMs }`(未表态的旋钮取缺省,缺省值属主
309
+ * `workspace-space.ts`;`persist` 缺省 `space`,`off` = 每轮新建)。**唯一读法**:能力位 `workspaceSync` 投影它的见证,k8s 适配器工厂(`boot/execution-env.ts`)把同一份
308
310
  * 见证交给适配器(适配器自己不再解析策略)。`persist: "off"` 仍是 available(存储在、没开 —— 缺省或运维显式 off)—— 与缺席不同义。
309
311
  *
310
- * **为何只读配置仍恒真**:`space` 的空间按会话住客分区,住客只有一只读口 —— 会话店的 `sessionFaceOf`。会话店没有它(内存会话店)的
311
- * 组合在起服时就被拒(`boot/execution-env.ts` 的 `assertWorkspaceSpaceReadable`,装配第 5 段,读的正是本事实的见证)⇒ 凡是起得来、
312
- * 报 `space` 的部署都读得出住客,本事实不需要运行期依赖(再加一位 = 同一前提两处判)。剩下的「这条 run 读不到住客」是逐 run 的读失败
313
- * (适配器的 `unavailable` 臂,响亮一行),不是部署形。
312
+ * **为何只读配置仍恒真**:`space` 的空间按会话住客分区、按 run 店的一行提交 —— 住客只有一只按 id 读(会话店的 `incarnationOf`,
313
+ * 会话行上的那一格),行住在存储后端里。缺一的组合在起服时就被拒(`boot/execution-env.ts` 的 `assertWorkspaceSpaceReadable`,装配第 5 段,
314
+ * 读的正是本事实的见证)⇒ 凡是起得来、报 `space` 的部署都读得出住客、写得下行,本事实不需要运行期依赖(再加一位 = 同一前提两处判)。
315
+ * 剩下的「这条 run 读不到住客」是逐 run 的读失败(适配器的 `unavailable` 臂,响亮一行),不是部署形。
314
316
  */
315
317
  export declare function workspaceSpace(c: Pick<ServiceConfig, "remoteExec">): Fact<"not_k8s_lane" | "workspace_snapshot_store_absent", {
316
318
  policy: WorkspaceSyncCapability;
317
319
  }>;
320
+ /**
321
+ * k8s 车道按工作区空间的构建缓存卷的有效策略 —— k8s 车道 ∧ `WORKSPACE_CACHE=volume` ⇒ `{ mount, size }`(缺省值属主 `workspace-cache.ts`)。
322
+ * **唯一读法**:能力位 `workspaceSync.cache` 投影它的见证,k8s 适配器工厂(`boot/execution-env.ts`)把同一份见证交给适配器。与快照存储无关
323
+ * (卷属于空间,不属于快照)。为何只读配置仍恒真:卷按会话住客键控,住客读口缺席的组合起服即拒(`assertWorkspaceSpaceReadable`);剩下的
324
+ * 「这条 run 拿不到卷」是逐 run 的 F 类放行(`server.workspace-cache.unavailable` 计数 + 一行),不是部署形。
325
+ */
326
+ export declare function workspaceCache(c: Pick<ServiceConfig, "remoteExec">): Fact<"not_k8s_lane" | "workspace_cache_off", {
327
+ cache: WorkspaceCacheCapability;
328
+ }>;
318
329
  /** 自唤醒调度器挂在宿主 env 上:`SCHEDULER_ENABLED` ∧ 单用户 ∧ host 车道。装配(`boot/execution-env.ts`)与能力位同调。 */
319
330
  export declare function hostScheduler(c: Pick<ServiceConfig, "schedulerEnabled" | "requirePrincipal" | "remoteExec">): Fact<"scheduler_disabled" | "multi_tenant_deployment" | "not_host_lane">;
320
331
  export {};
@@ -3,7 +3,7 @@ import { resolveStreamApprovalGate } from "./tool-approval.js";
3
3
  import { selfOrchestrationDenial } from "./task-workflow.js";
4
4
  import { centerEntitlementSourceWired } from "./runtime-caps-resolver.js";
5
5
  import { overwriteWipeIncapable } from "./session-sync.js";
6
- import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_DEFAULT } from "./workspace-space.js";
6
+ import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, workspaceSnapshotTimeoutOf } from "./workspace-space.js";
7
7
  export const CAPABILITY_FACT_WHY = {
8
8
  run_store_absent: "no durable run store is wired (DB_BACKEND=mysql|pg|local)",
9
9
  subagent_registry_absent: "the sub-agent steer registry is not wired",
@@ -62,6 +62,7 @@ export const CAPABILITY_FACT_WHY = {
62
62
  not_host_lane: "the execution lane is not the single-user host lane (REMOTE_EXEC=host)",
63
63
  not_k8s_lane: "the execution lane is not the k8s pod sandbox (REMOTE_EXEC=k8s)",
64
64
  workspace_snapshot_store_absent: "no workspace snapshot store is configured (MINIO_ENDPOINT / MINIO_ACCESS_KEY / MINIO_SECRET_KEY)",
65
+ workspace_cache_off: "the build-cache volume per workspace space is off (WORKSPACE_CACHE=off, the default)",
65
66
  };
66
67
  const no = (why) => ({ available: false, why });
67
68
  const yes = (w) => ({ available: true, ...w });
@@ -152,12 +153,10 @@ export function sessionPolicy(d) {
152
153
  export function sessionSync(d) {
153
154
  if (d.backend === undefined)
154
155
  return no("store_backend_absent");
155
- const s = d.sessionStorage;
156
- if (s?.ownerOf === undefined)
157
- return no("session_owner_face_absent");
158
156
  if (d.fileHistoryStore === undefined)
159
157
  return no("file_history_store_absent");
160
- if (s.exportEntries === undefined || s.exportEntriesStream === undefined)
158
+ const s = d.sessionStorage;
159
+ if (s?.exportEntries === undefined || s.exportEntriesStream === undefined)
161
160
  return no("session_export_absent");
162
161
  if (s.beginImportStaging === undefined)
163
162
  return no("session_import_staging_absent");
@@ -360,7 +359,15 @@ export function workspaceSpace(c) {
360
359
  return no("not_k8s_lane");
361
360
  if (!rx.s3Snapshot)
362
361
  return no("workspace_snapshot_store_absent");
363
- return yes({ policy: { persist: rx.workspacePersist ?? WORKSPACE_PERSIST_DEFAULT, keep: rx.workspaceSnapshotKeep ?? WORKSPACE_SNAPSHOT_KEEP_DEFAULT } });
362
+ return yes({ policy: { persist: rx.workspacePersist ?? WORKSPACE_PERSIST_DEFAULT, keep: rx.workspaceSnapshotKeep ?? WORKSPACE_SNAPSHOT_KEEP_DEFAULT, timeoutMs: workspaceSnapshotTimeoutOf(rx) } });
363
+ }
364
+ export function workspaceCache(c) {
365
+ const rx = c.remoteExec;
366
+ if (rx?.provider !== "k8s")
367
+ return no("not_k8s_lane");
368
+ if (rx.workspaceCache === undefined)
369
+ return no("workspace_cache_off");
370
+ return yes({ cache: { mount: rx.workspaceCache.mount, size: rx.workspaceCache.size } });
364
371
  }
365
372
  export function hostScheduler(c) {
366
373
  if (c.schedulerEnabled !== true)
@@ -2,7 +2,9 @@ import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } fr
2
2
  import { GLOBAL_SCOPE } from "@sema-agent/settings-schema/api/scopes";
3
3
  import { WEB_ADMIN_ENV, DOMAIN_SCHEMAS, buildRetiredEnvWire } from "@sema-agent/settings-schema";
4
4
  import { CENTER_CREDENTIAL_RENEW_LEAD_DEFAULT_MS, CENTER_CREDENTIAL_RENEW_LEAD_MAX_MS, CENTER_CREDENTIAL_RENEW_LEAD_MIN_MS } from "./center-credential-renewer.js";
5
- import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE } from "./workspace-space.js";
5
+ import { SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS } from "./sandbox-egress-policy.js";
6
+ import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE_SIZE_DEFAULT, WORKSPACE_CACHE_WORDS } from "./workspace-cache.js";
7
+ import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE } from "./workspace-space.js";
6
8
  import { THINKING_FORMATS } from "./model-compat.js";
7
9
  import { AUTO_MODE_CLASSIFIER_TIMEOUT_BOUNDS, CENTER_BASE_URL_RULE, CONFIG_PROVIDER_WORDS, DRAIN_SECOND_SIGNAL_MIN_GAP_BOUNDS, DRAIN_SECOND_SIGNAL_MIN_GAP_DEFAULT_MS, FIRST_BYTE_TIMEOUT_BOUNDS, FLEET_LEASE_OUTAGE_MAX_DEFAULT_MS, RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
8
10
  import { webSearchProviderFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
@@ -65,7 +67,7 @@ export const CONFIG_CATALOG = [
65
67
  r("APPROVAL_HMAC_KEYS", "approval", "secret", "直连门(D-G)HMAC 完整性密钥集(空=直连门不激活)", { danger: SEC }),
66
68
  r("APPROVAL_NEVER_AUTO", "approval", "csv", "永不被 AUTO_BUDGET 自动放行的工具集(不可逆动作恒要人)", { danger: SEC, configKey: "approvalNeverAuto" }),
67
69
  r("APPROVAL_REQUIRE", "approval", "csv", "必须人审的工具名单(F4 门;center 腿热:refresh 拍整表换代,无 checkpoint 店的部署发非空名单 ⇒ 审批组整组拒)", { danger: SEC, configKey: "approvalRequire", center: cGov("approvalRequire") }),
68
- r("APPROVAL_TERMINAL_BACKSTOP_MS", "approval", "number", "SQL checkpoint 店 pending 行的 crash-safe 绝对 GC 上限(ms;下限 60s,非有限值落默认)", { staticDefault: String(30 * 86_400_000), danger: DATA }),
70
+ r("APPROVAL_TERMINAL_BACKSTOP_MS", "approval", "number", "checkpoint 店(SQL 与 local 文件孪生同一公式)pending 行的 crash-safe 绝对 GC 上限(ms;下限 60s,非有限值落默认)", { staticDefault: String(30 * 86_400_000), danger: DATA }),
69
71
  r("APPROVAL_TIMEOUT_SEC", "approval", "number", "durable HITL 等人上限(秒);0=无限期等,时间型 suspended 回收不跑", { staticDefault: "0", configKey: "approvalTimeoutSec" }),
70
72
  r("ASK_QUESTION_ENABLED", "approval", "boolean", "AskUserQuestion 活流 HITL 装配开关", { derivedDefaultNote: "posture 三态:显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)⇒ on / 多租户 ⇒ off", configKey: "askQuestionEnabled" }),
71
73
  r("ATTACHMENT_MAX_BYTES", "limitsHttp", "number", "附件单文件字节上限(上传整段内存缓冲的 RAM 束)", { staticDefault: String(32 * 1024 * 1024), configKey: "attachmentMaxBytes", center: cLimits("attachmentMaxBytes") }),
@@ -193,7 +195,9 @@ export const CONFIG_CATALOG = [
193
195
  r("K8S_MOUNT_PATH", "orchestration", "path", "k8s 工作区挂载路径"),
194
196
  r("K8S_NAMESPACE", "orchestration", "string", "沙箱 pod namespace"),
195
197
  r("K8S_RUNTIME_CLASS", "orchestration", "string", "沙箱 pod runtimeClass(Kata 等)"),
198
+ r("K8S_SANDBOX_ENV", "orchestration", "secret", "转发进 k8s 沙箱 pod 容器 env 的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值只进 pod env —— 不进 spec 的其它位置 / 工作区 / 快照 / center / 事件日志;与 LSP_ENV_ALLOW / E2B_SANDBOX_ENV / DOCKER_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;与包源派生 env 撞名时显式名单胜;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)", { danger: SEC }),
196
199
  r("K8S_SANDBOX_IMAGE", "orchestration", "string", "k8s 车道沙箱镜像(REMOTE_EXEC=k8s 必需件)"),
200
+ r("K8S_SANDBOX_IPV6", "orchestration", "enum", "k8s 沙箱 pod 的 IPv6:disabled = pod 级 sysctl 关 IPv6(net.ipv6.conf.all / default.disable_ipv6=1;两只都不在 k8s 的 safe sysctl 表里 ⇒ 每个节点 kubelet 要 --allowed-unsafe-sysctls 点名,否则 pod 起不来;沙箱 ns 不能 enforce PodSecurity baseline / restricted);enabled = 不动 pod(pod 保留平台给的 IPv6,只执行 IPv4 的出网策略管不到它,起服自检每次一行 sandbox_egress_policy_gap{kind: ipv6_enabled});词表外拒启", { enumValues: SANDBOX_IPV6_WORDS, staticDefault: SANDBOX_IPV6_DEFAULT, danger: SEC }),
197
201
  r("K8S_TIMEOUT_MS", "orchestration", "number", "k8s 沙箱 pod 寿命(activeDeadlineSeconds,ms;整只 pod 的泄漏护栏,后台任务超时也被它钳;各条命令的超时见工具超时)"),
198
202
  r("K8S_TOKEN", "orchestration", "secret", "k8s API token(缺席走 in-cluster SA)", { danger: SEC }),
199
203
  r("KUBERNETES_SERVICE_HOST", "orchestration", "string", "平台注入的 in-cluster k8s API host(K8S_API_URL 缺席时用)"),
@@ -216,7 +220,7 @@ export const CONFIG_CATALOG = [
216
220
  r("LOCAL_DATA_ROOT", "store", "path", "file 店数据根的最高优先覆写(设了则与配置根分家,boot 出声)", { danger: DATA }),
217
221
  r("LOCKED_CONFIG_KEYS", "auth", "csv", "管理员锁定层声明(词表=core 闭集注册表,未知键拒启)", { danger: SEC, configKey: "lockedConfigKeys" }),
218
222
  r("LOG_LEVEL", "observability", "string", "日志档位", { staticDefault: "info", configKey: "logLevel" }),
219
- r("LSP_ENABLED", "orchestration", "boolean", "LSP 沙箱腿 opt-in(需烤好的 LSP 模板;host 腿另有 LSP_HOST_ENABLED)", { staticDefault: "false", configKey: "lspEnabled" }),
223
+ r("LSP_ENABLED", "orchestration", "boolean", "LSP 沙箱腿 opt-in —— 本版 e2b / k8s 执行环境不实现绑定读,开了也不挂(起服一行 lsp_not_mounted);host 腿另有 LSP_HOST_ENABLED", { staticDefault: "false", configKey: "lspEnabled" }),
220
224
  r("LSP_ENV_ALLOW", "orchestration", "secret", `host 腿语言服务器子进程 env 的点名放行名单(点到的键从服务 env 原样放回、部署方显式承担;没点到的照剥;与 host shell inheritEnv:[names] 同一只谓词;形错 / 点名任一只名单型旋钮(含自己)拒启且不回显值;本进程环境里没有的名按序号 warn、不放行、不回显;多租户姿态(忽略时起服 warn 一次):${describeAllowlistPostures()};原文按凭据类对待:只回在场位、不进任何子进程,生效名单见起服披露行 allowed)`, { danger: SEC, derivedDefaultNote: "缺席 / 空串 ⇒ 空名单 = 全剥(7.100.0 行为)" }),
221
225
  r("LSP_HOST_ENABLED", "orchestration", "boolean", "LSP host 腿开关(本机 language server,优雅降级)", { staticDefault: "true", configKey: "lspHostEnabled" }),
222
226
  r("MANUAL_MODE_SHELL_GATE", "approval", "enum", "手动模式 shell 门收紧腿(off=no-op 非放松 —— 本键只有收紧半场;拼错拒启;S-877 起 classify 退役 = 拒启指路)", { danger: SEC, enumValues: ["always", "off"], echoWords: ["always"], resolve: (c) => c.manualModeShellGate ?? null, derivedDefaultNote: "off/未设 ⇒ 本键不贡献任何东西(归一成键缺席)⇒ 回显 null;真姿态只有 always 一个(core 7.34.0 起形状档在每一种模式下都装,classify 不再是运维选的档)" }),
@@ -454,7 +458,7 @@ export const CONFIG_CATALOG = [
454
458
  r("TASK_MAX_OUTPUT_TOKENS_MAX", "limitsHttp", "number", "调用方自报 limits.maxOutputTokens 的运营方封顶(Math.min 钳显式请求值;坏值=封顶不生效+响亮 warn)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
455
459
  r("TASK_MAX_TURNS_MAX", "limitsHttp", "number", "调用方自报 limits.maxTurns 的运营方封顶(同族三旋钮;只钳显式请求值,不是全队默认限额)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
456
460
  r("TASK_TIMEOUT_MAX_SEC", "limitsHttp", "number", "调用方自报 limits.timeoutSec 的运营方封顶(同族三旋钮)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
457
- r("TASK_TIMEOUT_SEC", "limitsHttp", "number", "任务墙钟超时(秒);0/缺席=按 tenancy(单用户无墙;多租户 2400s、大任务 3600s);正值**只抬不降**(Math.max 基值);负值拒启", { staticDefault: "0", derivedDefaultNote: "0/缺席 ⇒ 单用户无墙、多租户 2400/3600s;要低于基值的配速请用 body.limits.maxWalltimeMs" }),
461
+ r("TASK_TIMEOUT_SEC", "limitsHttp", "number", "任务墙钟超时(秒);0/缺席=按 tenancy(单用户无墙;多租户 2400s);正值**只抬不降**(Math.max 基值);负值拒启", { staticDefault: "0", derivedDefaultNote: "0/缺席 ⇒ 单用户无墙、多租户 2400s;要低于基值的配速请用 body.limits.maxWalltimeMs" }),
458
462
  r("TASK_WRITE_FACE_ESCALATION", "orchestration", "enum", "任务层「掀开写围栏」声明的运维天花板档位(S-591;只抬天花板,不是部署席)", { enumValues: [...TASK_WRITE_FACE_ESCALATIONS], danger: SEC, derivedDefaultNote: "三态:allow ⇒ 任务层 bypassPermissions 的开放写面成立;refuse ⇒ 无条件拒;**缺席 = 本层不表态** ⇒ 按托管 posture 判(托管拒 / 单用户 turnkey 放行)。判据单点 src/hosted-posture.ts;device 车道不适用(成文裁定:那条穿越结构性不存在)", configKey: "taskWriteFaceEscalation" }),
459
463
  r("TOOL_APPROVAL_ENABLED", "approval", "boolean", "活体工具审批 HITL(onAsk 装配;关 ⇒ core resolveAsk 走 headless 缺省)", { danger: SEC, derivedDefaultNote: "posture 三态:显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)⇒ on / 多租户 ⇒ off", configKey: "toolApprovalEnabled" }),
460
464
  r("TOOL_DEFER_LONGTAIL", "orchestration", "boolean", "长尾一方工具 defer 呈现(实验)", { staticDefault: "false", configKey: "toolDeferLongtail" }),
@@ -479,11 +483,16 @@ export const CONFIG_CATALOG = [
479
483
  r("WORKFLOW_RUN_STORE", "orchestration", "enum", "后台 workflow run 店后端(auto=有 SQL 用 SQL,否则 File)", { enumValues: ["auto", "file", "memory"], staticDefault: "auto", configKey: "workflowRunStoreBackend" }),
480
484
  r("WORKFLOW_SIZE_GUIDELINE", "orchestration", "enum", "Workflow 工具卡的规模建议(advisory;typo 拒启)", { enumValues: ["small", "medium", "large", "unrestricted"], configKey: "workflowSizeGuideline" }),
481
485
  r("WORKFLOW_TOTAL_TIMEOUT_MS", "orchestration", "number", "workflow 脚本总时限(ms;0/负不是禁用 —— 无禁用臂)", { staticDefault: "3600000" }),
486
+ r("WORKSPACE_CACHE", "orchestration", "enum", "k8s 按工作区空间的构建缓存卷:volume = 每个空间(会话住客)一只 PVC(ws-cache-<摘要>,标签 sema.space,StorageClass 用集群缺省),挂到 WORKSPACE_CACHE_MOUNT,并在 pod env 注入工具缓存闭表(GRADLE_USER_HOME / MAVEN_OPTS / npm_config_cache / PIP_CACHE_DIR / UV_CACHE_DIR / GOMODCACHE;Rust 依赖缓存不盖 —— Cargo 没有只放下载缓存的 env;K8S_SANDBOX_ENV 同名显式值胜);卷在快照之外、可丢,pod 删卷留;卷拿不到(建不成 / 快照时限内没绑定)⇒ run 照起、无卷无缓存 env,fail-open 计数 + 一行 workspace_cache_unavailable;off = 不建卷。前提:会话店读得出会话住客(内存会话店 × volume ⇒ 拒启);委派子代不挂卷;词表外拒启", { enumValues: WORKSPACE_CACHE_WORDS, staticDefault: WORKSPACE_CACHE_DEFAULT }),
487
+ r("WORKSPACE_CACHE_MOUNT", "orchestration", "path", "k8s 构建缓存卷在沙箱 pod 里的挂载点(绝对路径;与工作区挂载 K8S_MOUNT_PATH 相同 / 互相嵌套 ⇒ 拒启;只在 WORKSPACE_CACHE=volume 时读)", { staticDefault: WORKSPACE_CACHE_MOUNT_DEFAULT }),
488
+ r("WORKSPACE_CACHE_SIZE", "orchestration", "string", "k8s 构建缓存卷的大小(PVC 申请量,k8s quantity 形如 20Gi / 500Mi;形不合拒启;只在 WORKSPACE_CACHE=volume 时读)", { staticDefault: WORKSPACE_CACHE_SIZE_DEFAULT }),
482
489
  r("WORKSPACE_FILE_MAX_BYTES", "limitsHttp", "number", "已随 workspace 浏览面退役(S-15):值照常校验/落值,应用于空(重启也不生效)", { envTiming: "inert", configKey: "workspaceFileMaxBytes", center: cLimits("workspaceFileMaxBytes", { timing: "inert" }) }),
483
- r("WORKSPACE_PERSIST", "orchestration", "enum", "k8s 工作区跨轮保留(opt-in):space = 每条 run 结束把 /workspace 快照进本会话住客的空间、下一条 run 开始恢复最近一份(开着时有已知残窗:提交标记晚于拆环境上界落地 ⇒ 上一条的改动可能被覆盖,见 USAGE §9.3b);off = 每轮新建(缺省)。设了 space 却没配快照存储(MINIO_* 三键)⇒ 不生效,起服一行 workspace_persist_unavailable 点名缺键;前提:会话店读得出会话住客(local / SQL 会话店),内存会话店设 space(且配了快照存储)⇒ 拒启;词表外拒启", { enumValues: WORKSPACE_PERSIST_WORDS, staticDefault: WORKSPACE_PERSIST_DEFAULT, danger: DATA }),
490
+ r("WORKSPACE_PERSIST", "orchestration", "enum", "k8s 工作区跨轮保留:space(缺省)= 每条 run 结束把 /workspace 快照进本会话住客的空间、下一条 run 开始恢复最近一份(上一条还在保存 ⇒ 下一条等它存完,最多等一个上传时限,见 USAGE §9.3b);off = 每轮新建。space(含缺省)却没配快照存储(MINIO_* 三键)⇒ 不生效,起服一行 workspace_persist_unavailable 点名缺键;前提:会话店读得出会话住客(local / SQL 会话店),内存会话店 × space(含缺省,且配了快照存储)⇒ 拒启(出路 WORKSPACE_PERSIST=off);词表外拒启", { enumValues: WORKSPACE_PERSIST_WORDS, staticDefault: WORKSPACE_PERSIST_DEFAULT, danger: DATA }),
484
491
  r("WORKSPACE_SNAPSHOT_BUCKET", "orchestration", "string", "k8s 工作区快照桶", { derivedDefaultNote: "缺席 ⇒ workspaces" }),
492
+ r("WORKSPACE_SNAPSHOT_EXCLUDE", "orchestration", "csv", "k8s 工作区快照的排除表(相对工作区根的 tar 排除模式,逗号分隔,如 build,.gradle,node_modules/.cache;每项一个 --exclude=./<项>,run 末快照与停驻挂起同一张表 —— 排除的目录恢复后不在,构建产物本就可再生;项形闭:相对路径段,不含 .. / 前导 / / 空白 / 引号,坏项拒启并点名第几项;不读项目 .gitignore)", { derivedDefaultNote: "缺席 = 不排除(上传命令与旧版逐字同)" }),
485
493
  r("WORKSPACE_SNAPSHOT_KEEP", "orchestration", "number", `k8s 工作区每个空间(会话住客)保留的快照份数(整数 [${WORKSPACE_SNAPSHOT_KEEP_RANGE.min}, ${WORKSPACE_SNAPSHOT_KEEP_RANGE.max}];写新快照后删最老的;域外拒启)`, { staticDefault: String(WORKSPACE_SNAPSHOT_KEEP_DEFAULT), danger: DATA }),
486
494
  r("WORKSPACE_SNAPSHOT_PREFIX", "orchestration", "string", "k8s 工作区快照 key 前缀"),
495
+ r("WORKSPACE_SNAPSHOT_TIMEOUT_MS", "orchestration", "number", `k8s 工作区快照的时限(毫秒,整数 [${WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.min}, ${WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.max}];域外拒启):一个数管四处 —— run 末上传与连沙箱时恢复的墙钟上界、空间结束时回收座一次调用的绝对截止(卷与对象都在内)、下一条 run 等上一条存完的最长等待(提交围栏的存活上界);大文件读写的墙钟也是它。大工作区(构建目录 GB 级)撞上界 ⇒ 调大它或配 WORKSPACE_SNAPSHOT_EXCLUDE`, { staticDefault: String(WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT) }),
487
496
  r("WORKSPACE_SNAPSHOT_TTL_SEC", "orchestration", "number", "k8s 工作区快照 presign TTL(秒)"),
488
497
  r("WORKTREE_ALLOWED_ROOTS", "orchestration", "csv", "worktree 隔离纵深白名单(缺省 = [repoRoot])", { danger: SEC }),
489
498
  r("WORKTREE_BASE_COMMIT", "orchestration", "string", "worktree 基准 ref(缺省 core HEAD)"),
@@ -1,4 +1,4 @@
1
- import { type DroppedConfigEntry, type UnreadConfigFile } from "./web-admin-client/read-warnings.js";
1
+ import { type DroppedConfigEntry, type RetiredDomainFinding, type UnreadConfigFile } from "./web-admin-client/read-warnings.js";
2
2
  import { FileConfigStore } from "@sema-agent/settings-schema/node";
3
3
  import { type EffectiveConfig as AgentConfigEffective, type EffectiveReadWarning } from "@sema-agent/settings-schema";
4
4
  import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, type EffectiveConfig } from "./web-admin-client/facade.js";
@@ -25,6 +25,9 @@ import type { ConfigProviderWord } from "./config-types.js";
25
25
  * `effective.models.droppedCatalogEntries`(`GET /v1/models.droppedEntries` 的源,与 `models` 同世代)。 */
26
26
  /** `unreadFiles`:`config.d` 里读腿根本不读的文件(不是域 / 不是便携域)—— **不**进候选门(不是读的域判形失败),调用方一份一行
27
27
  * `config_unread_file` 点名、其余照用;缺席 = 没有(本地读腿才会有)。 */
28
+ /** `retiredDomains`(S-1456):带事实的退役域文档(契约包的墓碑)—— 读腿不抛,交成发现,候选判官以成因 `retired_domain` 整拒这份候选(与域判形失败
29
+ * 同一条回落链、同一行拒判);缺席 = 没有。远端腿:那一键不进 effective、其余域照读;本地腿:契约读腿在读便携域之前就拒,交出的候选只有版本
30
+ * (这棵树的内容版本,哈希含退役文件 ⇒ 改 / 删它版本即变),内容是空的 effective(被整拒,不会被应用)。 */
28
31
  export type FetchEffectiveResult = {
29
32
  effective: EffectiveConfig;
30
33
  etag?: string;
@@ -34,6 +37,7 @@ export type FetchEffectiveResult = {
34
37
  }>;
35
38
  droppedEntries?: DroppedConfigEntry[];
36
39
  unreadFiles?: UnreadConfigFile[];
40
+ retiredDomains?: RetiredDomainFinding[];
37
41
  readWarnings: readonly EffectiveReadWarning[];
38
42
  credentialIdentity?: string;
39
43
  } | null;
@@ -1,8 +1,8 @@
1
1
  import { readMcpSourceLabel } from "./mcp-source-label.js";
2
2
  import { createHmac, randomBytes } from "node:crypto";
3
- import { splitFoldedReadWarnings, withDroppedCatalogEntries } from "./web-admin-client/read-warnings.js";
3
+ import { retiredDomainFindingOf, splitFoldedReadWarnings, withDroppedCatalogEntries } from "./web-admin-client/read-warnings.js";
4
4
  import { FileConfigStore } from "@sema-agent/settings-schema/node";
5
- import { danglingRefs, findSkillContent, renderRefIssue, skillContentHash, } from "@sema-agent/settings-schema";
5
+ import { danglingRefs, emptyEffective, findSkillContent, renderRefIssue, skillContentHash, } from "@sema-agent/settings-schema";
6
6
  import { redactSecrets } from "./trace/redact.js";
7
7
  import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, WebAdminHttpError, } from "./web-admin-client/facade.js";
8
8
  import { homedir } from "node:os";
@@ -84,7 +84,21 @@ export class LocalConfigProvider {
84
84
  readWarnings.push(w);
85
85
  },
86
86
  });
87
- const { effective: eff, domainErrors } = await store.getEffective({ tolerant: true });
87
+ let read;
88
+ let retired;
89
+ try {
90
+ read = await store.getEffective({ tolerant: true });
91
+ }
92
+ catch (e) {
93
+ retired = retiredDomainFindingOf(e);
94
+ if (retired === undefined)
95
+ throw e;
96
+ const version = await store.getVersion().catch(() => {
97
+ throw e;
98
+ });
99
+ read = { effective: { ...emptyEffective(), version }, domainErrors: [] };
100
+ }
101
+ const { effective: eff, domainErrors } = read;
88
102
  const tag = String(eff.version);
89
103
  if (etag !== undefined && etag === tag)
90
104
  return null;
@@ -97,7 +111,7 @@ export class LocalConfigProvider {
97
111
  }
98
112
  for (const [domain, rows] of byReferrer)
99
113
  refErrors.push({ domain, error: rows.join("; ") });
100
- const folded = splitFoldedReadWarnings(readWarnings);
114
+ const folded = splitFoldedReadWarnings(readWarnings, "local");
101
115
  const parseErrors = folded.domainErrors.map((w) => ({ domain: w.domain, error: redactConfigError(w.error) }));
102
116
  const droppedEntries = folded.droppedEntries.map(redactDroppedEntry);
103
117
  const errors = [...domainErrors.map((de) => ({ domain: de.domain, error: redactConfigError(de.error) })), ...parseErrors, ...refErrors];
@@ -108,6 +122,7 @@ export class LocalConfigProvider {
108
122
  ...(errors.length > 0 ? { domainErrors: errors } : {}),
109
123
  ...(droppedEntries.length > 0 ? { droppedEntries } : {}),
110
124
  ...(folded.unreadFiles.length > 0 ? { unreadFiles: folded.unreadFiles } : {}),
125
+ ...(retired !== undefined ? { retiredDomains: [retired] } : {}),
111
126
  };
112
127
  }
113
128
  async fetchSkillContent(contentHash) {
@@ -172,6 +187,9 @@ export function mapToServiceEffective(eff, version) {
172
187
  ...(s.contentOrigin !== undefined ? { contentOrigin: s.contentOrigin } : {}),
173
188
  ...readMcpSourceLabel(s.name, s.source),
174
189
  ...(s.toolFaces !== undefined ? { toolFaces: s.toolFaces } : {}),
190
+ ...(s.toolAxes !== undefined ? { toolAxes: s.toolAxes } : {}),
191
+ ...(s.authForm !== undefined ? { authForm: s.authForm } : {}),
192
+ ...(s.entryKind !== undefined ? { entryKind: s.entryKind } : {}),
175
193
  };
176
194
  if (s.transport.kind === "stdio") {
177
195
  return {
@@ -191,6 +209,7 @@ export function mapToServiceEffective(eff, version) {
191
209
  url: s.transport.url,
192
210
  ...(s.transport.headerRefs !== undefined ? { headerRefs: s.transport.headerRefs } : {}),
193
211
  ...(s.transport.principalHeader !== undefined ? { principalHeader: s.transport.principalHeader } : {}),
212
+ ...(s.transport.assertionHeader !== undefined ? { assertionHeader: s.transport.assertionHeader } : {}),
194
213
  },
195
214
  };
196
215
  }),
@@ -604,7 +604,8 @@ export interface ServiceConfigFlat {
604
604
  defaultScopes?: string[];
605
605
  }>;
606
606
  /** Role → model map (1.24): `subagent`/`summarize` → the cheap model when `MODEL_CHEAP_ID` is set
607
- * (council lenses + compaction get cheap), `default`/`team`/`synthesize` stay on the main model. */
607
+ * (delegated children + compaction get cheap), `default` stays on the main model. Keys are the engine's role
608
+ * words (core `MODEL_ROLES`, six since 7.49.0). */
608
609
  roles: ModelRoles;
609
610
  /** [ref] / [ref] —— **「`@` 能点名哪些模型」**(settings-schema 11 `models.atMention` 的服务端消费面;三态闭集
610
611
  * `{mode:"all"}` / `{mode:"none"}` / `{mode:"only", models}`,形与缺省读法的属主是契约,本仓读法 = `model-select.ts`
@@ -630,7 +631,8 @@ export interface ServiceConfigFlat {
630
631
  * engine DB_BACKEND picks), a historical label kept to avoid churning every comparison site.
631
632
  */
632
633
  sessionBackend: "memory" | "tidb" | "auto";
633
- /** Warm-cache idle TTL (seconds) for woken TiDB sessions; 0 disables. Requires session affinity. */
634
+ /** Warm-cache idle TTL (seconds) for woken durable sessions; 0 disables. Correct under any routing: a hit is served only
635
+ * while the store's head still names the cached leaf (`plugins/caching-session-store.ts`). */
634
636
  sessionCacheTtlSec: number;
635
637
  /** B1 /health 活体 store 探测:后台缓存探针间隔 ms(STORE_PROBE_INTERVAL_MS;0=显式关,负/非数 boot
636
638
  * 响亮拒)。只对 SQL 后端接线;探针原语=backend.dbNowMs()(S10 时钟探针的真 DB 往返,零新 SQL 面)。 */
@@ -898,6 +900,12 @@ export interface ServiceConfigFlat {
898
900
  /** Resource-profile knobs: sandbox pod limits (adapter defaults: 2Gi / 1 cpu). */
899
901
  memory?: string;
900
902
  cpu?: string;
903
+ /** `K8S_SANDBOX_IPV6`(闭集与缺省的属主 `sandbox-egress-policy.ts`):`disabled` ⇒ 沙箱 pod 级 sysctl 关 IPv6;`enabled` ⇒ pod spec 不带 sysctl。
904
+ * loader 恒铸;可选只为手搓配置。 */
905
+ sandboxIpv6?: import("./sandbox-egress-policy.js").SandboxIpv6;
906
+ /** `K8S_SANDBOX_ENV=NAME,NAME` 点到、且本进程环境里真有的键 ⇒ 原值(与 e2b 的 `sandboxEnv` 同名同形、同一只解析器、同一只谓词 `allowlistedEnv`,
907
+ * 空串值照带)。只进 pod 容器 env;装配点与包源派生 env 合并一处,显式名单胜。键缺席 = 旋钮未设 / 空串(零转发)。 */
908
+ sandboxEnv?: Record<string, string>;
901
909
  s3Snapshot?: {
902
910
  endpoint: string;
903
911
  bucket: string;
@@ -913,6 +921,15 @@ export interface ServiceConfigFlat {
913
921
  * unavailable). */
914
922
  workspacePersist?: import("./workspace-space.js").WorkspacePersist;
915
923
  workspaceSnapshotKeep?: number;
924
+ /** `WORKSPACE_SNAPSHOT_TIMEOUT_MS` — the lane's one data bound (`workspace-space.ts`); the loader always mints it, the boot
925
+ * assembly hands it to the adapter as `dataTimeoutMs`, the capability fact reports it (`workspaceSync.timeoutMs`). */
926
+ workspaceSnapshotTimeoutMs?: number;
927
+ /** The build-cache volume per workspace space (`WORKSPACE_CACHE=volume`, `_SIZE`, `_MOUNT`; `workspace-cache.ts`); present iff
928
+ * `volume`. Effective policy = the capability fact `workspaceCache`. */
929
+ workspaceCache?: import("./workspace-cache.js").WorkspaceCacheCapability;
930
+ /** `WORKSPACE_SNAPSHOT_EXCLUDE` — the snapshot's tar exclusion table (closed item form, `workspace-space.ts`); present iff
931
+ * non-empty. Handed to the adapter's one tar upload (both snapshot lanes); never on the wire. */
932
+ workspaceSnapshotExclude?: readonly string[];
916
933
  } | {
917
934
  provider: "ssh";
918
935
  host: string;
package/dist/config.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { DEFAULT_AT_MENTION, DEFAULT_MODEL_REF } from "./model-select.js";
3
3
  import { homedir } from "node:os";
4
- import { join } from "node:path";
4
+ import { join, posix as posixPath } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, isModelPriced, PROTOCOL_TABLE, RECOMMENDED_SENSITIVE_PATTERNS, judgeRuleToolName, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable } from "@sema-agent/core";
7
7
  import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES, WEB_ADMIN_ENV, retiredEnvRefusal } from "@sema-agent/settings-schema";
@@ -26,7 +26,9 @@ import { describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf }
26
26
  import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, centerIdentityRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
27
27
  import { declaredSemaConfigDir } from "./sema-config-dir.js";
28
28
  import { lkgPathOverrideOf } from "./config-lkg.js";
29
- import { WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE } from "./workspace-space.js";
29
+ import { K8S_WORKSPACE_MOUNT_DEFAULT, WORKSPACE_PERSIST_DEFAULT, WORKSPACE_PERSIST_WORDS, WORKSPACE_SNAPSHOT_KEEP_DEFAULT, WORKSPACE_SNAPSHOT_KEEP_RANGE, WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT, WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE, parseWorkspaceSnapshotExclude } from "./workspace-space.js";
30
+ import { SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS } from "./sandbox-egress-policy.js";
31
+ import { WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_MOUNT_DEFAULT, WORKSPACE_CACHE_SIZE_DEFAULT, WORKSPACE_CACHE_WORDS, isK8sQuantity, mountsOverlap } from "./workspace-cache.js";
30
32
  import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
31
33
  import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
32
34
  import { AUTONOMY_MODES } from "./runtime-governance.js";
@@ -1855,7 +1857,10 @@ function parseOrchestrationDomain(ctx) {
1855
1857
  ...((n) => (n !== undefined ? { timeoutMs: n } : {}))(optNumEnvStrict("K8S_TIMEOUT_MS")),
1856
1858
  ...(process.env.K8S_MEMORY ? { memory: process.env.K8S_MEMORY } : {}),
1857
1859
  ...(process.env.K8S_CPU ? { cpu: process.env.K8S_CPU } : {}),
1860
+ sandboxIpv6: enumEnv("K8S_SANDBOX_IPV6", SANDBOX_IPV6_DEFAULT, SANDBOX_IPV6_WORDS),
1861
+ ...((names) => (names !== undefined ? { sandboxEnv: allowlistedEnv(names, process.env) } : {}))(parseEnvNameAllowlist("K8S_SANDBOX_ENV", singleUserTurnkey)),
1858
1862
  ...k8sWorkspaceStore(),
1863
+ ...((c) => (c !== undefined ? { workspaceCache: c } : {}))(k8sWorkspaceCache()),
1859
1864
  }
1860
1865
  :
1861
1866
  process.env.REMOTE_EXEC === "ssh" && process.env.SSH_HOST && process.env.SSH_USER && process.env.SSH_PRIVATE_KEY
@@ -2387,6 +2392,11 @@ function k8sWorkspaceStore() {
2387
2392
  const workspaceSnapshotKeep = numEnvBounded("WORKSPACE_SNAPSHOT_KEEP", String(WORKSPACE_SNAPSHOT_KEEP_DEFAULT), WORKSPACE_SNAPSHOT_KEEP_RANGE.min, WORKSPACE_SNAPSHOT_KEEP_RANGE.max);
2388
2393
  if (!Number.isInteger(workspaceSnapshotKeep))
2389
2394
  throw new Error(`env WORKSPACE_SNAPSHOT_KEEP=${workspaceSnapshotKeep} must be an integer (a count of snapshots kept per space key)`);
2395
+ const workspaceSnapshotTimeoutMs = numEnvBounded("WORKSPACE_SNAPSHOT_TIMEOUT_MS", String(WORKSPACE_SNAPSHOT_TIMEOUT_MS_DEFAULT), WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.min, WORKSPACE_SNAPSHOT_TIMEOUT_MS_RANGE.max);
2396
+ if (!Number.isInteger(workspaceSnapshotTimeoutMs))
2397
+ throw new Error(`env WORKSPACE_SNAPSHOT_TIMEOUT_MS=${workspaceSnapshotTimeoutMs} must be an integer (milliseconds)`);
2398
+ const exclude = parseWorkspaceSnapshotExclude(process.env.WORKSPACE_SNAPSHOT_EXCLUDE);
2399
+ const knobs = { workspacePersist, workspaceSnapshotKeep, workspaceSnapshotTimeoutMs, ...(exclude.length > 0 ? { workspaceSnapshotExclude: exclude } : {}) };
2390
2400
  const endpoint = process.env.MINIO_ENDPOINT;
2391
2401
  const accessKey = process.env.MINIO_ACCESS_KEY;
2392
2402
  const secretKey = process.env.MINIO_SECRET_KEY;
@@ -2396,14 +2406,13 @@ function k8sWorkspaceStore() {
2396
2406
  CONFIG_NOTICES.push({
2397
2407
  level: "warn",
2398
2408
  event: "workspace_persist_unavailable",
2399
- fields: { missing: missing.join(","), effect: "k8s workspace starts empty every run (no snapshot store); set the missing keys, or drop WORKSPACE_PERSIST=space (the default is off) to silence this line" },
2409
+ fields: { missing: missing.join(","), effect: "k8s workspace starts empty every run (no snapshot store); set the missing keys, or set WORKSPACE_PERSIST=off to silence this line" },
2400
2410
  });
2401
2411
  }
2402
- return { workspacePersist, workspaceSnapshotKeep };
2412
+ return knobs;
2403
2413
  }
2404
2414
  return {
2405
- workspacePersist,
2406
- workspaceSnapshotKeep,
2415
+ ...knobs,
2407
2416
  s3Snapshot: {
2408
2417
  endpoint,
2409
2418
  bucket: process.env.WORKSPACE_SNAPSHOT_BUCKET ?? "workspaces",
@@ -2415,6 +2424,32 @@ function k8sWorkspaceStore() {
2415
2424
  },
2416
2425
  };
2417
2426
  }
2427
+ function k8sWorkspaceCache() {
2428
+ const word = enumEnv("WORKSPACE_CACHE", WORKSPACE_CACHE_DEFAULT, WORKSPACE_CACHE_WORDS);
2429
+ switch (word) {
2430
+ case "off":
2431
+ return undefined;
2432
+ case "volume":
2433
+ break;
2434
+ default: {
2435
+ const unreachable = word;
2436
+ throw new Error(`unhandled WORKSPACE_CACHE word: ${String(unreachable)}`);
2437
+ }
2438
+ }
2439
+ const size = process.env.WORKSPACE_CACHE_SIZE || WORKSPACE_CACHE_SIZE_DEFAULT;
2440
+ if (!isK8sQuantity(size))
2441
+ throw new Error(`env WORKSPACE_CACHE_SIZE=${JSON.stringify(size)} is not a k8s quantity (a positive number with an optional unit, e.g. 20Gi / 500Mi)`);
2442
+ const raw = process.env.WORKSPACE_CACHE_MOUNT || WORKSPACE_CACHE_MOUNT_DEFAULT;
2443
+ if (!raw.startsWith("/"))
2444
+ throw new Error(`env WORKSPACE_CACHE_MOUNT=${JSON.stringify(raw)} is not an absolute path (the cache volume's mount inside the sandbox pod)`);
2445
+ const mount = posixPath.normalize(raw).replace(/(.)\/+$/, "$1");
2446
+ const workspace = posixPath.normalize(process.env.K8S_MOUNT_PATH || K8S_WORKSPACE_MOUNT_DEFAULT).replace(/(.)\/+$/, "$1");
2447
+ if (mount === "/" || mountsOverlap(mount, workspace)) {
2448
+ throw new Error(`env WORKSPACE_CACHE_MOUNT=${mount} overlaps the workspace mount ${workspace} (K8S_MOUNT_PATH) — the cache volume must sit OUTSIDE the workspace ` +
2449
+ "(the workspace is snapshotted between runs, the cache is not); pick a separate absolute path such as /cache");
2450
+ }
2451
+ return { mount, size };
2452
+ }
2418
2453
  export function loadConfig() {
2419
2454
  EXEC_LANE_NOTICE = undefined;
2420
2455
  rejectRetiredEnvs();