@sema-agent/server 1.315.0 → 1.317.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 (133) hide show
  1. package/README.md +9 -0
  2. package/README.zh-CN.md +7 -0
  3. package/dist/config-types.d.ts +1 -1
  4. package/dist/config.d.ts +14 -0
  5. package/dist/config.js +148 -45
  6. package/dist/elicitation.js +2 -2
  7. package/dist/http/idempotency.d.ts +1 -0
  8. package/dist/http/idempotency.js +3 -0
  9. package/dist/http/principal-gate.d.ts +12 -0
  10. package/dist/http/principal-gate.js +52 -0
  11. package/dist/http/route-ctx.d.ts +116 -0
  12. package/dist/http/route-ctx.js +2 -0
  13. package/dist/http/routes/approvals-assistant.d.ts +11 -0
  14. package/dist/http/routes/approvals-assistant.js +530 -0
  15. package/dist/http/routes/attachments.d.ts +4 -0
  16. package/dist/http/routes/attachments.js +106 -0
  17. package/dist/http/routes/capabilities.d.ts +4 -0
  18. package/dist/http/routes/capabilities.js +111 -0
  19. package/dist/http/routes/fleet.d.ts +4 -0
  20. package/dist/http/routes/fleet.js +147 -0
  21. package/dist/http/routes/images.d.ts +20 -0
  22. package/dist/http/routes/images.js +572 -0
  23. package/dist/http/routes/leader.d.ts +4 -0
  24. package/dist/http/routes/leader.js +48 -0
  25. package/dist/http/routes/memory-policy.d.ts +4 -0
  26. package/dist/http/routes/memory-policy.js +105 -0
  27. package/dist/http/routes/notify-wake.d.ts +4 -0
  28. package/dist/http/routes/notify-wake.js +133 -0
  29. package/dist/http/routes/observability.d.ts +4 -0
  30. package/dist/http/routes/observability.js +63 -0
  31. package/dist/http/routes/runs.d.ts +19 -0
  32. package/dist/http/routes/runs.js +967 -0
  33. package/dist/http/routes/session-sync.d.ts +36 -0
  34. package/dist/http/routes/session-sync.js +513 -0
  35. package/dist/http/routes/sessions-list.d.ts +4 -0
  36. package/dist/http/routes/sessions-list.js +117 -0
  37. package/dist/http/routes/sessions.d.ts +17 -0
  38. package/dist/http/routes/sessions.js +746 -0
  39. package/dist/http/routes/side-query.d.ts +4 -0
  40. package/dist/http/routes/side-query.js +88 -0
  41. package/dist/http/routes/tasks.d.ts +4 -0
  42. package/dist/http/routes/tasks.js +632 -0
  43. package/dist/http/routes/trace-usage.d.ts +4 -0
  44. package/dist/http/routes/trace-usage.js +239 -0
  45. package/dist/http/routes/workflows.d.ts +5 -0
  46. package/dist/http/routes/workflows.js +337 -0
  47. package/dist/http/run-meta.d.ts +11 -0
  48. package/dist/http/run-meta.js +16 -0
  49. package/dist/http/send.d.ts +7 -0
  50. package/dist/http/send.js +34 -0
  51. package/dist/http/server.d.ts +12 -12
  52. package/dist/http/server.js +289 -5502
  53. package/dist/http/sse-log.d.ts +25 -0
  54. package/dist/http/sse-log.js +59 -0
  55. package/dist/http/wire-types.d.ts +6 -0
  56. package/dist/http/workspace-content.d.ts +3 -0
  57. package/dist/http/workspace-content.js +20 -0
  58. package/dist/leader/endpoint.js +4 -4
  59. package/dist/main.js +5 -5
  60. package/dist/plugins/approval-store-sql.d.ts +52 -0
  61. package/dist/plugins/approval-store-sql.js +119 -0
  62. package/dist/plugins/breaker-state-sql.d.ts +29 -0
  63. package/dist/plugins/breaker-state-sql.js +104 -0
  64. package/dist/plugins/file-snapshot-store-sql.d.ts +38 -0
  65. package/dist/plugins/file-snapshot-store-sql.js +210 -0
  66. package/dist/plugins/image-index-sql.d.ts +33 -0
  67. package/dist/plugins/image-index-sql.js +328 -0
  68. package/dist/plugins/outcome-ledger-sql.d.ts +70 -0
  69. package/dist/plugins/outcome-ledger-sql.js +158 -0
  70. package/dist/plugins/pg-approval-store.d.ts +1 -27
  71. package/dist/plugins/pg-approval-store.js +1 -83
  72. package/dist/plugins/pg-breaker-state.d.ts +1 -19
  73. package/dist/plugins/pg-breaker-state.js +1 -88
  74. package/dist/plugins/pg-file-snapshot-store.d.ts +1 -27
  75. package/dist/plugins/pg-file-snapshot-store.js +1 -190
  76. package/dist/plugins/pg-image-index.d.ts +1 -21
  77. package/dist/plugins/pg-image-index.js +1 -217
  78. package/dist/plugins/pg-outcome-ledger.d.ts +2 -22
  79. package/dist/plugins/pg-outcome-ledger.js +1 -104
  80. package/dist/plugins/pg-resume-anchor-store.d.ts +1 -13
  81. package/dist/plugins/pg-resume-anchor-store.js +1 -22
  82. package/dist/plugins/pg-run-store.d.ts +1 -75
  83. package/dist/plugins/pg-run-store.js +1 -287
  84. package/dist/plugins/pg-session-policy-store.d.ts +1 -11
  85. package/dist/plugins/pg-session-policy-store.js +1 -90
  86. package/dist/plugins/pg-tool-result-store.d.ts +1 -15
  87. package/dist/plugins/pg-tool-result-store.js +1 -52
  88. package/dist/plugins/pg-workflow-journal-store.d.ts +1 -20
  89. package/dist/plugins/pg-workflow-journal-store.js +1 -34
  90. package/dist/plugins/pg-workflow-run-store.d.ts +1 -55
  91. package/dist/plugins/pg-workflow-run-store.js +1 -220
  92. package/dist/plugins/remote-env-host.js +4 -3
  93. package/dist/plugins/resume-anchor-store-sql.d.ts +23 -0
  94. package/dist/plugins/resume-anchor-store-sql.js +38 -0
  95. package/dist/plugins/run-store-sql.d.ts +89 -0
  96. package/dist/plugins/run-store-sql.js +397 -0
  97. package/dist/plugins/session-policy-store-sql.d.ts +22 -0
  98. package/dist/plugins/session-policy-store-sql.js +109 -0
  99. package/dist/plugins/tidb-approval-store.d.ts +1 -39
  100. package/dist/plugins/tidb-approval-store.js +1 -71
  101. package/dist/plugins/tidb-breaker-state.d.ts +1 -17
  102. package/dist/plugins/tidb-breaker-state.js +1 -74
  103. package/dist/plugins/tidb-file-snapshot-store.d.ts +1 -27
  104. package/dist/plugins/tidb-file-snapshot-store.js +1 -192
  105. package/dist/plugins/tidb-image-index.d.ts +1 -19
  106. package/dist/plugins/tidb-image-index.js +1 -149
  107. package/dist/plugins/tidb-outcome-ledger.d.ts +1 -57
  108. package/dist/plugins/tidb-outcome-ledger.js +1 -97
  109. package/dist/plugins/tidb-resume-anchor-store.d.ts +1 -13
  110. package/dist/plugins/tidb-resume-anchor-store.js +1 -22
  111. package/dist/plugins/tidb-run-store.d.ts +1 -75
  112. package/dist/plugins/tidb-run-store.js +1 -306
  113. package/dist/plugins/tidb-session-policy-store.d.ts +1 -11
  114. package/dist/plugins/tidb-session-policy-store.js +1 -86
  115. package/dist/plugins/tidb-tool-result-store.d.ts +1 -13
  116. package/dist/plugins/tidb-tool-result-store.js +1 -38
  117. package/dist/plugins/tidb-workflow-journal-store.d.ts +1 -20
  118. package/dist/plugins/tidb-workflow-journal-store.js +1 -34
  119. package/dist/plugins/tidb-workflow-run-store.d.ts +1 -61
  120. package/dist/plugins/tidb-workflow-run-store.js +1 -231
  121. package/dist/plugins/tool-result-store-sql.d.ts +25 -0
  122. package/dist/plugins/tool-result-store-sql.js +69 -0
  123. package/dist/plugins/workflow-journal-store-sql.d.ts +30 -0
  124. package/dist/plugins/workflow-journal-store-sql.js +53 -0
  125. package/dist/plugins/workflow-run-store-sql.d.ts +85 -0
  126. package/dist/plugins/workflow-run-store-sql.js +299 -0
  127. package/dist/question.js +2 -2
  128. package/dist/run-local.js +1 -1
  129. package/dist/tool-approval.js +2 -2
  130. package/dist/trace/ledger-sink.js +1 -1
  131. package/dist/trace/project.d.ts +1 -0
  132. package/dist/trace/project.js +3 -0
  133. package/package.json +1 -1
