@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.
- package/README.md +9 -0
- package/README.zh-CN.md +7 -0
- package/dist/config-types.d.ts +1 -1
- package/dist/config.d.ts +14 -0
- package/dist/config.js +148 -45
- package/dist/elicitation.js +2 -2
- package/dist/http/idempotency.d.ts +1 -0
- package/dist/http/idempotency.js +3 -0
- package/dist/http/principal-gate.d.ts +12 -0
- package/dist/http/principal-gate.js +52 -0
- package/dist/http/route-ctx.d.ts +116 -0
- package/dist/http/route-ctx.js +2 -0
- package/dist/http/routes/approvals-assistant.d.ts +11 -0
- package/dist/http/routes/approvals-assistant.js +530 -0
- package/dist/http/routes/attachments.d.ts +4 -0
- package/dist/http/routes/attachments.js +106 -0
- package/dist/http/routes/capabilities.d.ts +4 -0
- package/dist/http/routes/capabilities.js +111 -0
- package/dist/http/routes/fleet.d.ts +4 -0
- package/dist/http/routes/fleet.js +147 -0
- package/dist/http/routes/images.d.ts +20 -0
- package/dist/http/routes/images.js +572 -0
- package/dist/http/routes/leader.d.ts +4 -0
- package/dist/http/routes/leader.js +48 -0
- package/dist/http/routes/memory-policy.d.ts +4 -0
- package/dist/http/routes/memory-policy.js +105 -0
- package/dist/http/routes/notify-wake.d.ts +4 -0
- package/dist/http/routes/notify-wake.js +133 -0
- package/dist/http/routes/observability.d.ts +4 -0
- package/dist/http/routes/observability.js +63 -0
- package/dist/http/routes/runs.d.ts +19 -0
- package/dist/http/routes/runs.js +967 -0
- package/dist/http/routes/session-sync.d.ts +36 -0
- package/dist/http/routes/session-sync.js +513 -0
- package/dist/http/routes/sessions-list.d.ts +4 -0
- package/dist/http/routes/sessions-list.js +117 -0
- package/dist/http/routes/sessions.d.ts +17 -0
- package/dist/http/routes/sessions.js +746 -0
- package/dist/http/routes/side-query.d.ts +4 -0
- package/dist/http/routes/side-query.js +88 -0
- package/dist/http/routes/tasks.d.ts +4 -0
- package/dist/http/routes/tasks.js +632 -0
- package/dist/http/routes/trace-usage.d.ts +4 -0
- package/dist/http/routes/trace-usage.js +239 -0
- package/dist/http/routes/workflows.d.ts +5 -0
- package/dist/http/routes/workflows.js +337 -0
- package/dist/http/run-meta.d.ts +11 -0
- package/dist/http/run-meta.js +16 -0
- package/dist/http/send.d.ts +7 -0
- package/dist/http/send.js +34 -0
- package/dist/http/server.d.ts +12 -12
- package/dist/http/server.js +289 -5502
- package/dist/http/sse-log.d.ts +25 -0
- package/dist/http/sse-log.js +59 -0
- package/dist/http/wire-types.d.ts +6 -0
- package/dist/http/workspace-content.d.ts +3 -0
- package/dist/http/workspace-content.js +20 -0
- package/dist/leader/endpoint.js +4 -4
- package/dist/main.js +5 -5
- package/dist/plugins/approval-store-sql.d.ts +52 -0
- package/dist/plugins/approval-store-sql.js +119 -0
- package/dist/plugins/breaker-state-sql.d.ts +29 -0
- package/dist/plugins/breaker-state-sql.js +104 -0
- package/dist/plugins/file-snapshot-store-sql.d.ts +38 -0
- package/dist/plugins/file-snapshot-store-sql.js +210 -0
- package/dist/plugins/image-index-sql.d.ts +33 -0
- package/dist/plugins/image-index-sql.js +328 -0
- package/dist/plugins/outcome-ledger-sql.d.ts +70 -0
- package/dist/plugins/outcome-ledger-sql.js +158 -0
- package/dist/plugins/pg-approval-store.d.ts +1 -27
- package/dist/plugins/pg-approval-store.js +1 -83
- package/dist/plugins/pg-breaker-state.d.ts +1 -19
- package/dist/plugins/pg-breaker-state.js +1 -88
- package/dist/plugins/pg-file-snapshot-store.d.ts +1 -27
- package/dist/plugins/pg-file-snapshot-store.js +1 -190
- package/dist/plugins/pg-image-index.d.ts +1 -21
- package/dist/plugins/pg-image-index.js +1 -217
- package/dist/plugins/pg-outcome-ledger.d.ts +2 -22
- package/dist/plugins/pg-outcome-ledger.js +1 -104
- package/dist/plugins/pg-resume-anchor-store.d.ts +1 -13
- package/dist/plugins/pg-resume-anchor-store.js +1 -22
- package/dist/plugins/pg-run-store.d.ts +1 -75
- package/dist/plugins/pg-run-store.js +1 -287
- package/dist/plugins/pg-session-policy-store.d.ts +1 -11
- package/dist/plugins/pg-session-policy-store.js +1 -90
- package/dist/plugins/pg-tool-result-store.d.ts +1 -15
- package/dist/plugins/pg-tool-result-store.js +1 -52
- package/dist/plugins/pg-workflow-journal-store.d.ts +1 -20
- package/dist/plugins/pg-workflow-journal-store.js +1 -34
- package/dist/plugins/pg-workflow-run-store.d.ts +1 -55
- package/dist/plugins/pg-workflow-run-store.js +1 -220
- package/dist/plugins/remote-env-host.js +4 -3
- package/dist/plugins/resume-anchor-store-sql.d.ts +23 -0
- package/dist/plugins/resume-anchor-store-sql.js +38 -0
- package/dist/plugins/run-store-sql.d.ts +89 -0
- package/dist/plugins/run-store-sql.js +397 -0
- package/dist/plugins/session-policy-store-sql.d.ts +22 -0
- package/dist/plugins/session-policy-store-sql.js +109 -0
- package/dist/plugins/tidb-approval-store.d.ts +1 -39
- package/dist/plugins/tidb-approval-store.js +1 -71
- package/dist/plugins/tidb-breaker-state.d.ts +1 -17
- package/dist/plugins/tidb-breaker-state.js +1 -74
- package/dist/plugins/tidb-file-snapshot-store.d.ts +1 -27
- package/dist/plugins/tidb-file-snapshot-store.js +1 -192
- package/dist/plugins/tidb-image-index.d.ts +1 -19
- package/dist/plugins/tidb-image-index.js +1 -149
- package/dist/plugins/tidb-outcome-ledger.d.ts +1 -57
- package/dist/plugins/tidb-outcome-ledger.js +1 -97
- package/dist/plugins/tidb-resume-anchor-store.d.ts +1 -13
- package/dist/plugins/tidb-resume-anchor-store.js +1 -22
- package/dist/plugins/tidb-run-store.d.ts +1 -75
- package/dist/plugins/tidb-run-store.js +1 -306
- package/dist/plugins/tidb-session-policy-store.d.ts +1 -11
- package/dist/plugins/tidb-session-policy-store.js +1 -86
- package/dist/plugins/tidb-tool-result-store.d.ts +1 -13
- package/dist/plugins/tidb-tool-result-store.js +1 -38
- package/dist/plugins/tidb-workflow-journal-store.d.ts +1 -20
- package/dist/plugins/tidb-workflow-journal-store.js +1 -34
- package/dist/plugins/tidb-workflow-run-store.d.ts +1 -61
- package/dist/plugins/tidb-workflow-run-store.js +1 -231
- package/dist/plugins/tool-result-store-sql.d.ts +25 -0
- package/dist/plugins/tool-result-store-sql.js +69 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +30 -0
- package/dist/plugins/workflow-journal-store-sql.js +53 -0
- package/dist/plugins/workflow-run-store-sql.d.ts +85 -0
- package/dist/plugins/workflow-run-store-sql.js +299 -0
- package/dist/question.js +2 -2
- package/dist/run-local.js +1 -1
- package/dist/tool-approval.js +2 -2
- package/dist/trace/ledger-sink.js +1 -1
- package/dist/trace/project.d.ts +1 -0
- package/dist/trace/project.js +3 -0
- 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)。
|
package/dist/config-types.d.ts
CHANGED
|
@@ -221,7 +221,7 @@ export interface ServiceConfig {
|
|
|
221
221
|
workflowRunStoreBackend: "auto" | "file" | "memory";
|
|
222
222
|
workflowOrphanGraceMs: number;
|
|
223
223
|
schedulerEnabled: boolean;
|
|
224
|
-
|
|
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
|
|
170
|
-
const
|
|
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 &&
|
|
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 &&
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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:
|
|
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:
|
|
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
|
-
...(
|
|
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
|
-
...(
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
581
|
-
resourceSuspend:
|
|
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:
|
|
598
|
-
leaderFanoutEnabled:
|
|
599
|
-
routerEnabled:
|
|
600
|
-
selfOrchestrationEnabled: postureOn(
|
|
601
|
-
forkEnabled: postureOn(
|
|
602
|
-
experimentalObserverAgents:
|
|
603
|
-
schedulerEnabled: postureOn(
|
|
604
|
-
|
|
605
|
-
planModeEnabled:
|
|
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:
|
|
711
|
+
schedulerSessionWakeup: boolEnv("SCHEDULER_SESSION_WAKEUP", true),
|
|
609
712
|
selfOrchestrationModels: csv("SELF_ORCHESTRATION_MODELS"),
|
|
610
|
-
selfOrchestrationWorkerIsolation:
|
|
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:
|
|
624
|
-
selectEnvironmentTool:
|
|
625
|
-
envFactsEnabled:
|
|
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:
|
|
638
|
-
lspEnabled:
|
|
639
|
-
lspHostEnabled
|
|
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:
|
|
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(
|
|
652
|
-
toolApprovalEnabled: postureOn(
|
|
653
|
-
workflowAgentsReadOnly:
|
|
754
|
+
askQuestionEnabled: postureOn("ASK_QUESTION_ENABLED"),
|
|
755
|
+
toolApprovalEnabled: postureOn("TOOL_APPROVAL_ENABLED"),
|
|
756
|
+
workflowAgentsReadOnly: boolEnv("WORKFLOW_AGENTS_READONLY", false),
|
|
654
757
|
imageBakes: {
|
|
655
|
-
enabled:
|
|
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:
|
|
666
|
-
traceThinking:
|
|
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:
|
|
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:
|
|
784
|
+
toSupportsImages: boolEnv("MODEL_DEGRADE_TO_VISION", false),
|
|
682
785
|
}
|
|
683
786
|
: undefined,
|
|
684
787
|
otel: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
package/dist/elicitation.js
CHANGED
|
@@ -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
|
package/dist/http/idempotency.js
CHANGED
|
@@ -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
|