@sema-agent/server 7.79.3 → 7.80.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/boot/stage-07-capability-layer.js +2 -1
  2. package/dist/capabilities/skills.d.ts +10 -0
  3. package/dist/capabilities/skills.js +3 -1
  4. package/dist/http/admission.js +3 -1
  5. package/dist/http/dispatch.js +28 -8
  6. package/dist/http/resume-legs.js +4 -3
  7. package/dist/http/route-ctx.d.ts +101 -4
  8. package/dist/http/route-ctx.js +4 -0
  9. package/dist/http/route-table.d.ts +71 -43
  10. package/dist/http/route-table.js +111 -187
  11. package/dist/http/routes/a2a-serve.d.ts +18 -3
  12. package/dist/http/routes/a2a-serve.js +25 -23
  13. package/dist/http/routes/admin-config-refresh.d.ts +9 -2
  14. package/dist/http/routes/admin-config-refresh.js +32 -31
  15. package/dist/http/routes/admin-drain.d.ts +9 -2
  16. package/dist/http/routes/admin-drain.js +32 -31
  17. package/dist/http/routes/admin-memory-optout.d.ts +28 -2
  18. package/dist/http/routes/admin-memory-optout.js +27 -40
  19. package/dist/http/routes/adoption.d.ts +15 -2
  20. package/dist/http/routes/adoption.js +52 -52
  21. package/dist/http/routes/agents-roster.d.ts +9 -2
  22. package/dist/http/routes/agents-roster.js +82 -80
  23. package/dist/http/routes/approvals-assistant.d.ts +58 -5
  24. package/dist/http/routes/approvals-assistant.js +38 -39
  25. package/dist/http/routes/attachments.d.ts +22 -2
  26. package/dist/http/routes/attachments.js +81 -72
  27. package/dist/http/routes/capabilities.d.ts +21 -2
  28. package/dist/http/routes/capabilities.js +174 -170
  29. package/dist/http/routes/config-catalog.d.ts +9 -2
  30. package/dist/http/routes/config-catalog.js +28 -31
  31. package/dist/http/routes/devices.d.ts +24 -2
  32. package/dist/http/routes/devices.js +19 -21
  33. package/dist/http/routes/diagnostics.d.ts +9 -2
  34. package/dist/http/routes/diagnostics.js +41 -43
  35. package/dist/http/routes/fleet.d.ts +11 -2
  36. package/dist/http/routes/fleet.js +23 -22
  37. package/dist/http/routes/images.d.ts +63 -9
  38. package/dist/http/routes/images.js +53 -46
  39. package/dist/http/routes/leader.d.ts +20 -2
  40. package/dist/http/routes/leader.js +43 -40
  41. package/dist/http/routes/memory-bundle.d.ts +15 -2
  42. package/dist/http/routes/memory-bundle.js +38 -38
  43. package/dist/http/routes/memory-compliance.d.ts +16 -4
  44. package/dist/http/routes/memory-compliance.js +44 -45
  45. package/dist/http/routes/memory-consolidation.d.ts +14 -2
  46. package/dist/http/routes/memory-consolidation.js +66 -66
  47. package/dist/http/routes/memory-origin.d.ts +23 -4
  48. package/dist/http/routes/memory-origin.js +65 -65
  49. package/dist/http/routes/memory-policy.d.ts +21 -2
  50. package/dist/http/routes/memory-policy.js +119 -115
  51. package/dist/http/routes/notify-wake.d.ts +21 -11
  52. package/dist/http/routes/notify-wake.js +118 -116
  53. package/dist/http/routes/observability.d.ts +16 -2
  54. package/dist/http/routes/observability.js +54 -51
  55. package/dist/http/routes/retention-ops.d.ts +19 -2
  56. package/dist/http/routes/retention-ops.js +20 -22
  57. package/dist/http/routes/rules.d.ts +36 -2
  58. package/dist/http/routes/rules.js +22 -24
  59. package/dist/http/routes/run-memory-capture.d.ts +10 -3
  60. package/dist/http/routes/run-memory-capture.js +84 -84
  61. package/dist/http/routes/runs.d.ts +102 -22
  62. package/dist/http/routes/runs.js +79 -96
  63. package/dist/http/routes/session-sync.d.ts +41 -2
  64. package/dist/http/routes/session-sync.js +26 -24
  65. package/dist/http/routes/sessions-list.d.ts +16 -2
  66. package/dist/http/routes/sessions-list.js +78 -75
  67. package/dist/http/routes/sessions.d.ts +77 -2
  68. package/dist/http/routes/sessions.js +50 -47
  69. package/dist/http/routes/shared-memory.d.ts +19 -2
  70. package/dist/http/routes/shared-memory.js +65 -63
  71. package/dist/http/routes/side-query.d.ts +11 -2
  72. package/dist/http/routes/side-query.js +81 -79
  73. package/dist/http/routes/tasks.d.ts +17 -2
  74. package/dist/http/routes/tasks.js +18 -11
  75. package/dist/http/routes/trace-usage.d.ts +40 -2
  76. package/dist/http/routes/trace-usage.js +46 -35
  77. package/dist/http/routes/workflows.d.ts +35 -3
  78. package/dist/http/routes/workflows.js +29 -29
  79. package/dist/http/wire-types.d.ts +15 -9
  80. package/dist/parked-decide.d.ts +9 -0
  81. package/dist/parked-decide.js +3 -3
  82. package/dist/run-local.js +2 -1
  83. package/dist/spec-fields.js +33 -30
  84. package/dist/task-cwd.d.ts +21 -0
  85. package/dist/task-cwd.js +3 -0
  86. package/dist/trace/core-keyset-guard.d.ts +6 -1
  87. package/dist/trace/project.d.ts +20 -0
  88. package/dist/trace/project.js +15 -1
  89. package/package.json +2 -2