package/README.md CHANGED
@@ -147,6 +147,15 @@ The server is configured entirely through environment variables. The most import
147
147
  | `MODEL_IDLE_TIMEOUT_MS` | `300000` | Mid-stream idle timeout (`0` = off) |
148
148
  | `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` (structured JSON logs) |
149
149
 
150
+ **Boolean knobs** take exactly `true` or `false`. Any other value (`1`, `yes`, `TRUE`) is rejected with a
151
+ `config_env_invalid_using_default` warning at boot and the knob keeps its default — so a mistyped switch is
152
+ visible instead of silent. The default is not guessable from the name, so the server prints one
153
+ `config_knob_polarity` line per knob at `LOG_LEVEL=debug`: name → polarity (`opt-in` = default OFF,
154
+ `opt-out` = default ON, `posture` = derived from `REQUIRE_PRINCIPAL`) → effective value → where it came from.
155
+ The table describes the knobs live in *this* configuration — a knob gated on an inactive lane (e.g.
156
+ `K8S_INSECURE_TLS` outside the k8s provider, `E2B_ALLOW_NET` when unset) has no row, which means
157
+ "not live here", not "off".
158
+
150
159
  The full surface — cost/quota ceilings, circuit breaker and failover, multi-model roles, approval
151
160
  gates, observability (Prometheus `/metrics` + optional OTLP), registry control plane, per-lane
152
161
  sandbox settings — is documented in [`USAGE.md`](USAGE.md).
package/README.zh-CN.md CHANGED
@@ -140,6 +140,13 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
140
140
  | `MODEL_IDLE_TIMEOUT_MS` | `300000` | 流中 idle 超时(`0` 关) |
141
141
  | `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error`(结构化 JSON 日志) |
142
142
 
143
+ **布尔旋钮只认 `true` / `false` 两个字面量**。写成 `1`/`yes`/`TRUE` 会在启动时报一条
144
+ `config_env_invalid_using_default` 警告并退回该旋钮的缺省值 —— 打错的开关会被看见,而不是静默失效。
145
+ 缺省值无法从名字推出,所以服务在 `LOG_LEVEL=debug` 下每个旋钮打一行 `config_knob_polarity`:
146
+ 名字 → 极性(`opt-in`=缺省关、`opt-out`=缺省开、`posture`=由 `REQUIRE_PRINCIPAL` 推导)→ 生效值 → 来源。
147
+ 该表描述**本配置下活着的旋钮**——挂在未激活通道上的旋钮(如非 k8s 通道下的 `K8S_INSECURE_TLS`、
148
+ 未设时的 `E2B_ALLOW_NET`)没有行,含义是「在这里不生效」,不是「关」。
149
+
143
150
  完整配置面 —— 成本/配额上限、断路器与 failover、多模型角色、审批门、可观测
144
151
  (Prometheus `/metrics` + 可选 OTLP)、registry 控制面、各沙箱通道细项 ——
145
152
  见 [`USAGE.md`](USAGE.md)。
