@sema-agent/server 7.79.0 → 7.79.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,499 @@
1
+ import { createHash } from "node:crypto";
2
+ import { setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isDestructiveSessionWrite, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
3
+ import { publicStoreProbeError } from "../store-live-probe.js";
4
+ import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
5
+ import { handleCapabilities } from "./routes/capabilities.js";
6
+ import { handleObservability } from "./routes/observability.js";
7
+ import { handleDiagnostics } from "./routes/diagnostics.js";
8
+ import { handleAdoption } from "./routes/adoption.js";
9
+ import { handleRetention } from "./routes/retention-ops.js";
10
+ import { handleAdminDrain } from "./routes/admin-drain.js";
11
+ import { handleDevices } from "./routes/devices.js";
12
+ import { handleAdminConfigRefresh } from "./routes/admin-config-refresh.js";
13
+ import { handleConfigCatalog } from "./routes/config-catalog.js";
14
+ import { handleMemoryOptOut } from "./routes/admin-memory-optout.js";
15
+ import { handleMemoryPolicy } from "./routes/memory-policy.js";
16
+ import { handleMemoryBundle } from "./routes/memory-bundle.js";
17
+ import { handleMemoryCompliance } from "./routes/memory-compliance.js";
18
+ import { handleMemoryOrigin } from "./routes/memory-origin.js";
19
+ import { handleMemoryConsolidation } from "./routes/memory-consolidation.js";
20
+ import { handleSharedMemory } from "./routes/shared-memory.js";
21
+ import { handleRules } from "./routes/rules.js";
22
+ import { handleSessionsList } from "./routes/sessions-list.js";
23
+ import { handleAgentRoster } from "./routes/agents-roster.js";
24
+ import { handleSessions } from "./routes/sessions.js";
25
+ import { handleSessionSync } from "./routes/session-sync.js";
26
+ import { handleAttachments } from "./routes/attachments.js";
27
+ import { handleFleet } from "./routes/fleet.js";
28
+ import { handleA2aServe, handleA2a } from "./routes/a2a-serve.js";
29
+ import { handleTraceUsage } from "./routes/trace-usage.js";
30
+ import { handleWorkflows, handleWorkflowAgentSteer } from "./routes/workflows.js";
31
+ import { handleNotifyWake, SESSION_WAKE_RE } from "./routes/notify-wake.js";
32
+ import { handleApprovalsAssistant, ASSISTANT_RESUME_RE, ASSISTANT_PLAN_REVIEW_RE } from "./routes/approvals-assistant.js";
33
+ import { handleRuns, handleRunVerbs, RUN_SUBAGENT_RESUME_RE } from "./routes/runs.js";
34
+ import { handleRunMemoryCapture } from "./routes/run-memory-capture.js";
35
+ import { handleSideQuery } from "./routes/side-query.js";
36
+ import { handleTasks } from "./routes/tasks.js";
37
+ import { handleLeader } from "./routes/leader.js";
38
+ import { handleImages } from "./routes/images.js";
39
+ import { sendJson, sendError, isSseResponse } from "./send.js";
40
+ import { authorized, systemFor } from "./principal-gate.js";
41
+ const PROCESS_STARTED_AT_MS = Date.now() - Math.round(process.uptime() * 1000);
42
+ const ROUTE_DOMAINS = [
43
+ handleTraceUsage,
44
+ handleWorkflows,
45
+ handleFleet,
46
+ handleA2aServe,
47
+ handleCapabilities,
48
+ handleSideQuery,
49
+ handleTasks,
50
+ handleRuns,
51
+ handleA2a,
52
+ handleWorkflowAgentSteer,
53
+ handleRunVerbs,
54
+ handleRunMemoryCapture,
55
+ handleLeader,
56
+ handleImages,
57
+ handleApprovalsAssistant,
58
+ handleObservability,
59
+ handleDiagnostics,
60
+ handleAdoption,
61
+ handleRetention,
62
+ handleAdminDrain,
63
+ handleDevices,
64
+ handleAdminConfigRefresh,
65
+ handleConfigCatalog,
66
+ handleMemoryOptOut,
67
+ handleMemoryPolicy,
68
+ handleMemoryBundle,
69
+ handleMemoryCompliance,
70
+ handleMemoryOrigin,
71
+ handleMemoryConsolidation,
72
+ handleRules,
73
+ handleSharedMemory,
74
+ handleSessionsList,
75
+ handleAgentRoster,
76
+ handleSessions,
77
+ handleSessionSync,
78
+ handleAttachments,
79
+ handleNotifyWake,
80
+ ];
81
+ void ROUTE_DOMAINS;
82
+ export function isMethodDispatchedSubmitPath(url) {
83
+ return url === "/v1/a2a";
84
+ }
85
+ export function hasServiceAuth(config) {
86
+ return Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
87
+ }
88
+ export function isBillableSubmitPath(url) {
89
+ return (url === "/v1/side-query" ||
90
+ url === "/v1/tasks" ||
91
+ url === "/v1/tasks/stream" ||
92
+ url === "/v1/runs" ||
93
+ url === "/v1/leader" ||
94
+ url === "/v1/a2a" ||
95
+ url === "/v1/admin/memory/consolidation/run" ||
96
+ ASSISTANT_RESUME_RE.test(url) ||
97
+ ASSISTANT_PLAN_REVIEW_RE.test(url) ||
98
+ RUN_SUBAGENT_RESUME_RE.test(url) ||
99
+ SESSION_WAKE_RE.test(url) ||
100
+ url.startsWith("/v1/approvals"));
101
+ }
102
+ export function isCredentialGatedRewrite(method, url) {
103
+ const m = method.toUpperCase();
104
+ if (m === "POST") {
105
+ return (url === "/v1/adoption" ||
106
+ url.startsWith("/v1/rules/cc-import/") ||
107
+ url.startsWith("/v1/rules/local-import/") ||
108
+ url === "/v1/memory/import" ||
109
+ url === "/v1/memory/export" ||
110
+ url === "/v1/memory/erase" ||
111
+ url.startsWith("/v1/memory/origin/entries/") ||
112
+ url.startsWith("/v1/devices/"));
113
+ }
114
+ if (m === "PUT")
115
+ return url === "/v1/admin/memory-optout" || url.startsWith("/v1/admin/memory-optout/");
116
+ if (m === "DELETE")
117
+ return url === "/v1/rules" || url.startsWith("/v1/admin/memory-optout/");
118
+ if (m === "GET") {
119
+ return (/^\/v1\/memory\/entries\/[^/]+\/provenance$/.test(url) ||
120
+ url === "/v1/memory/origin/external" ||
121
+ url === "/v1/memory/origin/clearances" ||
122
+ url === "/v1/admin/memory-optout" ||
123
+ url === "/v1/devices" ||
124
+ /^\/v1\/devices\/sessions\/[^/]+$/.test(url));
125
+ }
126
+ return false;
127
+ }
128
+ const ROUTE_LABEL_PATTERNS = [
129
+ [/^\/v1\/runs\/[^/]+\/steer$/, "/v1/runs/:id/steer"],
130
+ [/^\/v1\/runs\/[^/]+\/memory\/capture-optout$/, "/v1/runs/:id/memory/capture-optout"],
131
+ [/^\/v1\/runs\/[^/]+\/interrupt$/, "/v1/runs/:id/interrupt"],
132
+ [/^\/v1\/runs\/[^/]+\/detach$/, "/v1/runs/:id/detach"],
133
+ [/^\/v1\/runs\/[^/]+\/cancel$/, "/v1/runs/:id/cancel"],
134
+ [/^\/v1\/runs\/[^/]+\/compact$/, "/v1/runs/:id/compact"],
135
+ [/^\/v1\/runs\/[^/]+\/events$/, "/v1/runs/:id/events"],
136
+ [/^\/v1\/runs\/[^/]+\/subagents\/[^/]+\/(steer|resume|output|stream)$/, "/v1/runs/:id/subagents/:sub/*"],
137
+ [/^\/v1\/runs\/[^/]+\/tasks\/[^/]+\/(output|stop)$/, "/v1/runs/:id/tasks/:tid/*"],
138
+ [/^\/v1\/runs\/[^/]+$/, "/v1/runs/:id"],
139
+ [/^\/v1\/images\/bakes\/[^/]+\/events$/, "/v1/images/bakes/:id/events"],
140
+ [/^\/v1\/images\/bakes\/[^/]+\/cancel$/, "/v1/images/bakes/:id/cancel"],
141
+ [/^\/v1\/images\/bakes\/[^/]+\/claim$/, "/v1/images/bakes/:id/claim"],
142
+ [/^\/v1\/images\/bakes\/[^/]+\/ingest$/, "/v1/images/bakes/:id/ingest"],
143
+ [/^\/v1\/images\/bakes\/[^/]+$/, "/v1/images/bakes/:id"],
144
+ [/^\/v1\/images\/digests\/[^/]+$/, "/v1/images/digests/:digest"],
145
+ [/^\/v1\/images\/[^/]+$/, "/v1/images/:profile"],
146
+ [/^\/v1\/approvals\/[^/]+\/decide$/, "/v1/approvals/:id/decide"],
147
+ [/^\/v1\/approvals\/[^/]+\/exemptions(\/[^/]+)?$/, "/v1/approvals/:id/exemptions"],
148
+ [/^\/v1\/sessions\/[^/]+\/(wake|fork|init|notify|events|head|mcp|a2a|policy|settings)$/, "/v1/sessions/:id/$verb"],
149
+ [/^\/v1\/admin\/memory-optout\/[^/]+$/, "/v1/admin/memory-optout/:principal"],
150
+ [/^\/v1\/sessions\/[^/]+\/memory\/erase$/, "/v1/sessions/:id/memory/erase"],
151
+ [/^\/v1\/sessions\/[^/]+\/memory-status$/, "/v1/sessions/:id/memory-status"],
152
+ [/^\/v1\/sessions\/[^/]+\/sync\/(manifest|entries|plan)$/, "/v1/sessions/:id/sync/$verb"],
153
+ [/^\/v1\/sessions\/[^/]+\/sync\/import(\/[^/]+\/entries)?$/, "/v1/sessions/:id/sync/import"],
154
+ [/^\/v1\/sessions\/[^/]+\/sync\/blobs(\/[^/]+)?$/, "/v1/sessions/:id/sync/blobs"],
155
+ [/^\/v1\/sessions\/[^/]+\/sync\/history\/blobs\/[^/]+$/, "/v1/sessions/:id/sync/history/blobs/:hash"],
156
+ [/^\/v1\/sessions\/[^/]+\/workspace(\/[^/]+(\/(tree|file|archive))?)?$/, "/v1/sessions/:id/workspace"],
157
+ [/^\/v1\/sessions\/[^/]+$/, "/v1/sessions/:id"],
158
+ [/^\/v1\/assistant\/tasks\/[^/]+\/preempt$/, "/v1/assistant/tasks/:id/preempt"],
159
+ [/^\/v1\/assistant\/tasks\/[^/]+\/resume$/, "/v1/assistant/tasks/:id/resume"],
160
+ [/^\/v1\/assistant\/tasks\/[^/]+\/plan_review$/, "/v1/assistant/tasks/:id/plan_review"],
161
+ [/^\/v1\/elicitations\/[^/]+\/respond$/, "/v1/elicitations/:id/respond"],
162
+ [/^\/v1\/questions\/[^/]+\/respond$/, "/v1/questions/:id/respond"],
163
+ [/^\/v1\/tool-approvals\/[^/]+\/respond$/, "/v1/tool-approvals/:id/respond"],
164
+ [/^\/v1\/workflows\/[^/]+\/agents\/[^/]+\/steer$/, "/v1/workflows/:id/agents/:aid/steer"],
165
+ [/^\/v1\/workflows\/[^/]+\/(stream|journal)$/, "/v1/workflows/:id/$sub"],
166
+ [/^\/v1\/workflows\/[^/]+$/, "/v1/workflows/:id"],
167
+ [/^\/v1\/tasks\/[^/]+\/asks\/[^/]+\/decision$/, "/v1/tasks/:id/asks/:askId/decision"],
168
+ [/^\/v1\/tasks\/[^/]+\/tool-results\/[^/]+$/, "/v1/tasks/:id/tool-results/:ref"],
169
+ [/^\/v1\/tasks\/[^/]+\/(turns|stream|artifacts)$/, "/v1/tasks/:id/$sub"],
170
+ [/^\/v1\/leader\/[^/]+$/, "/v1/leader/:id"],
171
+ [/^\/v1\/devices\/sessions\/[^/]+\/rebind$/, "/v1/devices/sessions/:id/rebind"],
172
+ [/^\/v1\/devices\/sessions\/[^/]+$/, "/v1/devices/sessions/:id"],
173
+ [/^\/v1\/attachments\/[^/]+$/, "/v1/attachments/:id"],
174
+ [/^\/v1\/capabilities\/scenarios\/[^/]+$/, "/v1/capabilities/scenarios/:name"],
175
+ [/^\/v1\/adoption\/[^/]+$/, "/v1/adoption/:id"],
176
+ [/^\/v1\/ops\/retention\/holds\/[^/]*$/, "/v1/ops/retention/holds/:domain"],
177
+ [/^\/v1\/memory\/sync\/[^/]+$/, "/v1/memory/sync/:scope"],
178
+ [/^\/v1\/memory\/entries\/[^/]+\/provenance$/, "/v1/memory/entries/:entryId/provenance"],
179
+ [/^\/v1\/memory\/origin\/entries\/[^/]+\/clear$/, "/v1/memory/origin/entries/:entryId/clear"],
180
+ [/^\/v1\/usage\/(summary|series|breakdown)$/, "/v1/usage/$sub"],
181
+ [/^\/v1\/shared-memory\/stores\/[^/]+\/documents$/, "/v1/shared-memory/stores/:store/documents"],
182
+ [/^\/v1\/shared-memory\/stores\/[^/]+\/document$/, "/v1/shared-memory/stores/:store/document"],
183
+ ];
184
+ const ROUTE_LABEL_LITERALS = new Set([
185
+ "/health", "/metrics", "/metrics/summary", "/metrics/plan-cache",
186
+ "/v1/models", "/v1/tasks", "/v1/tasks/stream", "/v1/tasks/source-summary",
187
+ "/v1/runs", "/v1/sessions", "/v1/approvals", "/v1/approvals/stream",
188
+ "/v1/assistant/inbox", "/v1/assistant/tasks", "/v1/usage", "/v1/policy", "/v1/capabilities",
189
+ "/v1/images", "/v1/images/bakes", "/v1/images/bakes/claim", "/v1/images/select", "/v1/images/register",
190
+ "/v1/workflows", "/v1/attachments", "/v1/leader", "/v1/outcomes", "/v1/side-query",
191
+ "/v1/devices",
192
+ "/v1/fleet/stream",
193
+ "/v1/memory/export", "/v1/sendfile-links",
194
+ "/v1/memory/import",
195
+ "/v1/memory/erase",
196
+ "/v1/memory/origin/external", "/v1/memory/origin/clearances",
197
+ "/v1/shared-memory/stores",
198
+ "/v1/diagnostics/wiring",
199
+ "/v1/adoption",
200
+ "/v1/admin/drain",
201
+ "/v1/admin/config/refresh",
202
+ "/v1/config/catalog",
203
+ "/v1/admin/memory-optout",
204
+ "/v1/admin/memory/consolidation/run", "/v1/admin/memory/consolidation",
205
+ "/v1/rules/cc-import/prepare", "/v1/rules/cc-import/redeem",
206
+ "/v1/rules/local-import/prepare", "/v1/rules/local-import/redeem",
207
+ "/v1/rules",
208
+ "/.well-known/agent-card.json", "/v1/a2a",
209
+ "/v1/agents/roster",
210
+ "/v1/ops/retention/audit",
211
+ ]);
212
+ export function routeLabel(_method, url) {
213
+ if (ROUTE_LABEL_LITERALS.has(url))
214
+ return url;
215
+ for (const [re, label] of ROUTE_LABEL_PATTERNS)
216
+ if (re.test(url))
217
+ return label;
218
+ return "other";
219
+ }
220
+ function cors(res, req, origins, principalHeader) {
221
+ if (!origins || origins.length === 0)
222
+ return;
223
+ let allow;
224
+ if (origins.length === 1) {
225
+ allow = origins[0];
226
+ }
227
+ else {
228
+ res.setHeader("vary", "Origin");
229
+ const reqOrigin = typeof req.headers.origin === "string" ? req.headers.origin : undefined;
230
+ allow = reqOrigin !== undefined && origins.includes(reqOrigin) ? reqOrigin : undefined;
231
+ }
232
+ if (allow === undefined)
233
+ return;
234
+ res.setHeader("access-control-allow-origin", allow);
235
+ res.setHeader("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS");
236
+ res.setHeader("access-control-allow-headers", `content-type, authorization, last-event-id, ${principalHeader}, idempotency-key, x-detach-on-disconnect, ${PRINCIPAL_TOKEN_HEADER}, ${APPROVAL_MAC_HEADER}, ${APPROVAL_MAC_KID_HEADER}, if-none-match`);
237
+ res.setHeader("access-control-expose-headers", "x-task-id, retry-after");
238
+ }
239
+ export function createDispatch(ctx) {
240
+ const { deps, noteActivity, routeCtxBase, counters } = ctx;
241
+ const openResponses = new Set();
242
+ let lastAttachEndedAt = 0;
243
+ if (deps.drainState) {
244
+ deps.drainState.attachedStreams = () => {
245
+ let n = 0;
246
+ for (const r of openResponses)
247
+ if (isSseResponse(r))
248
+ n++;
249
+ return n;
250
+ };
251
+ deps.drainState.lastAttachEndedAt = () => lastAttachEndedAt;
252
+ }
253
+ async function handle(req, res) {
254
+ const startedAt = Date.now();
255
+ const method = req.method ?? "GET";
256
+ const url = (req.url ?? "/").split("?")[0];
257
+ noteActivity(url);
258
+ const admitted = method !== "OPTIONS" && url !== "/health" && !url.startsWith("/metrics");
259
+ if (admitted)
260
+ counters.admittedInflight++;
261
+ if (admitted)
262
+ openResponses.add(res);
263
+ let logged = false;
264
+ const reqState = { streamTaskId: undefined, streamDetached: false, source: null };
265
+ const ctx = { deps: routeCtxBase.deps, registry: routeCtxBase.registry, helpers: routeCtxBase.helpers, local: routeCtxBase.local, legs: routeCtxBase.legs, req: reqState };
266
+ const record = (viaClose) => {
267
+ if (logged)
268
+ return;
269
+ logged = true;
270
+ if (admitted)
271
+ counters.admittedInflight--;
272
+ if (openResponses.delete(res) && isSseResponse(res))
273
+ lastAttachEndedAt = performance.now();
274
+ const route = routeLabel(method, url);
275
+ const seconds = (Date.now() - startedAt) / 1000;
276
+ const aborted = viaClose && !res.writableEnded;
277
+ const status = aborted ? "499" : String(res.statusCode);
278
+ deps.metrics?.inc("http_requests_total", { route, method, status });
279
+ deps.metrics?.observe("http_request_duration_seconds", seconds, { route });
280
+ const log = route === "/health" || route === "/metrics" ? deps.logger?.debug : deps.logger?.info;
281
+ log?.call(deps.logger, "request", { method, route, status, ms: Date.now() - startedAt, ...(reqState.runStatus !== undefined ? { runStatus: reqState.runStatus } : {}), ...(reqState.runErrorCode !== undefined ? { errorCode: reqState.runErrorCode } : {}), ...(aborted ? { aborted: true, ...(reqState.streamTaskId ? { taskId: reqState.streamTaskId } : {}), ...(reqState.streamDetached ? { detached: true } : {}) } : {}) });
282
+ };
283
+ res.on("finish", () => record(false));
284
+ res.on("close", () => record(true));
285
+ cors(res, req, deps.config.corsOrigins, deps.config.principalHeader);
286
+ if (req.method === "OPTIONS") {
287
+ res.writeHead(204);
288
+ res.end();
289
+ return;
290
+ }
291
+ if (req.method === "GET" && url === "/health") {
292
+ const restart = deps.restartState?.();
293
+ const configHash = createHash("sha256")
294
+ .update(JSON.stringify({ model: deps.config.model.id, sessionBackend: deps.config.sessionBackend, dbBackend: deps.config.dbBackend, dataRoot: deps.config.localDataRoot, configProvider: deps.config.configProvider, remoteExec: deps.config.remoteExec?.provider, port: deps.config.port }))
295
+ .digest("hex")
296
+ .slice(0, 16);
297
+ sendJson(res, 200, {
298
+ status: "ok",
299
+ ...(typeof deps.capabilities?.version === "string" ? { version: deps.capabilities.version } : {}),
300
+ ...(deps.drainState?.draining ? { draining: true, ...(deps.drainState.since ? { drainingSince: deps.drainState.since } : {}), ...(deps.drainState.reason ? { drainReason: deps.drainState.reason } : {}) } : {}),
301
+ ...(deps.drainState?.inflight ? { inflight: deps.drainState.inflight() } : {}),
302
+ ...(deps.storeDegraded ? { durable: false, storeDegraded: true } : {}),
303
+ ...(deps.modelReady && !deps.modelReady() ? { ready: false, readyReason: "model roster pending (first effective-config pull)" } : {}),
304
+ model: deps.config.model.id,
305
+ sessionBackend: deps.sessionStoreLabel ?? deps.config.sessionBackend,
306
+ sessions: deps.runner.sessions.size,
307
+ ...(deps.instanceId ? { instanceId: deps.instanceId } : {}),
308
+ ...(deps.config.localDataRoot ? { dataRoot: deps.config.localDataRoot } : {}),
309
+ configHash,
310
+ pid: process.pid,
311
+ startedAt: PROCESS_STARTED_AT_MS,
312
+ port: deps.config.port,
313
+ ...(deps.config.directDoorActive ? { directDoorActive: true } : {}),
314
+ ...(restart ? { restartRequired: true, restart: { reasons: restart.reasons, version: restart.version, since: restart.since } } : {}),
315
+ ...(() => {
316
+ const stuck = deps.planeDeferredState?.();
317
+ return stuck ? { modelPlaneDeferred: { version: stuck.version, since: stuck.since, noHandoff: true, ...(stuck.blocked ? { blockedReasons: stuck.blocked } : {}) } } : {};
318
+ })(),
319
+ ...(() => {
320
+ const gen = deps.configApplyState?.();
321
+ if (!gen)
322
+ return {};
323
+ return {
324
+ configTargetVersion: gen.configTargetVersion,
325
+ configAppliedVersion: gen.configAppliedVersion,
326
+ configTargetOrdinal: gen.configTargetOrdinal,
327
+ configAppliedOrdinal: gen.configAppliedOrdinal,
328
+ ...(gen.configApplyStaleMs !== undefined ? { configApplyStaleMs: gen.configApplyStaleMs } : {}),
329
+ };
330
+ })(),
331
+ ...(() => {
332
+ const probe = deps.storeLiveState?.();
333
+ if (!probe)
334
+ return {};
335
+ return {
336
+ ...(probe.live ? {} : { storeLive: false }),
337
+ storeProbe: { live: probe.live, ageMs: probe.ageMs, ...(probe.error !== undefined ? { error: publicStoreProbeError(probe.error) } : {}) },
338
+ };
339
+ })(),
340
+ });
341
+ return;
342
+ }
343
+ if (req.method === "GET" && (url === "/metrics" || url === "/metrics/summary" || url === "/metrics/plan-cache")) {
344
+ if (!deps.metrics) {
345
+ sendError(res, 404, "feature.metrics_disabled", "metrics disabled");
346
+ return;
347
+ }
348
+ const { authToken: t, metricsToken: mt } = deps.config;
349
+ const anyT = Boolean(t) || Object.keys(deps.config.authTokens ?? {}).length > 0;
350
+ const devOpen = !anyT && !mt && !deps.config.requirePrincipal;
351
+ const ok = devOpen || (anyT && systemFor(req, deps.config) !== undefined) || (!!mt && authorized(req, mt));
352
+ if (!ok) {
353
+ sendError(res, 401, "auth.unauthorized", "unauthorized");
354
+ return;
355
+ }
356
+ if (url === "/metrics/summary") {
357
+ sendJson(res, 200, { model: deps.config.model.id, ...deps.metrics.summarize() });
358
+ return;
359
+ }
360
+ if (url === "/metrics/plan-cache") {
361
+ sendJson(res, 200, { scopes: deps.planCacheProbe?.dump() ?? {} });
362
+ return;
363
+ }
364
+ res.writeHead(200, { "content-type": "text/plain; version=0.0.4" });
365
+ res.end(deps.metrics.render());
366
+ return;
367
+ }
368
+ if (deps.registryJwtVerifier) {
369
+ const authz = req.headers.authorization ?? "";
370
+ const rawBearer = /^Bearer\s/i.test(authz) ? authz.replace(/^Bearer\s+/i, "") : "";
371
+ if (rawBearer && looksLikeJwt(rawBearer)) {
372
+ const v = await deps.registryJwtVerifier.verify(rawBearer);
373
+ if (v.ok) {
374
+ setSsoPrincipal(req, v.identity.principal);
375
+ req.headers[deps.config.principalHeader.toLowerCase()] = v.identity.principal;
376
+ if (v.identity.scope)
377
+ setSsoScope(req, v.identity.scope);
378
+ deps.logger?.info?.("auth_bridge_principal_accepted", { principal: v.identity.principal, ...(v.identity.scope ? { scope: v.identity.scope } : {}) });
379
+ }
380
+ }
381
+ }
382
+ if (await handleTraceUsage(req, res, url, ctx))
383
+ return;
384
+ if (await handleWorkflows(req, res, url, ctx))
385
+ return;
386
+ if (await handleFleet(req, res, url, ctx))
387
+ return;
388
+ if (await handleA2aServe(req, res, url, ctx))
389
+ return;
390
+ let source = null;
391
+ const anyServiceAuth = hasServiceAuth(deps.config);
392
+ if (anyServiceAuth) {
393
+ const sys = systemFor(req, deps.config);
394
+ if (sys === undefined) {
395
+ if (ssoVerifiedPrincipal(req)) {
396
+ source = "sso";
397
+ }
398
+ else {
399
+ sendError(res, 401, "auth.unauthorized", "unauthorized");
400
+ return;
401
+ }
402
+ }
403
+ else {
404
+ source = sys;
405
+ }
406
+ }
407
+ reqState.source = source;
408
+ if (!anyServiceAuth &&
409
+ !deps.config.allowUnauthedWrites &&
410
+ ((req.method === "POST" && (isBillableSubmitPath(url) || url.startsWith("/v1/images/bakes"))) ||
411
+ isCredentialGatedRewrite(req.method ?? "", url))) {
412
+ sendError(res, 503, "auth.service_token_required", "this worker requires a service auth token (set SERVICE_AUTH_TOKEN) before accepting task submissions");
413
+ return;
414
+ }
415
+ if (deps.drainState?.draining && req.method === "POST" && isBillableSubmitPath(url) && !isMethodDispatchedSubmitPath(url)) {
416
+ res.setHeader("retry-after", "15");
417
+ sendError(res, 503, "draining", "draining", { message: "this instance is draining for shutdown/upgrade — retry against the replacement instance", ...(deps.drainState.reason ? { reason: deps.drainState.reason } : {}) });
418
+ return;
419
+ }
420
+ if (deps.modelReady && !deps.modelReady() && req.method === "POST" && isBillableSubmitPath(url) && !isMethodDispatchedSubmitPath(url)) {
421
+ res.setHeader("retry-after", "5");
422
+ sendError(res, 503, "state.model_roster_pending", "model_roster_pending", { message: "this worker has no model yet (waiting for the first effective-config pull to land the roster) — retry shortly" });
423
+ return;
424
+ }
425
+ if (isDestructiveSessionWrite(req.method ?? "", url) && !anyServiceAuth && !deps.config.allowUnauthedWrites) {
426
+ sendError(res, 503, "auth.service_token_required", "this worker requires a service auth token (set SERVICE_AUTH_TOKEN) before accepting destructive session writes (policy / sync-import / fork / delete)");
427
+ return;
428
+ }
429
+ if (await handleCapabilities(req, res, url, ctx))
430
+ return;
431
+ if (await handleSideQuery(req, res, url, ctx))
432
+ return;
433
+ if (await handleTasks(req, res, url, ctx))
434
+ return;
435
+ if (await handleRuns(req, res, url, ctx))
436
+ return;
437
+ if (await handleA2a(req, res, url, ctx))
438
+ return;
439
+ if (await handleWorkflowAgentSteer(req, res, url, ctx))
440
+ return;
441
+ if (await handleRunVerbs(req, res, url, ctx))
442
+ return;
443
+ if (await handleRunMemoryCapture(req, res, url, ctx))
444
+ return;
445
+ if (await handleLeader(req, res, url, ctx))
446
+ return;
447
+ if (await handleImages(req, res, url, ctx))
448
+ return;
449
+ if (await handleApprovalsAssistant(req, res, url, ctx))
450
+ return;
451
+ if (await handleObservability(req, res, url, ctx))
452
+ return;
453
+ if (await handleDiagnostics(req, res, url, ctx))
454
+ return;
455
+ if (await handleAdoption(req, res, url, ctx))
456
+ return;
457
+ if (await handleRetention(req, res, url, ctx))
458
+ return;
459
+ if (await handleAdminDrain(req, res, url, ctx))
460
+ return;
461
+ if (await handleDevices(req, res, url, ctx))
462
+ return;
463
+ if (await handleAdminConfigRefresh(req, res, url, ctx))
464
+ return;
465
+ if (await handleConfigCatalog(req, res, url, ctx))
466
+ return;
467
+ if (await handleMemoryOptOut(req, res, url, ctx))
468
+ return;
469
+ if (await handleMemoryPolicy(req, res, url, ctx))
470
+ return;
471
+ if (await handleMemoryBundle(req, res, url, ctx))
472
+ return;
473
+ if (await handleMemoryCompliance(req, res, url, ctx))
474
+ return;
475
+ if (await handleMemoryOrigin(req, res, url, ctx))
476
+ return;
477
+ if (deps.memoryConsolidation && (await handleMemoryConsolidation(req, res, url, ctx)))
478
+ return;
479
+ if (await handleRules(req, res, url, ctx))
480
+ return;
481
+ if (deps.sharedMemoryStore && deps.orgMemoryDirectory && (await handleSharedMemory(req, res, url, ctx)))
482
+ return;
483
+ if (await handleSessionsList(req, res, url, ctx))
484
+ return;
485
+ if (await handleAgentRoster(req, res, url, ctx))
486
+ return;
487
+ if (await handleSessions(req, res, url, ctx))
488
+ return;
489
+ if (await handleSessionSync(req, res, url, ctx))
490
+ return;
491
+ if (await handleAttachments(req, res, url, ctx))
492
+ return;
493
+ if (await handleNotifyWake(req, res, url, ctx))
494
+ return;
495
+ sendError(res, 404, "not_found.route", "not found");
496
+ }
497
+ return { handle };
498
+ }
499
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 请求守门族(S-147 切片:`http/server.ts` 4837–5195 + `MAX_BODY` 逐字搬家,正文一字未改)。
3
+ * 配额 / 租约 / 用量窗 / 限流 / owner-scope / 体读取 —— 每只守门的判据与它自己的头注原位随行。
4
+ * ctx 字段 1 只(`deps`):本族对外围的耦合就这一格,它也是这一族的可测性读数。
5
+ */
6
+ import type { IncomingMessage, ServerResponse } from "node:http";
7
+ import type { FlatServiceDeps } from "./server.js";
8
+ export declare function createRequestGuards(ctx: {
9
+ deps: FlatServiceDeps;
10
+ }): {
11
+ quotaExceeded: (req: IncomingMessage, res: ServerResponse) => boolean;
12
+ ownerCostQuotaDenied: (principal: string | undefined) => {
13
+ status: number;
14
+ body: object;
15
+ } | undefined;
16
+ ownerFleetLeaseDenied: (owner: string | undefined) => Promise<{
17
+ status: number;
18
+ body: object;
19
+ } | undefined>;
20
+ ownerLeaseDenied: (res: ServerResponse, owner: string | undefined) => Promise<boolean>;
21
+ ownerQuotaDenied: (res: ServerResponse, owner: string | undefined) => boolean;
22
+ leaseDenied: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
23
+ usageWindowDenialFor: (key: string) => Promise<{
24
+ status: number;
25
+ body: {
26
+ error: string;
27
+ errorCode: string;
28
+ retryAfterSec: number;
29
+ };
30
+ retryAfterSec: number;
31
+ } | undefined>;
32
+ usageWindowDenied: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
33
+ rateLimited: (req: IncomingMessage, res: ServerResponse) => boolean;
34
+ safeDecode: (seg: string) => string | null;
35
+ isFleetWide: (req: IncomingMessage) => boolean;
36
+ sessionOwnerScope: (req: IncomingMessage) => {
37
+ fleetWide: boolean;
38
+ gateOwner: string | null;
39
+ };
40
+ sessionOwnerScopeForWrite: (req: IncomingMessage) => {
41
+ fleetWide: boolean;
42
+ gateOwner: string | null;
43
+ };
44
+ runOwnerOk: (req: IncomingMessage, res: ServerResponse, owner: string | null, notFoundForm?: "run" | "session") => boolean;
45
+ runSessionAcceptOk: (req: IncomingMessage, res: ServerResponse, run: {
46
+ sessionId: string | null;
47
+ }, face: string, notFoundError?: string) => boolean;
48
+ readJson: (req: IncomingMessage) => Promise<unknown>;
49
+ readRawBody: (req: IncomingMessage, max: number) => Promise<Buffer>;
50
+ };
51
+ //# sourceMappingURL=request-guards.d.ts.map