@@ -11,6 +11,7 @@ const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
11
11
  const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
12
12
  const SYNC_IMPORT_BATCH = 500;
13
13
  import { SHA256_HEX_RE } from "../../digest-form.js";
14
+ import { assertNever } from "../route-ctx.js";
14
15
  export function createSessionSyncLocal() {
15
16
  const pendingStagings = new Map();
16
17
  const importLeases = new Map();
@@ -27,18 +28,12 @@ export function createSessionSyncLocal() {
27
28
  };
28
29
  return { pendingStagings, importLeases, touchImportLease, cleanupStaging };
29
30
  }
30
- export async function handleSessionSync(req, res, url, ctx) {
31
- const miss = { fell: false };
32
- await handleSessionSyncBody(req, res, url, ctx, miss);
33
- return !miss.fell;
34
- }
35
- async function handleSessionSyncBody(req, res, url, ctx, miss) {
31
+ export async function handleSessionSync(req, res, match, ctx) {
36
32
  const { deps } = ctx;
37
33
  const { readJson, readRawBody, safeDecode, sessionOwnerScopeForWrite, rateLimited } = ctx.helpers;
38
34
  const { pendingStagings, importLeases, touchImportLease, cleanupStaging } = ctx.local.sessionSync;
39
35
  {
40
- const sm = /^\/v1\/sessions\/([^/]+)\/sync\/(manifest|entries|import|plan|blobs(?:\/([^/]+))?|history\/blobs\/([^/]+)|import\/([^/]+)\/entries)$/.exec(url);
41
- if (sm) {
36
+ {
42
37
  const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
43
38
  if (!deps.backend || !ownerOf) {
44
39
  sendError(res, 501, "capability.session_store_required", "session sync requires a durable session store");
@@ -49,7 +44,7 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
49
44
  sendError(res, 401, "auth.unauthorized", "unauthorized");
50
45
  return;
51
46
  }
52
- const sessionId = safeDecode(sm[1]);
47
+ const sessionId = safeDecode(match.params.sessionId);
53
48
  if (sessionId === null || !isUuidShape(sessionId)) {
54
49
  sendError(res, 400, "request.id_invalid", "invalid session id (must be a canonical uuid)");
55
50
  return;
@@ -74,7 +69,7 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
74
69
  }
75
70
  return true;
76
71
  };
77
- if (sm[2] === "manifest" && req.method === "GET") {
72
+ if (match.decl.id === "session-sync-manifest") {
78
73
  if (!pullGateOk())
79
74
  return;
80
75
  const out = await exportSessionManifest(sessionId, deps.backend);
@@ -85,10 +80,10 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
85
80
  sendJson(res, 200, { manifest: out.manifest });
86
81
  return;
87
82
  }
88
- if (sm[2] === "entries" && req.method === "GET") {
83
+ if (match.decl.id === "session-sync-entries") {
89
84
  if (!pullGateOk())
90
85
  return;
91
- const afterSeqRaw = new URL(req.url ?? "", "http://x").searchParams.get("afterSeq");
86
+ const afterSeqRaw = new URLSearchParams(match.search).get("afterSeq");
92
87
  let afterSeq;
93
88
  if (afterSeqRaw !== null) {
94
89
  const n = Number(afterSeqRaw);
@@ -127,10 +122,10 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
127
122
  }
128
123
  return;
129
124
  }
130
- if (sm[4] !== undefined && req.method === "GET") {
125
+ if (match.decl.id === "session-sync-history-blob") {
131
126
  if (!pullGateOk())
132
127
  return;
133
- const hash = safeDecode(sm[4]);
128
+ const hash = safeDecode(match.params.hash);
134
129
  if (hash === null) {
135
130
  sendError(res, 400, "request.path_malformed", "invalid blob hash");
136
131
  return;
@@ -159,12 +154,12 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
159
154
  res.end(Buffer.from(bytes));
160
155
  return;
161
156
  }
162
- if (sm[3] !== undefined && req.method === "PUT") {
157
+ if (match.decl.id === "session-sync-blob-put") {
163
158
  if (rateLimited(req, res))
164
159
  return;
165
160
  if (!pushGateOk())
166
161
  return;
167
- const hash = safeDecode(sm[3]);
162
+ const hash = safeDecode(match.params.hash);
168
163
  if (hash === null || !SHA256_HEX_RE.test(hash)) {
169
164
  sendError(res, 400, "request.id_invalid", "blob hash must be 64 lowercase-hex chars");
170
165
  return;
@@ -191,7 +186,7 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
191
186
  res.writeHead(204).end();
192
187
  return;
193
188
  }
194
- if (sm[2] === "import" && req.method === "POST") {
189
+ if (match.decl.id === "session-sync-import") {
195
190
  if (!pushGateOk())
196
191
  return;
197
192
  if (deps.sessionMirrorRuling && !scope.fleetWide && scope.gateOwner !== null) {
@@ -353,8 +348,8 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
353
348
  sendJson(res, 200, { stagingId, relation: relA.relation });
354
349
  return;
355
350
  }
356
- if (sm[5] !== undefined && req.method === "POST") {
357
- const stagingId = safeDecode(sm[5]);
351
+ if (match.decl.id === "session-sync-import-entries") {
352
+ const stagingId = safeDecode(match.params.stagingId);
358
353
  if (stagingId === null) {
359
354
  sendError(res, 400, "request.id_invalid", "invalid staging id");
360
355
  return;
@@ -462,7 +457,7 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
462
457
  if (pending.resolution !== undefined && overwriteWipeIncapable(deps.sessionPolicyStore)) {
463
458
  await pending.handle.abort().catch(() => undefined);
464
459
  cleanupStaging(stagingId);
465
- sendError(res, 501, "capability.policy_purge_required", "overwrite-dst cannot proceed: this deployment's session-policy store has no deleteBySession purge seam, so the destination's stale policy rows cannot be wiped and the import would land a MIXED state (the source's conversation plus the destination's leftover policy rows, which keep gating). Upgrade the policy store (every in-tree store carries deleteBySession since core 1.423), or re-run without resolution overwrite-dst");
460
+ sendError(res, 501, "capability.policy_purge_required", "overwrite-dst cannot proceed: this deployment's session-policy store has no deleteBySession purge verb, so the destination's stale policy rows cannot be wiped and the import would land a MIXED state (the source's conversation plus the destination's leftover policy rows, which keep gating). Upgrade the policy store (every in-tree store carries deleteBySession since core 1.423), or re-run without resolution overwrite-dst");
466
461
  return;
467
462
  }
468
463
  try {
@@ -507,7 +502,7 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
507
502
  }
508
503
  return;
509
504
  }
510
- if (sm[2] === "plan" && req.method === "POST") {
505
+ if (match.decl.id === "session-sync-plan") {
511
506
  if (!pushGateOk())
512
507
  return;
513
508
  let body;
@@ -527,10 +522,17 @@ async function handleSessionSyncBody(req, res, url, ctx, miss) {
527
522
  sendJson(res, 200, relation);
528
523
  return;
529
524
  }
530
- sendError(res, 404, "not_found.route", "not found");
531
- return;
525
+ return assertNever(match.decl.id);
532
526
  }
533
527
  }
534
- miss.fell = true;
535
528
  }
529
+ export const SESSION_SYNC_ROUTES = [
530
+ { id: "session-sync-manifest", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/sync\/manifest$/, label: "/v1/sessions/:id/sync/$verb", methods: ["GET"] },
531
+ { id: "session-sync-entries", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/sync\/entries$/, label: "/v1/sessions/:id/sync/$verb", methods: ["GET"] },
532
+ { id: "session-sync-plan", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/sync\/plan$/, label: "/v1/sessions/:id/sync/$verb", methods: ["POST"] },
533
+ { id: "session-sync-import", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/sync\/import$/, label: "/v1/sessions/:id/sync/import", methods: ["POST"] },
534
+ { id: "session-sync-import-entries", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/sync\/import\/(?<stagingId>[^/]+)\/entries$/, label: "/v1/sessions/:id/sync/import", methods: ["POST"] },
535
+ { id: "session-sync-blob-put", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/sync\/blobs\/(?<hash>[^/]+)$/, label: "/v1/sessions/:id/sync/blobs", methods: ["PUT"] },
536
+ { id: "session-sync-history-blob", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/sync\/history\/blobs\/(?<hash>[^/]+)$/, label: "/v1/sessions/:id/sync/history/blobs/:hash", methods: ["GET"] },
537
+ ];
536
538
  //# sourceMappingURL=session-sync.js.map
@@ -5,6 +5,20 @@
5
5
  * owner-scope 与 trace 列表同一权威模型(system/traceToken=fleet-wide,principal=钉住本人)。本域零可变状态。
6
6
  */
7
7
  import type { IncomingMessage, ServerResponse } from "node:http";
8
- import type { RouteCtx } from "../route-ctx.js";
9
- export declare function handleSessionsList(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
8
+ import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
9
+ /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
10
+ * 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
11
+ * 跨域的东西(deps / 助手 / 单域状态)一律在首行解构出来,于是下面每一行都逐字不变。 */
12
+ export declare function handleSessionsList(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SessionsListRouteId>, ctx: RouteCtx): Promise<void>;
13
+ export declare const SESSIONS_LIST_ROUTES: readonly [{
14
+ readonly id: "sessions-list";
15
+ readonly path: "/v1/sessions";
16
+ readonly methods: readonly ["GET"];
17
+ }, {
18
+ readonly id: "sendfile-links";
19
+ readonly path: "/v1/sendfile-links";
20
+ readonly methods: readonly ["GET"];
21
+ }];
22
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
23
+ export type SessionsListRouteId = RouteIdsOf<typeof SESSIONS_LIST_ROUTES>;
10
24
  //# sourceMappingURL=sessions-list.d.ts.map
@@ -1,91 +1,90 @@
1
1
  import { isUuidV7, legacyUnownedAmnestyOpen } from "../../security.js";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { filterFingerprint } from "../cursor-fingerprint.js";
4
- export async function handleSessionsList(req, res, url, ctx) {
5
- const miss = { fell: false };
6
- await handleSessionsListBody(req, res, url, ctx, miss);
7
- return !miss.fell;
8
- }
9
- async function handleSessionsListBody(req, res, url, ctx, miss) {
4
+ import { assertNever } from "../route-ctx.js";
5
+ export async function handleSessionsList(req, res, match, ctx) {
10
6
  const { deps } = ctx;
11
7
  const { isFleetWide, sessionOwnerScope } = ctx.helpers;
12
- if (req.method === "GET" && url === "/v1/sessions") {
13
- const { fleetWide, gateOwner } = sessionOwnerScope(req);
14
- if (!fleetWide && gateOwner === null) {
15
- sendError(res, 401, "auth.unauthorized", "unauthorized");
16
- return;
17
- }
18
- const lister = deps.sessionStorage?.listSessions?.bind(deps.sessionStorage) ?? deps.runStore?.listSessions?.bind(deps.runStore);
19
- if (!lister) {
20
- sendError(res, 501, "capability.session_store_required", "session list requires a durable session store (DB_BACKEND=mysql|pg|local)");
21
- return;
22
- }
23
- const includeUnowned = !fleetWide && legacyUnownedAmnestyOpen(deps.config.requirePrincipal, gateOwner);
24
- await handleSessionList(res, lister, new URL(req.url ?? "", "http://x").searchParams, gateOwner, includeUnowned);
25
- return;
26
- }
27
- if (req.method === "GET" && url === "/v1/sendfile-links") {
28
- const { fleetWide, gateOwner } = sessionOwnerScope(req);
29
- if (!fleetWide && gateOwner === null) {
30
- sendError(res, 401, "auth.unauthorized", "unauthorized");
31
- return;
32
- }
33
- if (!deps.sendFileLedger) {
34
- sendError(res, 501, "capability.store_required", "sendfile ledger requires a store backend (DB_BACKEND=mysql|pg|local) and the SendUserFile issuer configured");
35
- return;
36
- }
37
- const query = new URL(req.url ?? "", "http://x").searchParams;
38
- const limitRaw = query.get("limit");
39
- if (limitRaw !== null && !(/^[0-9]+$/.test(limitRaw) && Number(limitRaw) >= 1 && Number(limitRaw) <= 100)) {
40
- sendError(res, 400, "request.query_invalid", "limit must be a decimal integer in 1..100");
41
- return;
42
- }
43
- const limit = limitRaw === null ? 50 : Number(limitRaw);
44
- const beforeRaw = query.get("before");
45
- if (beforeRaw !== null && !isUuidV7(beforeRaw)) {
46
- sendError(res, 400, "request.id_invalid", "before must be a uuidv7 link id (the `id`/`nextBefore` of a previous page)");
8
+ switch (match.decl.id) {
9
+ case "sessions-list": {
10
+ const { fleetWide, gateOwner } = sessionOwnerScope(req);
11
+ if (!fleetWide && gateOwner === null) {
12
+ sendError(res, 401, "auth.unauthorized", "unauthorized");
13
+ return;
14
+ }
15
+ const lister = deps.sessionStorage?.listSessions?.bind(deps.sessionStorage) ?? deps.runStore?.listSessions?.bind(deps.runStore);
16
+ if (!lister) {
17
+ sendError(res, 501, "capability.session_store_required", "session list requires a durable session store (DB_BACKEND=mysql|pg|local)");
18
+ return;
19
+ }
20
+ const includeUnowned = !fleetWide && legacyUnownedAmnestyOpen(deps.config.requirePrincipal, gateOwner);
21
+ await handleSessionList(res, lister, new URLSearchParams(match.search), gateOwner, includeUnowned);
47
22
  return;
48
23
  }
49
- const before = beforeRaw ?? undefined;
50
- let scope;
51
- if (gateOwner !== null) {
52
- scope = gateOwner;
53
- }
54
- else {
55
- const scopeRaw = query.get("scope");
56
- if (scopeRaw === null) {
57
- scope = "_";
24
+ case "sendfile-links": {
25
+ const { fleetWide, gateOwner } = sessionOwnerScope(req);
26
+ if (!fleetWide && gateOwner === null) {
27
+ sendError(res, 401, "auth.unauthorized", "unauthorized");
28
+ return;
29
+ }
30
+ if (!deps.sendFileLedger) {
31
+ sendError(res, 501, "capability.store_required", "sendfile ledger requires a store backend (DB_BACKEND=mysql|pg|local) and the SendUserFile issuer configured");
32
+ return;
33
+ }
34
+ const query = new URLSearchParams(match.search);
35
+ const limitRaw = query.get("limit");
36
+ if (limitRaw !== null && !(/^[0-9]+$/.test(limitRaw) && Number(limitRaw) >= 1 && Number(limitRaw) <= 100)) {
37
+ sendError(res, 400, "request.query_invalid", "limit must be a decimal integer in 1..100");
38
+ return;
39
+ }
40
+ const limit = limitRaw === null ? 50 : Number(limitRaw);
41
+ const beforeRaw = query.get("before");
42
+ if (beforeRaw !== null && !isUuidV7(beforeRaw)) {
43
+ sendError(res, 400, "request.id_invalid", "before must be a uuidv7 link id (the `id`/`nextBefore` of a previous page)");
44
+ return;
45
+ }
46
+ const before = beforeRaw ?? undefined;
47
+ let scope;
48
+ if (gateOwner !== null) {
49
+ scope = gateOwner;
58
50
  }
59
51
  else {
60
- const s = scopeRaw.trim();
61
- if (s === "" || s.length > 190) {
62
- sendError(res, 400, "request.query_invalid", "scope must be a non-blank tenant principal of at most 190 characters (the sendfile_link.scope column width)");
63
- return;
52
+ const scopeRaw = query.get("scope");
53
+ if (scopeRaw === null) {
54
+ scope = "_";
55
+ }
56
+ else {
57
+ const s = scopeRaw.trim();
58
+ if (s === "" || s.length > 190) {
59
+ sendError(res, 400, "request.query_invalid", "scope must be a non-blank tenant principal of at most 190 characters (the sendfile_link.scope column width)");
60
+ return;
61
+ }
62
+ scope = s;
64
63
  }
65
- scope = s;
66
64
  }
65
+ const rows = await deps.sendFileLedger.listByScope(scope, limit + 1, before);
66
+ const hasMore = rows.length > limit;
67
+ const page = hasMore ? rows.slice(0, limit) : rows;
68
+ sendJson(res, 200, {
69
+ links: page.map((r) => ({
70
+ id: r.id,
71
+ filename: r.filename,
72
+ size: r.size,
73
+ ttlSec: r.ttlSec,
74
+ track: r.track,
75
+ bucket: r.bucket,
76
+ key: r.objectKey,
77
+ createdAt: new Date(r.createdAt).toISOString(),
78
+ ...(r.taskId !== undefined ? { taskId: r.taskId } : {}),
79
+ ...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
80
+ })),
81
+ ...(hasMore ? { nextBefore: page[page.length - 1].id } : {}),
82
+ });
83
+ return;
67
84
  }
68
- const rows = await deps.sendFileLedger.listByScope(scope, limit + 1, before);
69
- const hasMore = rows.length > limit;
70
- const page = hasMore ? rows.slice(0, limit) : rows;
71
- sendJson(res, 200, {
72
- links: page.map((r) => ({
73
- id: r.id,
74
- filename: r.filename,
75
- size: r.size,
76
- ttlSec: r.ttlSec,
77
- track: r.track,
78
- bucket: r.bucket,
79
- key: r.objectKey,
80
- createdAt: new Date(r.createdAt).toISOString(),
81
- ...(r.taskId !== undefined ? { taskId: r.taskId } : {}),
82
- ...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
83
- })),
84
- ...(hasMore ? { nextBefore: page[page.length - 1].id } : {}),
85
- });
86
- return;
85
+ default:
86
+ return assertNever(match.decl.id);
87
87
  }
88
- miss.fell = true;
89
88
  }
90
89
  function encodeSessionCursor(c, f) {
91
90
  return Buffer.from(JSON.stringify({ ...c, f })).toString("base64url");
@@ -123,4 +122,8 @@ async function handleSessionList(res, lister, query, forceOwner, includeUnowned
123
122
  const nextCursor = hasMore && last ? encodeSessionCursor({ lastActivityAt: last.lastActivityAt, sessionId: last.sessionId }, f) : undefined;
124
123
  sendJson(res, 200, { sessions: page, ...(nextCursor ? { nextCursor } : {}) });
125
124
  }
125
+ export const SESSIONS_LIST_ROUTES = [
126
+ { id: "sessions-list", path: "/v1/sessions", methods: ["GET"] },
127
+ { id: "sendfile-links", path: "/v1/sendfile-links", methods: ["GET"] },
128
+ ];
126
129
  //# sourceMappingURL=sessions-list.js.map
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import type { IncomingMessage, ServerResponse } from "node:http";
15
15
  import { type SessionMemoryStatus } from "@sema-agent/core";
16
- import type { RouteCtx } from "../route-ctx.js";
16
+ import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
17
17
  /** 单域独占状态。每个 server 实例一份——模块级会把连接坑/缓存跨实例串味。 */
18
18
  export interface SessionsLocal {
19
19
  sseConnections: number;
@@ -46,5 +46,80 @@ export interface SessionsLocal {
46
46
  }>;
47
47
  }
48
48
  export declare function createSessionsLocal(): SessionsLocal;
49
- export declare function handleSessions(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
49
+ /** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(去缩进两格;sessions 另有一处见文件头说明)。
50
+ * 裸 `return;` 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。 */
51
+ export declare function handleSessions(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SessionsRouteId>, ctx: RouteCtx): Promise<void>;
52
+ export declare const SESSIONS_ROUTES: readonly [{
53
+ readonly id: "session-init";
54
+ readonly pattern: RegExp;
55
+ readonly label: "/v1/sessions/:id/$verb";
56
+ readonly methods: readonly ["GET"];
57
+ }, {
58
+ readonly id: "session-settings";
59
+ readonly pattern: RegExp;
60
+ readonly label: "/v1/sessions/:id/$verb";
61
+ readonly methods: readonly ["GET"];
62
+ }, {
63
+ readonly id: "session-mcp";
64
+ readonly pattern: RegExp;
65
+ readonly label: "/v1/sessions/:id/$verb";
66
+ readonly methods: readonly ["GET"];
67
+ }, {
68
+ readonly id: "session-a2a";
69
+ readonly pattern: RegExp;
70
+ readonly label: "/v1/sessions/:id/$verb";
71
+ readonly methods: readonly ["GET"];
72
+ }, {
73
+ readonly id: "session-head";
74
+ readonly pattern: RegExp;
75
+ readonly label: "/v1/sessions/:id/$verb";
76
+ readonly methods: readonly ["GET"];
77
+ }, {
78
+ readonly id: "session-events";
79
+ readonly pattern: RegExp;
80
+ readonly label: "/v1/sessions/:id/$verb";
81
+ readonly methods: readonly ["GET"];
82
+ }, {
83
+ readonly id: "session-fork";
84
+ readonly pattern: RegExp;
85
+ readonly label: "/v1/sessions/:id/$verb";
86
+ readonly methods: readonly ["POST"];
87
+ }, {
88
+ readonly id: "session-policy-get";
89
+ readonly pattern: RegExp;
90
+ readonly label: "/v1/sessions/:id/$verb";
91
+ readonly methods: readonly ["GET"];
92
+ }, {
93
+ readonly id: "session-policy-put";
94
+ readonly pattern: RegExp;
95
+ readonly label: "/v1/sessions/:id/$verb";
96
+ readonly methods: readonly ["PUT"];
97
+ }, {
98
+ readonly id: "session-memory-erase";
99
+ readonly pattern: RegExp;
100
+ readonly label: "/v1/sessions/:id/memory/erase";
101
+ readonly methods: readonly ["POST"];
102
+ }, {
103
+ readonly id: "session-memory-status";
104
+ readonly pattern: RegExp;
105
+ readonly label: "/v1/sessions/:id/memory-status";
106
+ readonly methods: readonly ["GET"];
107
+ }, {
108
+ readonly id: "session-workspace";
109
+ readonly pattern: RegExp;
110
+ readonly label: "/v1/sessions/:id/workspace";
111
+ readonly methods: readonly ["GET"];
112
+ }, {
113
+ readonly id: "session-detail";
114
+ readonly pattern: RegExp;
115
+ readonly label: "/v1/sessions/:id";
116
+ readonly methods: readonly ["GET"];
117
+ }, {
118
+ readonly id: "session-delete";
119
+ readonly pattern: RegExp;
120
+ readonly label: "/v1/sessions/:id";
121
+ readonly methods: readonly ["DELETE"];
122
+ }];
123
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
124
+ export type SessionsRouteId = RouteIdsOf<typeof SESSIONS_ROUTES>;
50
125
  //# sourceMappingURL=sessions.d.ts.map