@@ -221,7 +221,7 @@ export interface ServiceConfig {
221
221
  workflowRunStoreBackend: "auto" | "file" | "memory";
222
222
  workflowOrphanGraceMs: number;
223
223
  schedulerEnabled: boolean;
224
- projectMemoryDisabled: boolean;
224
+ projectMemoryEnabled: boolean;
225
225
  schedulerStorePath?: string;
226
226
  configBootFetchBudgetMs: number;
227
227
  schedulerSessionWakeup: boolean;
package/dist/config.d.ts CHANGED
@@ -13,7 +13,21 @@ export declare function drainConfigNotices(): Array<{
13
13
  }>;
14
14
  export declare function logConfigDiagnostics(logger: {
15
15
  warn: (event: string, fields?: Record<string, unknown>) => void;
16
+ debug?: (event: string, fields?: Record<string, unknown>) => void;
16
17
  }): void;
18
+ type ConfigKnobSource = "env" | "legacy-env" | "posture" | "default";
19
+ export interface ConfigKnobRecord {
20
+ name: string;
21
+ polarity: "opt-in" | "opt-out" | "posture";
22
+ value: boolean;
23
+ source: ConfigKnobSource;
24
+ legacyName?: string;
25
+ }
26
+ export declare function configKnobTable(): ConfigKnobRecord[];
27
+ export declare function boolEnvWithLegacyNegated(name: string, legacyNegatedName: string, def: boolean): boolean;
28
+ export declare const configLkgEnabled: () => boolean;
29
+ export declare const hostBackgroundShellEnabled: () => boolean;
30
+ export declare const hostExecSpoolEnabled: () => boolean;
17
31
  export declare function applyAutoCompactWindow(m: Model, explicit?: number): void;
18
32
  export declare function loadConfig(): ServiceConfig;
19
33
  export declare function resolveBindHost(config: {
package/dist/config.js CHANGED
@@ -82,6 +82,17 @@ export function logConfigDiagnostics(logger) {
82
82
  logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw });
83
83
  for (const n of drainConfigNotices())
84
84
  logger.warn(n.event, n.fields);
85
+ if (logger.debug) {
86
+ for (const k of configKnobTable()) {
87
+ logger.debug("config_knob_polarity", {
88
+ knob: k.name,
89
+ polarity: k.polarity,
90
+ value: k.value,
91
+ source: k.source,
92
+ ...(k.legacyName ? { legacyName: k.legacyName } : {}),
93
+ });
94
+ }
95
+ }
85
96
  }
86
97
  function firstSetEnv(...names) {
87
98
  for (const n of names) {
@@ -102,6 +113,71 @@ function optFinitePositiveEnv(name) {
102
113
  }
103
114
  return n;
104
115
  }
116
+ const CONFIG_KNOBS = new Map();
117
+ const KNOB_DEPRECATIONS_SEEN = new Set();
118
+ export function configKnobTable() {
119
+ return [...CONFIG_KNOBS.values()].sort((a, b) => a.name.localeCompare(b.name));
120
+ }
121
+ function registerKnob(rec) {
122
+ CONFIG_KNOBS.set(rec.name, rec);
123
+ }
124
+ function boolEnv(name, def) {
125
+ const raw = process.env[name];
126
+ let value = def;
127
+ let source = "default";
128
+ if (raw !== undefined && raw !== "") {
129
+ if (raw === "true") {
130
+ value = true;
131
+ source = "env";
132
+ }
133
+ else if (raw === "false") {
134
+ value = false;
135
+ source = "env";
136
+ }
137
+ else if (!CONFIG_KNOBS.has(name)) {
138
+ CONFIG_WARNINGS.push({ env: name, raw });
139
+ }
140
+ }
141
+ registerKnob({ name, polarity: def ? "opt-out" : "opt-in", value, source });
142
+ return value;
143
+ }
144
+ export function boolEnvWithLegacyNegated(name, legacyNegatedName, def) {
145
+ const raw = process.env[name];
146
+ const legacy = process.env[legacyNegatedName];
147
+ let value;
148
+ let source;
149
+ if (raw === "true" || raw === "false") {
150
+ value = raw === "true";
151
+ source = "env";
152
+ }
153
+ else if (raw !== undefined && raw !== "") {
154
+ if (!CONFIG_KNOBS.has(name)) {
155
+ CONFIG_WARNINGS.push({ env: name, raw });
156
+ }
157
+ value = def;
158
+ source = "default";
159
+ }
160
+ else if (legacy !== undefined && legacy !== "") {
161
+ value = legacy !== "true";
162
+ source = "legacy-env";
163
+ if (!KNOB_DEPRECATIONS_SEEN.has(legacyNegatedName)) {
164
+ KNOB_DEPRECATIONS_SEEN.add(legacyNegatedName);
165
+ CONFIG_NOTICES.push({
166
+ event: "config_env_deprecated_negated_name",
167
+ fields: { deprecated: legacyNegatedName, replacement: name, effective: String(value) },
168
+ });
169
+ }
170
+ }
171
+ else {
172
+ value = def;
173
+ source = "default";
174
+ }
175
+ registerKnob({ name, polarity: def ? "opt-out" : "opt-in", value, source, legacyName: legacyNegatedName });
176
+ return value;
177
+ }
178
+ export const configLkgEnabled = () => boolEnvWithLegacyNegated("CONFIG_LKG_ENABLED", "CONFIG_LKG_DISABLED", true);
179
+ export const hostBackgroundShellEnabled = () => boolEnvWithLegacyNegated("HOST_BG_ENABLED", "HOST_BG_DISABLED", true);
180
+ export const hostExecSpoolEnabled = () => boolEnvWithLegacyNegated("HOST_EXEC_SPOOL_ENABLED", "HOST_EXEC_SPOOL_DISABLED", true);
105
181
  function enumEnv(name, fallback, allowed) {
106
182
  const v = (process.env[name] ?? fallback);
107
183
  if (!allowed.includes(v))
@@ -164,15 +240,24 @@ export function applyAutoCompactWindow(m, explicit) {
164
240
  export function loadConfig() {
165
241
  CONFIG_WARNINGS.length = 0;
166
242
  CONFIG_NOTICES.length = 0;
243
+ CONFIG_KNOBS.clear();
244
+ KNOB_DEPRECATIONS_SEEN.clear();
167
245
  const gatewayBaseUrl = env("MODEL_GATEWAY_BASEURL", "http://127.0.0.1:8000/v1");
168
246
  const modelId = env("MODEL_ID", "Qwen3.5-35B");
169
- const singleUserTurnkey = process.env.REQUIRE_PRINCIPAL !== "true";
170
- const postureOn = (envVal, infraReady = true) => envVal === "true" ? true : envVal === "false" ? false : singleUserTurnkey && infraReady;
247
+ const requirePrincipal = boolEnv("REQUIRE_PRINCIPAL", false);
248
+ const singleUserTurnkey = !requirePrincipal;
249
+ const postureOn = (name, infraReady = true) => {
250
+ const envVal = process.env[name];
251
+ const explicit = envVal === "true" ? true : envVal === "false" ? false : undefined;
252
+ const value = explicit ?? (singleUserTurnkey && infraReady);
253
+ registerKnob({ name, polarity: "posture", value, source: explicit === undefined ? "posture" : "env" });
254
+ return value;
255
+ };
171
256
  const dbBackendSet = !!process.env.DB_BACKEND;
172
257
  const sqlSignal = (!!process.env.SESSION_BACKEND && process.env.SESSION_BACKEND !== "memory") ||
173
258
  !!(process.env.TIDB_HOST || process.env.MYSQL_HOST || process.env.PG_HOST);
174
259
  const bareBoot = !dbBackendSet && !sqlSignal;
175
- const multiTenantBareBoot = bareBoot && process.env.REQUIRE_PRINCIPAL === "true";
260
+ const multiTenantBareBoot = bareBoot && requirePrincipal;
176
261
  const dbBackendRaw = enumEnv("DB_BACKEND", bareBoot ? (multiTenantBareBoot ? "memory" : "local") : "mysql", ["mysql", "tidb", "pg", "local", "memory"]);
177
262
  const dbBackend = dbBackendRaw === "tidb" ? "mysql" : dbBackendRaw;
178
263
  if (multiTenantBareBoot) {
@@ -197,7 +282,7 @@ export function loadConfig() {
197
282
  throw new Error(`WORKFLOW_SIZE_GUIDELINE must be small|medium|large|unrestricted, got "${workflowSizeGuidelineRaw}"`);
198
283
  }
199
284
  const workflowSizeGuideline = workflowSizeGuidelineRaw;
200
- const memoryScope = process.env.MEMORY_SCOPE ?? (memoryEngineEnabled && process.env.REQUIRE_PRINCIPAL !== "true" ? "local" : undefined);
285
+ const memoryScope = process.env.MEMORY_SCOPE ?? (memoryEngineEnabled && !requirePrincipal ? "local" : undefined);
201
286
  const memorySyncUrl = process.env.MEMORY_SYNC_URL || undefined;
202
287
  const memorySyncToken = process.env.MEMORY_SYNC_TOKEN || undefined;
203
288
  const memorySyncScopeRaw = process.env.MEMORY_SYNC_SCOPE || undefined;
@@ -320,14 +405,14 @@ export function loadConfig() {
320
405
  ? { model: cur, systemPrompt: CODE_AGENT_PROMPT }
321
406
  : { ...cur, systemPrompt: CODE_AGENT_PROMPT };
322
407
  }
323
- const directApprovalDoor = process.env.DIRECT_APPROVAL_DOOR === "true";
408
+ const directApprovalDoor = boolEnv("DIRECT_APPROVAL_DOOR", false);
324
409
  const principalJwtPubkeys = parsePrincipalJwks(process.env.PRINCIPAL_JWT_PUBKEYS);
325
410
  const principalJwtIss = process.env.PRINCIPAL_JWT_ISS || undefined;
326
411
  const principalJwtAud = process.env.PRINCIPAL_JWT_AUD || undefined;
327
412
  const principalJwtMaxTtlSec = Math.max(0, numEnv("PRINCIPAL_JWT_MAX_TTL_SEC", "120"));
328
413
  const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS);
329
414
  const dgOperators = csv("OPERATOR_PRINCIPALS");
330
- const dgDurable = process.env.DURABLE_APPROVAL === "true";
415
+ const dgDurable = boolEnv("DURABLE_APPROVAL", false);
331
416
  const directDoorActive = directApprovalDoor && dgDurable && dgHmacKeys.length > 0 && principalJwtPubkeys.length > 0 && !!principalJwtIss && !!principalJwtAud;
332
417
  if (directApprovalDoor) {
333
418
  const missing = [];
@@ -343,7 +428,7 @@ export function loadConfig() {
343
428
  missing.push("PRINCIPAL_JWT_AUD");
344
429
  if (dgOperators.length === 0)
345
430
  missing.push("OPERATOR_PRINCIPALS (else isOperator is true-for-all)");
346
- if (process.env.REQUIRE_PRINCIPAL !== "true")
431
+ if (!requirePrincipal)
347
432
  missing.push("REQUIRE_PRINCIPAL=true (a direct door is multi-tenant; without it an absent principal is anonymous and the spoofable header governs owner/quota checks)");
348
433
  if (missing.length) {
349
434
  throw new Error(`DIRECT_APPROVAL_DOOR=true but D-G anchors are missing: ${missing.join(", ")} — refusing to start a half-open direct door (design/80 D-G boot invariant)`);
@@ -356,12 +441,30 @@ export function loadConfig() {
356
441
  .map((s) => [s.slice(0, s.indexOf("=")).trim(), s.slice(s.indexOf("=") + 1).trim()])
357
442
  .filter(([tok, sys]) => tok.length > 0 && sys.length > 0));
358
443
  const bakeRunnerPrincipal = env("BAKE_RUNNER_PRINCIPAL", "system:bake-runner");
359
- if (process.env.IMAGE_BAKES_ENABLED === "true" && dgOperators.length === 0) {
444
+ if (boolEnv("IMAGE_BAKES_ENABLED", false) && dgOperators.length === 0) {
360
445
  throw new Error("IMAGE_BAKES_ENABLED=true but OPERATOR_PRINCIPALS is empty — the bake door runs build.sh on the privileged build host; an empty operator set makes isOperator() true-for-all (world-writable RCE). Set OPERATOR_PRINCIPALS or disable bakes (IMAGE-API-DESIGN.md §P2.4a boot invariant)");
361
446
  }
362
- if (process.env.IMAGE_BAKES_ENABLED === "true" && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
447
+ if (boolEnv("IMAGE_BAKES_ENABLED", false) && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
363
448
  throw new Error(`IMAGE_BAKES_ENABLED=true but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${bakeRunnerPrincipal}' — the bake-runner authenticates by its credential (token-derived source), so a '<BAKE_RUNNER_TOKEN>=${bakeRunnerPrincipal}' SERVICE_AUTH_TOKENS entry is required or claim/ingest/heartbeat all 403 (IMAGE-API-DESIGN.md §P2.12 boot invariant)`);
364
449
  }
450
+ const projectMemoryEnabled = boolEnvWithLegacyNegated("PROJECT_MEMORY_ENABLED", "PROJECT_MEMORY_DISABLED", true);
451
+ configLkgEnabled();
452
+ hostBackgroundShellEnabled();
453
+ hostExecSpoolEnabled();
454
+ const lspHostEnabled = (() => {
455
+ const explicitHost = process.env.LSP_HOST_ENABLED;
456
+ if (explicitHost === undefined || explicitHost === "") {
457
+ if (process.env.LSP_ENABLED === "false") {
458
+ CONFIG_NOTICES.push({
459
+ event: "config_env_deprecated_lane_alias",
460
+ fields: { deprecated: "LSP_ENABLED=false", replacement: "LSP_HOST_ENABLED=false", lane: "host", effective: "false" },
461
+ });
462
+ registerKnob({ name: "LSP_HOST_ENABLED", polarity: "opt-out", value: false, source: "legacy-env", legacyName: "LSP_ENABLED" });
463
+ return false;
464
+ }
465
+ }
466
+ return boolEnv("LSP_HOST_ENABLED", true);
467
+ })();
365
468
  return {
366
469
  port: Number(env("PORT", "8090")),
367
470
  gatewayBaseUrl,
@@ -381,7 +484,7 @@ export function loadConfig() {
381
484
  connectTimeoutMs: Number(env("MODEL_CONNECT_TIMEOUT_MS", "30000")),
382
485
  firstTokenTimeoutMs: Number(env("MODEL_FIRST_TOKEN_TIMEOUT_MS", "120000")),
383
486
  idleTimeoutMs: Number(env("MODEL_IDLE_TIMEOUT_MS", "300000")),
384
- circuitBreaker: process.env.MODEL_CIRCUIT_BREAKER === "true",
487
+ circuitBreaker: boolEnv("MODEL_CIRCUIT_BREAKER", false),
385
488
  failureThreshold: Number(env("MODEL_CB_FAILURE_THRESHOLD", "5")),
386
489
  cooldownMs: Number(env("MODEL_CB_COOLDOWN_MS", "30000")),
387
490
  },
@@ -410,7 +513,7 @@ export function loadConfig() {
410
513
  ...(process.env.E2B_TEMPLATE ? { template: process.env.E2B_TEMPLATE } : {}),
411
514
  ...(process.env.E2B_TIMEOUT_MS ? { timeoutMs: Number(process.env.E2B_TIMEOUT_MS) } : {}),
412
515
  ...(process.env.E2B_LIVENESS_MS ? { livenessMs: Number(process.env.E2B_LIVENESS_MS) } : {}),
413
- ...(process.env.E2B_ALLOW_NET ? { allowInternetAccess: process.env.E2B_ALLOW_NET !== "false" } : {}),
516
+ ...(process.env.E2B_ALLOW_NET ? { allowInternetAccess: boolEnv("E2B_ALLOW_NET", true) } : {}),
414
517
  ...(process.env.E2B_SANDBOX_ENV ? { sandboxEnv: resolveEnvForward(process.env.E2B_SANDBOX_ENV) } : {}),
415
518
  ...(process.env.E2B_MOUNT_PATH ? { mountPath: process.env.E2B_MOUNT_PATH } : {}),
416
519
  }
@@ -422,7 +525,7 @@ export function loadConfig() {
422
525
  ...(process.env.K8S_API_URL ? { apiUrl: process.env.K8S_API_URL } : {}),
423
526
  ...(process.env.K8S_TOKEN ? { token: process.env.K8S_TOKEN } : {}),
424
527
  ...(process.env.K8S_CA_CERT_B64 ? { caCert: Buffer.from(process.env.K8S_CA_CERT_B64, "base64").toString("utf8") } : {}),
425
- ...(process.env.K8S_INSECURE_TLS === "true" ? { insecureTls: true } : {}),
528
+ ...(boolEnv("K8S_INSECURE_TLS", false) ? { insecureTls: true } : {}),
426
529
  ...(process.env.K8S_NAMESPACE ? { namespace: process.env.K8S_NAMESPACE } : {}),
427
530
  ...(process.env.K8S_RUNTIME_CLASS != null ? { runtimeClass: process.env.K8S_RUNTIME_CLASS } : {}),
428
531
  ...(process.env.K8S_MOUNT_PATH ? { mountPath: process.env.K8S_MOUNT_PATH } : {}),
@@ -479,7 +582,7 @@ export function loadConfig() {
479
582
  ...(process.env.DOCKER_MEMORY ? { memory: process.env.DOCKER_MEMORY } : {}),
480
583
  ...(process.env.DOCKER_CPUS ? { cpus: Number(process.env.DOCKER_CPUS) } : {}),
481
584
  ...(process.env.DOCKER_PIDS_LIMIT ? { pidsLimit: Number(process.env.DOCKER_PIDS_LIMIT) } : {}),
482
- ...(process.env.DOCKER_DROP_CAPS === "true" ? { dropAllCaps: true } : {}),
585
+ ...(boolEnv("DOCKER_DROP_CAPS", false) ? { dropAllCaps: true } : {}),
483
586
  ...(process.env.DOCKER_NETWORK
484
587
  ? { network: enumEnv("DOCKER_NETWORK", "bridge", ["none", "bridge", "host"]) }
485
588
  : {}),
@@ -490,7 +593,7 @@ export function loadConfig() {
490
593
  process.env.CONFIG_PROVIDER === "local"
491
594
  ? { provider: "host" }
492
595
  : undefined,
493
- worktreeIsolation: process.env.WORKTREE_ISOLATION_ENABLED === "true" && process.env.WORKTREE_REPO_ROOT
596
+ worktreeIsolation: boolEnv("WORKTREE_ISOLATION_ENABLED", false) && process.env.WORKTREE_REPO_ROOT
494
597
  ? {
495
598
  repoRoot: process.env.WORKTREE_REPO_ROOT,
496
599
  ...(process.env.WORKTREE_ALLOWED_ROOTS
@@ -537,7 +640,7 @@ export function loadConfig() {
537
640
  }
538
641
  : undefined,
539
642
  snapshotBlobSqlMaxBytes: optFinitePositiveEnv("SNAPSHOT_BLOB_SQL_MAX_BYTES"),
540
- snapshotBlobAllowSql: process.env.SNAPSHOT_BLOB_ALLOW_SQL_BYTES === "true",
643
+ snapshotBlobAllowSql: boolEnv("SNAPSHOT_BLOB_ALLOW_SQL_BYTES", false),
541
644
  pluginsAllowHosts: (process.env.PLUGINS_ALLOW_HOSTS ?? "github.com").split(",").map((h) => h.trim()).filter(Boolean),
542
645
  bindHost: process.env.BIND_HOST || process.env.HOST || undefined,
543
646
  attachmentOrphanGraceMs: ((v) => (v !== undefined && Number.isFinite(v) && v >= 0 ? v : 3_600_000))(process.env.ATTACHMENT_ORPHAN_GRACE_MS !== undefined ? Number(process.env.ATTACHMENT_ORPHAN_GRACE_MS) : undefined),
@@ -560,7 +663,7 @@ export function loadConfig() {
560
663
  : undefined,
561
664
  authToken: process.env.SERVICE_AUTH_TOKEN,
562
665
  authTokens,
563
- allowUnauthedWrites: process.env.ALLOW_UNAUTHED_WRITES === "true",
666
+ allowUnauthedWrites: boolEnv("ALLOW_UNAUTHED_WRITES", false),
564
667
  metricsToken: process.env.METRICS_TOKEN || undefined,
565
668
  traceToken: process.env.TRACE_TOKEN || undefined,
566
669
  corsOrigins: (process.env.CORS_ORIGIN ?? "").split(",").map((s) => s.trim()).filter(Boolean),
@@ -570,15 +673,15 @@ export function loadConfig() {
570
673
  : {}),
571
674
  attachmentUnboundTtlMs: Math.max(60_000, numEnv("ATTACHMENT_UNBOUND_TTL_MS", String(24 * 3600 * 1000))),
572
675
  principalHeader: headerNameEnv("PRINCIPAL_HEADER", "x-agent-principal"),
573
- requirePrincipal: process.env.REQUIRE_PRINCIPAL === "true",
676
+ requirePrincipal,
574
677
  autonomy: parseAutonomy(process.env.AUTONOMY),
575
678
  approvalRequire: csv("APPROVAL_REQUIRE"),
576
679
  operatorPrincipals: csv("OPERATOR_PRINCIPALS"),
577
680
  cascadeLadder: csv("MODEL_CASCADE_LADDER"),
578
681
  approvalDeny: csv("APPROVAL_DENY"),
579
682
  approvalPollMs: Number(env("APPROVAL_POLL_MS", "1500")),
580
- durableApproval: process.env.DURABLE_APPROVAL === "true",
581
- resourceSuspend: process.env.RESOURCE_SUSPEND === "true",
683
+ durableApproval: boolEnv("DURABLE_APPROVAL", false),
684
+ resourceSuspend: boolEnv("RESOURCE_SUSPEND", false),
582
685
  resourceSuspendTtlSec: Math.max(0, numEnv("RESOURCE_SUSPEND_TTL_SEC", "0")),
583
686
  approvalAutoBudget: Math.min(10000, Math.max(0, numEnv("APPROVAL_AUTO_BUDGET", "0"))),
584
687
  approvalNeverAuto: csv("APPROVAL_NEVER_AUTO"),
@@ -594,20 +697,20 @@ export function loadConfig() {
594
697
  sandboxSecMicroUsd: Math.max(0, numEnv("INFRA_COST_SANDBOX_SEC", "0")),
595
698
  egressGbMicroUsd: Math.max(0, numEnv("INFRA_COST_EGRESS_GB", "0")),
596
699
  },
597
- leaderEnabled: process.env.LEADER_ENABLED === "true",
598
- leaderFanoutEnabled: process.env.LEADER_FANOUT_ENABLED !== "false",
599
- routerEnabled: process.env.ROUTER_ENABLED === "true",
600
- selfOrchestrationEnabled: postureOn(process.env.SELF_ORCHESTRATION_ENABLED),
601
- forkEnabled: postureOn(process.env.FORK_ENABLED),
602
- experimentalObserverAgents: process.env.EXPERIMENTAL_OBSERVER_AGENTS === "true",
603
- schedulerEnabled: postureOn(process.env.SCHEDULER_ENABLED),
604
- projectMemoryDisabled: process.env.PROJECT_MEMORY_DISABLED === "true",
605
- planModeEnabled: process.env.PLAN_MODE_ENABLED !== "false",
700
+ leaderEnabled: boolEnv("LEADER_ENABLED", false),
701
+ leaderFanoutEnabled: boolEnv("LEADER_FANOUT_ENABLED", true),
702
+ routerEnabled: boolEnv("ROUTER_ENABLED", false),
703
+ selfOrchestrationEnabled: postureOn("SELF_ORCHESTRATION_ENABLED"),
704
+ forkEnabled: postureOn("FORK_ENABLED"),
705
+ experimentalObserverAgents: boolEnv("EXPERIMENTAL_OBSERVER_AGENTS", false),
706
+ schedulerEnabled: postureOn("SCHEDULER_ENABLED"),
707
+ projectMemoryEnabled,
708
+ planModeEnabled: boolEnv("PLAN_MODE_ENABLED", true),
606
709
  ...(process.env.SCHEDULER_STORE_PATH ? { schedulerStorePath: process.env.SCHEDULER_STORE_PATH } : {}),
607
710
  configBootFetchBudgetMs: Math.max(0, numEnv("CONFIG_BOOT_FETCH_BUDGET_MS", "1500")),
608
- schedulerSessionWakeup: process.env.SCHEDULER_SESSION_WAKEUP !== "false",
711
+ schedulerSessionWakeup: boolEnv("SCHEDULER_SESSION_WAKEUP", true),
609
712
  selfOrchestrationModels: csv("SELF_ORCHESTRATION_MODELS"),
610
- selfOrchestrationWorkerIsolation: process.env.SELF_ORCHESTRATION_WORKER_ISOLATION === "true",
713
+ selfOrchestrationWorkerIsolation: boolEnv("SELF_ORCHESTRATION_WORKER_ISOLATION", false),
611
714
  workflowRunStoreBackend: enumEnv("WORKFLOW_RUN_STORE", "auto", ["auto", "file", "memory"]),
612
715
  workflowOrphanGraceMs: Math.max(60_000, numEnv("WORKFLOW_ORPHAN_GRACE_MS", String(24 * 60 * 60 * 1000))),
613
716
  workflowJournalRetentionMs: Math.max(60_000, numEnv("WORKFLOW_JOURNAL_RETENTION_MS", String(7 * 24 * 60 * 60 * 1000))),
@@ -620,9 +723,9 @@ export function loadConfig() {
620
723
  ...(process.env.SANDBOX_PKG_SOURCE !== "none"
621
724
  ? { sandboxPkgSource: process.env.SANDBOX_PKG_SOURCE || "global" }
622
725
  : {}),
623
- sessionAutoTitle: process.env.SESSION_AUTO_TITLE !== "false",
624
- selectEnvironmentTool: process.env.SELECT_ENVIRONMENT_TOOL !== "false",
625
- envFactsEnabled: process.env.SANDBOX_ENV_FACTS !== "false",
726
+ sessionAutoTitle: boolEnv("SESSION_AUTO_TITLE", true),
727
+ selectEnvironmentTool: boolEnv("SELECT_ENVIRONMENT_TOOL", true),
728
+ envFactsEnabled: boolEnv("SANDBOX_ENV_FACTS", true),
626
729
  sensitiveWritePatterns: ((raw) => {
627
730
  if (raw === undefined)
628
731
  return [...RECOMMENDED_SENSITIVE_PATTERNS];
@@ -634,13 +737,13 @@ export function loadConfig() {
634
737
  ...(process.env.MANUAL_MODE_SHELL_GATE === "always" || process.env.MANUAL_MODE_SHELL_GATE === "classify"
635
738
  ? { manualModeShellGate: process.env.MANUAL_MODE_SHELL_GATE }
636
739
  : {}),
637
- toolDeferLongtail: process.env.TOOL_DEFER_LONGTAIL === "true",
638
- lspEnabled: process.env.LSP_ENABLED === "true",
639
- lspHostEnabled: process.env.LSP_ENABLED !== "false",
740
+ toolDeferLongtail: boolEnv("TOOL_DEFER_LONGTAIL", false),
741
+ lspEnabled: boolEnv("LSP_ENABLED", false),
742
+ lspHostEnabled,
640
743
  drainGraceMs: Math.max(10_000, numEnv("DRAIN_GRACE_MS", String(10 * 60 * 1000))),
641
744
  sighupIdleGraceMs: Math.max(5_000, numEnv("SIGHUP_IDLE_GRACE_MS", String(120_000))),
642
745
  mcpElicitation: {
643
- enabled: process.env.MCP_ELICITATION_ENABLED === "true",
746
+ enabled: boolEnv("MCP_ELICITATION_ENABLED", false),
644
747
  throttle: {
645
748
  maxConcurrentPerRun: numEnvBounded("MCP_ELICITATION_MAX_CONCURRENT", String(DEFAULT_ELICITATION_THROTTLE.maxConcurrentPerRun), 1, 64),
646
749
  maxTotalPerRun: numEnvBounded("MCP_ELICITATION_MAX_TOTAL", String(DEFAULT_ELICITATION_THROTTLE.maxTotalPerRun), 1, 10_000),
@@ -648,11 +751,11 @@ export function loadConfig() {
648
751
  ttlMs: numEnvBounded("MCP_ELICITATION_TTL_MS", String(DEFAULT_ELICITATION_THROTTLE.ttlMs), 1_000, 3_600_000),
649
752
  },
650
753
  },
651
- askQuestionEnabled: postureOn(process.env.ASK_QUESTION_ENABLED),
652
- toolApprovalEnabled: postureOn(process.env.TOOL_APPROVAL_ENABLED),
653
- workflowAgentsReadOnly: process.env.WORKFLOW_AGENTS_READONLY === "true",
754
+ askQuestionEnabled: postureOn("ASK_QUESTION_ENABLED"),
755
+ toolApprovalEnabled: postureOn("TOOL_APPROVAL_ENABLED"),
756
+ workflowAgentsReadOnly: boolEnv("WORKFLOW_AGENTS_READONLY", false),
654
757
  imageBakes: {
655
- enabled: process.env.IMAGE_BAKES_ENABLED === "true",
758
+ enabled: boolEnv("IMAGE_BAKES_ENABLED", false),
656
759
  runnerPrincipal: bakeRunnerPrincipal,
657
760
  registry: process.env.BAKE_IMAGE_REGISTRY || null,
658
761
  cacheBase: process.env.BAKE_CACHE_BASE || null,
@@ -662,8 +765,8 @@ export function loadConfig() {
662
765
  submitRateMax: Math.max(0, numEnv("BAKE_SUBMIT_RATE_MAX", "10")),
663
766
  submitRateWindowSec: Math.max(1, numEnv("BAKE_SUBMIT_RATE_WINDOW_SEC", "60")),
664
767
  },
665
- toolTrace: process.env.TOOL_TRACE === "true",
666
- traceThinking: process.env.TRACE_THINKING !== "false",
768
+ toolTrace: boolEnv("TOOL_TRACE", false),
769
+ traceThinking: boolEnv("TRACE_THINKING", true),
667
770
  logLevel: env("LOG_LEVEL", "info"),
668
771
  rateLimitPerMin: numEnv("RATE_LIMIT_RPM", "0"),
669
772
  maxTaskCostUsd: numEnv("MAX_TASK_COST_USD", "0"),
@@ -674,11 +777,11 @@ export function loadConfig() {
674
777
  ? {
675
778
  to: env("MODEL_DEGRADE_TO"),
676
779
  atCostFraction: Number(env("MODEL_DEGRADE_AT_COST_FRACTION", "0.7")),
677
- reactive: process.env.MODEL_DEGRADE_REACTIVE === "true",
780
+ reactive: boolEnv("MODEL_DEGRADE_REACTIVE", false),
678
781
  downgradeOn: csv("MODEL_DEGRADE_ON").length
679
782
  ? csv("MODEL_DEGRADE_ON")
680
783
  : undefined,
681
- toSupportsImages: process.env.MODEL_DEGRADE_TO_VISION === "true",
784
+ toSupportsImages: boolEnv("MODEL_DEGRADE_TO_VISION", false),
682
785
  }
683
786
  : undefined,
684
787
  otel: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
@@ -129,10 +129,10 @@ export class ElicitationCoordinator {
129
129
  respond(id, principal, body) {
130
130
  const parsed = parseElicitationResponse(body);
131
131
  if (!parsed.ok)
132
- return { status: 400, body: { error: parsed.error } };
132
+ return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape" } };
133
133
  const entry = this.pending.get(id);
134
134
  if (!entry || (entry.owner !== null && entry.owner !== principal)) {
135
- return { status: 404, body: { error: "no pending elicitation for this id (answered, expired, or not on this replica)" } };
135
+ return { status: 404, body: { error: "no pending elicitation for this id (answered, expired, or not on this replica)", errorCode: "elicitation.not_pending" } };
136
136
  }
137
137
  entry.settle(parsed.value);
138
138
  return { status: 200, body: { elicitationId: id, delivery: "applied", action: parsed.value.action } };
@@ -9,4 +9,5 @@ export declare class IdempotencyCache<T> {
9
9
  private live;
10
10
  private maybeSweep;
11
11
  }
12
+ export declare function scopedIdempotencyKey(raw: string | undefined, source: string | null, principal: string | undefined): string | undefined;
12
13
  //# sourceMappingURL=idempotency.d.ts.map
@@ -46,4 +46,7 @@ export class IdempotencyCache {
46
46
  this.entries.delete(k);
47
47
  }
48
48
  }
49
+ export function scopedIdempotencyKey(raw, source, principal) {
50
+ return raw ? `${source ?? ""}\x1f${principal ?? ""}\x1f${raw}` : undefined;
51
+ }
49
52
  //# sourceMappingURL=idempotency.js.map
@@ -0,0 +1,12 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import type { ServiceConfig } from "../config-types.js";
3
+ export declare function headerStr(v: string | string[] | undefined): string | undefined;
4
+ export declare function safeEqual(a: string, b: string): boolean;
5
+ export declare function authorized(req: IncomingMessage, token: string): boolean;
6
+ export declare function systemFor(req: IncomingMessage, config: ServiceConfig): string | null | undefined;
7
+ export declare function bearerPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean;
8
+ export declare function gatedPrincipal(req: IncomingMessage, config: ServiceConfig): string | undefined;
9
+ export declare function explicitOperatorOk(principal: string | undefined, operatorPrincipals: readonly string[]): boolean;
10
+ export declare function isOperator(principal: string | undefined, operatorPrincipals: readonly string[]): boolean;
11
+ export declare function explicitOperator(principal: string | undefined, operatorPrincipals: readonly string[]): boolean;
12
+ //# sourceMappingURL=principal-gate.d.ts.map
@@ -0,0 +1,52 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { verifiedPrincipal, ssoVerifiedPrincipal } from "../security.js";
3
+ export function headerStr(v) {
4
+ return Array.isArray(v) ? v[0] : v;
5
+ }
6
+ export function safeEqual(a, b) {
7
+ const ab = Buffer.from(a, "utf8");
8
+ const bb = Buffer.from(b, "utf8");
9
+ return ab.length === bb.length && timingSafeEqual(ab, bb);
10
+ }
11
+ export function authorized(req, token) {
12
+ const h = req.headers.authorization ?? "";
13
+ return safeEqual(h, `Bearer ${token}`) || safeEqual(h, `token ${token}`);
14
+ }
15
+ export function systemFor(req, config) {
16
+ for (const [tok, system] of Object.entries(config.authTokens ?? {})) {
17
+ if (authorized(req, tok))
18
+ return system;
19
+ }
20
+ if (config.authToken && authorized(req, config.authToken))
21
+ return null;
22
+ return undefined;
23
+ }
24
+ export function bearerPresentedButUnverified(req, config) {
25
+ const h = req.headers.authorization;
26
+ if (typeof h !== "string" || !/^(Bearer|token) /i.test(h))
27
+ return false;
28
+ if (ssoVerifiedPrincipal(req))
29
+ return false;
30
+ if (systemFor(req, config) !== undefined)
31
+ return false;
32
+ if (config.traceToken && authorized(req, config.traceToken))
33
+ return false;
34
+ return true;
35
+ }
36
+ export function gatedPrincipal(req, config) {
37
+ if (bearerPresentedButUnverified(req, config))
38
+ return undefined;
39
+ return verifiedPrincipal(req, config);
40
+ }
41
+ export function explicitOperatorOk(principal, operatorPrincipals) {
42
+ return operatorPrincipals.length > 0 && principal !== undefined && operatorPrincipals.includes(principal);
43
+ }
44
+ export function isOperator(principal, operatorPrincipals) {
45
+ if (operatorPrincipals.length === 0)
46
+ return true;
47
+ return principal !== undefined && operatorPrincipals.includes(principal);
48
+ }
49
+ export function explicitOperator(principal, operatorPrincipals) {
50
+ return operatorPrincipals.length > 0 && principal !== undefined && operatorPrincipals.includes(principal);
51
+ }
52
+ //# sourceMappingURL=principal-gate.js.map
@@ -0,0 +1,116 @@
1
+ import type { IncomingMessage, ServerResponse } from "node:http";
2
+ import type { TaskStream, TaskSpec, TaskResult, QuestionAnswer, CheckpointToken, ResumeOutcome } from "@sema-agent/core";
3
+ import type { FlatServiceDeps, RequestAuth } from "./server.js";
4
+ import type { TaskRequestBody, DecideBinding } from "./wire-types.js";
5
+ import type { IdempotencyCache } from "./idempotency.js";
6
+ import type { ImagesLocal } from "./routes/images.js";
7
+ import type { SessionsLocal } from "./routes/sessions.js";
8
+ import type { SessionSyncLocal } from "./routes/session-sync.js";
9
+ export interface RunRegistry {
10
+ idemCache: IdempotencyCache<{
11
+ status: number;
12
+ body: unknown;
13
+ }>;
14
+ inflightRuns: Map<string, AbortController>;
15
+ preemptableRuns: Map<string, AbortController>;
16
+ cancelledViaVerb: Set<string>;
17
+ steerableRuns: Map<string, TaskStream>;
18
+ wakeParkMints: Map<string, Promise<void>>;
19
+ counters: {
20
+ uncountedBillableInflight: number;
21
+ admittedInflight: number;
22
+ };
23
+ }
24
+ export interface RouteHelpers {
25
+ readJson(req: IncomingMessage): Promise<unknown>;
26
+ readRawBody(req: IncomingMessage, max: number): Promise<Buffer>;
27
+ rateLimited(req: IncomingMessage, res: ServerResponse): boolean;
28
+ quotaExceeded(req: IncomingMessage, res: ServerResponse): boolean;
29
+ leaseDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
30
+ safeDecode(seg: string): string | null;
31
+ isFleetWide(req: IncomingMessage): boolean;
32
+ runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null): boolean;
33
+ sessionOwnerScope(req: IncomingMessage): {
34
+ fleetWide: boolean;
35
+ gateOwner: string | null;
36
+ };
37
+ sessionOwnerScopeForWrite(req: IncomingMessage): {
38
+ fleetWide: boolean;
39
+ gateOwner: string | null;
40
+ };
41
+ runSessionAcceptOk(req: IncomingMessage, res: ServerResponse, run: {
42
+ sessionId: string | null;
43
+ }, face: string, notFoundError?: string): boolean;
44
+ }
45
+ export interface RouteLocals {
46
+ images: ImagesLocal;
47
+ sessions: SessionsLocal;
48
+ sessionSync: SessionSyncLocal;
49
+ }
50
+ export interface RouteRequestState {
51
+ streamTaskId: string | undefined;
52
+ streamDetached: boolean;
53
+ source: string | null;
54
+ }
55
+ export interface RouteLegs {
56
+ prepareSpec(req: IncomingMessage, res: ServerResponse): Promise<{
57
+ spec: TaskSpec;
58
+ auth?: RequestAuth;
59
+ verify?: {
60
+ maxRounds: number;
61
+ costCeilingMicroUsd?: number;
62
+ };
63
+ cascade?: boolean;
64
+ jobId?: string;
65
+ body: TaskRequestBody;
66
+ } | null>;
67
+ finalizeTaskResult(result: TaskResult, principal: string | undefined, objective: string, sessionId: string | undefined): void;
68
+ resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding, onResumeCommitted?: (overrideSessionId?: string) => Promise<void>): Promise<{
69
+ status: number;
70
+ body: object;
71
+ }>;
72
+ driveResumeIntoRunLog(args: {
73
+ token: CheckpointToken;
74
+ sessionId: string;
75
+ principal: string | undefined;
76
+ fleetScope: string;
77
+ taskConfig: Omit<TaskSpec, "objective" | "sessionId">;
78
+ resumeObjective: string;
79
+ outcome: ResumeOutcome;
80
+ verifyRounds: {
81
+ maxRounds: number;
82
+ costCeilingMicroUsd?: number;
83
+ } | undefined;
84
+ onResumeCommitted?: () => Promise<void>;
85
+ }): Promise<{
86
+ status: number;
87
+ body: object;
88
+ }>;
89
+ resumePreempted(sessionId: string, req: IncomingMessage | undefined): Promise<{
90
+ status: number;
91
+ body: object;
92
+ }>;
93
+ resumeWake(sessionId: string, message: string | undefined, caller: string | undefined, req: IncomingMessage | undefined): Promise<{
94
+ status: number;
95
+ body: object;
96
+ }>;
97
+ resumePlanReview(sessionId: string, decision: "approve" | "edit" | "reject", editedPlan: string | undefined, reason: string | undefined, req: IncomingMessage | undefined): Promise<{
98
+ status: number;
99
+ body: object;
100
+ }>;
101
+ }
102
+ export interface RouteCtxBase {
103
+ deps: FlatServiceDeps;
104
+ registry: RunRegistry;
105
+ helpers: RouteHelpers;
106
+ local: RouteLocals;
107
+ legs: RouteLegs;
108
+ }
109
+ export interface RouteCtx extends RouteCtxBase {
110
+ req: RouteRequestState;
111
+ }
112
+ export type RouteHandler = (req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx) => Promise<boolean>;
113
+ export interface RouteMiss {
114
+ fell: boolean;
115
+ }
116
+ //# sourceMappingURL=route-ctx.d.ts.map