@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
@@ -59,7 +59,7 @@
59
59
  */
60
60
  import type { IncomingMessage, ServerResponse } from "node:http";
61
61
  import type { MemoryImportReport } from "@sema-agent/core";
62
- import type { RouteCtx } from "../route-ctx.js";
62
+ import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
63
63
  export declare const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export";
64
64
  export declare const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import";
65
65
  /**
@@ -105,5 +105,18 @@ export declare function buildImportReportWire(r: MemoryImportReport): {
105
105
  }[];
106
106
  custodyAppended: number;
107
107
  };
108
- export declare function handleMemoryBundle(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
108
+ export declare function handleMemoryBundle(req: IncomingMessage, res: ServerResponse, match: RouteMatch<MemoryBundleRouteId>, ctx: RouteCtx): Promise<void>;
109
+ export declare const MEMORY_BUNDLE_ROUTES: readonly [{
110
+ readonly id: "memory-import";
111
+ readonly path: "/v1/memory/import";
112
+ readonly credentialGated: readonly ["POST"];
113
+ readonly methods: readonly ["POST"];
114
+ }, {
115
+ readonly id: "memory-bundle-export";
116
+ readonly path: "/v1/memory/export";
117
+ readonly credentialGated: readonly ["POST"];
118
+ readonly methods: readonly ["POST"];
119
+ }];
120
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
121
+ export type MemoryBundleRouteId = RouteIdsOf<typeof MEMORY_BUNDLE_ROUTES>;
109
122
  //# sourceMappingURL=memory-bundle.d.ts.map
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { sendJson, sendError } from "../send.js";
3
3
  import { buildOriginFencedBundle, readOriginAware } from "../../memory-origin-fence.js";
4
4
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
5
+ import { assertNever } from "../route-ctx.js";
5
6
  export const MEMORY_BUNDLE_EXPORT_PATH = "/v1/memory/export";
6
7
  export const MEMORY_BUNDLE_IMPORT_PATH = "/v1/memory/import";
7
8
  const ExportRequestSchema = z.object({ scopes: z.array(z.string().min(1)).min(1) });
@@ -56,20 +57,8 @@ export function buildImportReportWire(r) {
56
57
  custodyAppended: r.custodyAppended,
57
58
  };
58
59
  }
59
- export async function handleMemoryBundle(req, res, url, ctx) {
60
- const miss = { fell: false };
61
- await handleMemoryBundleBody(req, res, url, ctx, miss);
62
- return !miss.fell;
63
- }
64
- async function handleMemoryBundleBody(req, res, url, ctx, miss) {
60
+ export async function handleMemoryBundle(req, res, match, ctx) {
65
61
  const { deps } = ctx;
66
- const path = url.split("?")[0] ?? url;
67
- const isExport = req.method === "POST" && path === MEMORY_BUNDLE_EXPORT_PATH;
68
- const isImport = req.method === "POST" && path === MEMORY_BUNDLE_IMPORT_PATH;
69
- if (!isExport && !isImport) {
70
- miss.fell = true;
71
- return;
72
- }
73
62
  const principal = gatedPrincipal(req, deps.config);
74
63
  if (deps.config.requirePrincipal && !principal) {
75
64
  sendPrincipalRefusal(res, req, deps.config);
@@ -86,34 +75,45 @@ async function handleMemoryBundleBody(req, res, url, ctx, miss) {
86
75
  return;
87
76
  }
88
77
  const raw = await ctx.helpers.readJson(req);
89
- if (isExport) {
90
- const parsed = ExportRequestSchema.safeParse(raw);
91
- if (!parsed.success) {
92
- sendError(res, 400, "request.body_shape", "invalid memory export request — expected an object with a non-empty `scopes` array of non-empty strings");
78
+ switch (match.decl.id) {
79
+ case "memory-bundle-export": {
80
+ const parsed = ExportRequestSchema.safeParse(raw);
81
+ if (!parsed.success) {
82
+ sendError(res, 400, "request.body_shape", "invalid memory export request — expected an object with a non-empty `scopes` array of non-empty strings");
83
+ return;
84
+ }
85
+ try {
86
+ const bundle = await engineExport(parsed.data.scopes);
87
+ sendJson(res, 200, buildOriginFencedBundle(bundle, readOriginAware(new URLSearchParams(match.search))));
88
+ }
89
+ catch (err) {
90
+ if (!sentClassifiedFailure(res, ctx, "export", err))
91
+ sendError(res, 500, "internal.error", "memory bundle export failed");
92
+ }
93
93
  return;
94
94
  }
95
- try {
96
- const bundle = await engineExport(parsed.data.scopes);
97
- sendJson(res, 200, buildOriginFencedBundle(bundle, readOriginAware(new URL(req.url ?? "", "http://x").searchParams)));
98
- }
99
- catch (err) {
100
- if (!sentClassifiedFailure(res, ctx, "export", err))
101
- sendError(res, 500, "internal.error", "memory bundle export failed");
95
+ case "memory-import": {
96
+ const parsed = ImportRequestSchema.safeParse(raw);
97
+ if (!parsed.success) {
98
+ sendError(res, 400, "request.body_shape", "invalid memory import request — expected an object with a `bundle` object");
99
+ return;
100
+ }
101
+ try {
102
+ const report = await engineImport(parsed.data.bundle);
103
+ sendJson(res, 200, buildImportReportWire(report));
104
+ }
105
+ catch (err) {
106
+ if (!sentClassifiedFailure(res, ctx, "import", err))
107
+ sendError(res, 500, "internal.error", "memory bundle import failed");
108
+ }
109
+ return;
102
110
  }
103
- return;
104
- }
105
- const parsed = ImportRequestSchema.safeParse(raw);
106
- if (!parsed.success) {
107
- sendError(res, 400, "request.body_shape", "invalid memory import request — expected an object with a `bundle` object");
108
- return;
109
- }
110
- try {
111
- const report = await engineImport(parsed.data.bundle);
112
- sendJson(res, 200, buildImportReportWire(report));
113
- }
114
- catch (err) {
115
- if (!sentClassifiedFailure(res, ctx, "import", err))
116
- sendError(res, 500, "internal.error", "memory bundle import failed");
111
+ default:
112
+ return assertNever(match.decl.id);
117
113
  }
118
114
  }
115
+ export const MEMORY_BUNDLE_ROUTES = [
116
+ { id: "memory-import", path: MEMORY_BUNDLE_IMPORT_PATH, credentialGated: ["POST"], methods: ["POST"] },
117
+ { id: "memory-bundle-export", path: MEMORY_BUNDLE_EXPORT_PATH, credentialGated: ["POST"], methods: ["POST"] },
118
+ ];
119
119
  //# sourceMappingURL=memory-bundle.js.map
@@ -94,9 +94,21 @@
94
94
  * 分层:本模块不值 import `server.ts`(那条边闭合运行时装载环),只 `import type`。
95
95
  */
96
96
  import type { IncomingMessage, ServerResponse } from "node:http";
97
- import type { RouteCtx } from "../route-ctx.js";
97
+ import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
98
98
  export declare const MEMORY_ERASE_PATH = "/v1/memory/erase";
99
- /** 单条出处读面。带参路径 ⇒ 路由声明表(`http/route-table.ts`)里走 `pattern` 行,不是 `path` 行。 */
100
- export declare const MEMORY_PROVENANCE_RE: RegExp;
101
- export declare function handleMemoryCompliance(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
99
+ export declare function handleMemoryCompliance(req: IncomingMessage, res: ServerResponse, match: RouteMatch<MemoryComplianceRouteId>, ctx: RouteCtx): Promise<void>;
100
+ export declare const MEMORY_COMPLIANCE_ROUTES: readonly [{
101
+ readonly id: "memory-erase";
102
+ readonly path: "/v1/memory/erase";
103
+ readonly credentialGated: readonly ["POST"];
104
+ readonly methods: readonly ["POST"];
105
+ }, {
106
+ readonly id: "memory-entry-provenance";
107
+ readonly pattern: RegExp;
108
+ readonly label: "/v1/memory/entries/:entryId/provenance";
109
+ readonly credentialGated: readonly ["GET"];
110
+ readonly methods: readonly ["GET"];
111
+ }];
112
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
113
+ export type MemoryComplianceRouteId = RouteIdsOf<typeof MEMORY_COMPLIANCE_ROUTES>;
102
114
  //# sourceMappingURL=memory-compliance.d.ts.map
@@ -3,8 +3,8 @@ import { ControlPlaneCorruptError } from "@sema-agent/core";
3
3
  import { erasureInputSmugglesPrototypeKey } from "../../memory-operator-faces.js";
4
4
  import { sendJson, sendError } from "../send.js";
5
5
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
6
+ import { assertNever } from "../route-ctx.js";
6
7
  export const MEMORY_ERASE_PATH = "/v1/memory/erase";
7
- export const MEMORY_PROVENANCE_RE = /^\/v1\/memory\/entries\/([^/]+)\/provenance$/;
8
8
  const EraseRequestSchema = z.object({
9
9
  requestId: z.string().min(1),
10
10
  select: z.record(z.string(), z.unknown()),
@@ -45,20 +45,8 @@ function sentClassifiedFailure(res, ctx, face, err) {
45
45
  ctx.deps.logger?.warn?.(`memory_compliance_${face}_failed`, { err: String(err) });
46
46
  return false;
47
47
  }
48
- export async function handleMemoryCompliance(req, res, url, ctx) {
49
- const miss = { fell: false };
50
- await handleMemoryComplianceBody(req, res, url, ctx, miss);
51
- return !miss.fell;
52
- }
53
- async function handleMemoryComplianceBody(req, res, url, ctx, miss) {
48
+ export async function handleMemoryCompliance(req, res, match, ctx) {
54
49
  const { deps } = ctx;
55
- const path = url.split("?")[0] ?? url;
56
- const provMatch = req.method === "GET" ? MEMORY_PROVENANCE_RE.exec(path) : null;
57
- const isErase = req.method === "POST" && path === MEMORY_ERASE_PATH;
58
- if (provMatch === null && !isErase) {
59
- miss.fell = true;
60
- return;
61
- }
62
50
  const principal = gatedPrincipal(req, deps.config);
63
51
  if (deps.config.requirePrincipal && !principal) {
64
52
  sendPrincipalRefusal(res, req, deps.config);
@@ -73,41 +61,52 @@ async function handleMemoryComplianceBody(req, res, url, ctx, miss) {
73
61
  sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
74
62
  return;
75
63
  }
76
- if (provMatch !== null) {
77
- const entryId = ctx.helpers.safeDecode(provMatch[1]);
78
- if (entryId === null) {
79
- sendError(res, 400, "request.id_invalid", "invalid memory entry id");
64
+ switch (match.decl.id) {
65
+ case "memory-entry-provenance": {
66
+ const entryId = ctx.helpers.safeDecode(match.params.entryId);
67
+ if (entryId === null) {
68
+ sendError(res, 400, "request.id_invalid", "invalid memory entry id");
69
+ return;
70
+ }
71
+ try {
72
+ sendJson(res, 200, await faces.provenanceOf(entryId));
73
+ }
74
+ catch (err) {
75
+ if (!sentClassifiedFailure(res, ctx, "provenance", err))
76
+ sendError(res, 500, "internal.error", "memory provenance read failed");
77
+ }
80
78
  return;
81
79
  }
82
- try {
83
- sendJson(res, 200, await faces.provenanceOf(entryId));
84
- }
85
- catch (err) {
86
- if (!sentClassifiedFailure(res, ctx, "provenance", err))
87
- sendError(res, 500, "internal.error", "memory provenance read failed");
88
- }
89
- return;
90
- }
91
- const raw = await ctx.helpers.readJson(req);
92
- const parsed = EraseRequestSchema.safeParse(raw);
93
- if (!parsed.success) {
94
- if (parsed.error.issues.some((i) => i.path.length > 0 && i.path[0] === "requestId")) {
95
- sendError(res, 400, "request.request_id_required", "memory erasure requires a non-empty `requestId` string — it is this request's idempotency identity and the server never mints one for you (a retry must carry the SAME id)");
80
+ case "memory-erase": {
81
+ const raw = await ctx.helpers.readJson(req);
82
+ const parsed = EraseRequestSchema.safeParse(raw);
83
+ if (!parsed.success) {
84
+ if (parsed.error.issues.some((i) => i.path.length > 0 && i.path[0] === "requestId")) {
85
+ sendError(res, 400, "request.request_id_required", "memory erasure requires a non-empty `requestId` string — it is this request's idempotency identity and the server never mints one for you (a retry must carry the SAME id)");
86
+ return;
87
+ }
88
+ sendError(res, 400, "request.body_shape", "invalid memory erasure request — expected an object with a non-empty `requestId`, a `select` object, and an optional boolean `allowUnevidenced`");
89
+ return;
90
+ }
91
+ if (erasureInputSmugglesPrototypeKey(raw)) {
92
+ sendError(res, 400, "request.body_shape", "invalid memory erasure request — a key that rewrites an object's prototype (`__proto__`) is not a selector key; send exactly one of `select.ids` / `select.scope` / `select.sessionId`");
93
+ return;
94
+ }
95
+ try {
96
+ sendJson(res, 200, await faces.erase(raw));
97
+ }
98
+ catch (err) {
99
+ if (!sentClassifiedFailure(res, ctx, "erase", err))
100
+ sendError(res, 500, "internal.error", "memory erasure failed");
101
+ }
96
102
  return;
97
103
  }
98
- sendError(res, 400, "request.body_shape", "invalid memory erasure request — expected an object with a non-empty `requestId`, a `select` object, and an optional boolean `allowUnevidenced`");
99
- return;
100
- }
101
- if (erasureInputSmugglesPrototypeKey(raw)) {
102
- sendError(res, 400, "request.body_shape", "invalid memory erasure request — a key that rewrites an object's prototype (`__proto__`) is not a selector key; send exactly one of `select.ids` / `select.scope` / `select.sessionId`");
103
- return;
104
- }
105
- try {
106
- sendJson(res, 200, await faces.erase(raw));
107
- }
108
- catch (err) {
109
- if (!sentClassifiedFailure(res, ctx, "erase", err))
110
- sendError(res, 500, "internal.error", "memory erasure failed");
104
+ default:
105
+ return assertNever(match.decl.id);
111
106
  }
112
107
  }
108
+ export const MEMORY_COMPLIANCE_ROUTES = [
109
+ { id: "memory-erase", path: MEMORY_ERASE_PATH, credentialGated: ["POST"], methods: ["POST"] },
110
+ { id: "memory-entry-provenance", pattern: /^\/v1\/memory\/entries\/(?<entryId>[^/]+)\/provenance$/, label: "/v1/memory/entries/:entryId/provenance", credentialGated: ["GET"], methods: ["GET"] },
111
+ ];
113
112
  //# sourceMappingURL=memory-compliance.js.map
@@ -53,8 +53,20 @@
53
53
  * 分层:本模块不值 import `server.ts`(那条边闭合运行时装载环),只 `import type`。
54
54
  */
55
55
  import type { IncomingMessage, ServerResponse } from "node:http";
56
- import type { RouteCtx } from "../route-ctx.js";
56
+ import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
57
57
  export declare const MEMORY_CONSOLIDATION_STATUS_PATH = "/v1/admin/memory/consolidation";
58
58
  export declare const MEMORY_CONSOLIDATION_RUN_PATH = "/v1/admin/memory/consolidation/run";
59
- export declare function handleMemoryConsolidation(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
59
+ export declare function handleMemoryConsolidation(req: IncomingMessage, res: ServerResponse, match: RouteMatch<MemoryConsolidationRouteId>, ctx: RouteCtx): Promise<void>;
60
+ export declare const MEMORY_CONSOLIDATION_ROUTES: readonly [{
61
+ readonly id: "memory-consolidation-run";
62
+ readonly path: "/v1/admin/memory/consolidation/run";
63
+ readonly billable: true;
64
+ readonly methods: readonly ["POST"];
65
+ }, {
66
+ readonly id: "memory-consolidation-status";
67
+ readonly path: "/v1/admin/memory/consolidation";
68
+ readonly methods: readonly ["GET"];
69
+ }];
70
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
71
+ export type MemoryConsolidationRouteId = RouteIdsOf<typeof MEMORY_CONSOLIDATION_ROUTES>;
60
72
  //# sourceMappingURL=memory-consolidation.d.ts.map
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { ControlPlaneCorruptError } from "@sema-agent/core";
3
3
  import { sendJson, sendError } from "../send.js";
4
4
  import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
5
+ import { assertNever } from "../route-ctx.js";
5
6
  export const MEMORY_CONSOLIDATION_STATUS_PATH = "/v1/admin/memory/consolidation";
6
7
  export const MEMORY_CONSOLIDATION_RUN_PATH = "/v1/admin/memory/consolidation/run";
7
8
  const RunRequestSchema = z.object({ scope: z.string().optional() }).strict();
@@ -69,21 +70,9 @@ function codeOf(err) {
69
70
  const { code } = err;
70
71
  return typeof code === "string" ? code : undefined;
71
72
  }
72
- export async function handleMemoryConsolidation(req, res, url, ctx) {
73
- const miss = { fell: false };
74
- await handleMemoryConsolidationBody(req, res, url, ctx, miss);
75
- return !miss.fell;
76
- }
77
- async function handleMemoryConsolidationBody(req, res, url, ctx, miss) {
73
+ export async function handleMemoryConsolidation(req, res, match, ctx) {
78
74
  const { deps } = ctx;
79
- const path = url.split("?")[0] ?? url;
80
- const isRun = req.method === "POST" && path === MEMORY_CONSOLIDATION_RUN_PATH;
81
- const isStatus = req.method === "GET" && path === MEMORY_CONSOLIDATION_STATUS_PATH;
82
75
  const faces = deps.memoryConsolidation;
83
- if ((!isRun && !isStatus) || faces === undefined) {
84
- miss.fell = true;
85
- return;
86
- }
87
76
  const principal = gatedPrincipal(req, deps.config);
88
77
  if (deps.config.requirePrincipal && !principal) {
89
78
  sendPrincipalRefusal(res, req, deps.config);
@@ -93,63 +82,74 @@ async function handleMemoryConsolidationBody(req, res, url, ctx, miss) {
93
82
  sendError(res, 403, "auth.operator_only", "memory consolidation is operator-only: the caller principal is not in OPERATOR_PRINCIPALS (an empty list means NO one is an operator — configure it to enable these endpoints)");
94
83
  return;
95
84
  }
96
- if (isStatus) {
97
- const q = new URL(req.url ?? "", "http://x").searchParams.get("scope") ?? undefined;
98
- const decided = decideScope(q, faces.scopes);
99
- const scope = "scope" in decided ? decided.scope : undefined;
100
- const row = scope !== undefined ? faces.lastRun(scope) : undefined;
101
- sendJson(res, 200, {
102
- enabled: true,
103
- seat: faces.seat,
104
- model: faces.model,
105
- scopes: faces.scopes,
106
- ...(scope !== undefined ? { scope } : {}),
107
- ...(row !== undefined ? { lastRun: buildRunRowSummary(row) } : {}),
108
- });
109
- return;
110
- }
111
- const parsed = RunRequestSchema.safeParse(await ctx.helpers.readJson(req));
112
- if (!parsed.success) {
113
- sendError(res, 400, "request.body_shape", "invalid memory consolidation request — expected an object with an optional `scope` string");
114
- return;
115
- }
116
- const decided = decideScope(parsed.data.scope, faces.scopes);
117
- if ("refusal" in decided) {
118
- switch (decided.refusal) {
119
- case "blank":
120
- sendError(res, 400, "request.body_shape", "invalid memory consolidation request — `scope` must be a non-empty string naming the memory scope to fold");
121
- return;
122
- case "none-configured":
123
- sendError(res, 400, "request.body_shape", "invalid memory consolidation request — `scope` is required: this deployment configured no MEMORY_CONSOLIDATION_SCOPES, so there is no single library to default to, and one consolidation run reads a WHOLE scope (~1e5 prompt tokens). Send a `scope`, or set MEMORY_CONSOLIDATION_SCOPES to the one scope this worker folds");
124
- return;
125
- case "off-roster":
126
- sendError(res, 400, "request.body_shape", `invalid memory consolidation request — this worker declares the scopes it folds in MEMORY_CONSOLIDATION_SCOPES (${faces.scopes.join(", ")}) and the requested scope is not one of them; a stale or mistyped scope would spend a whole-library distillation on a different library and supersede its entries. Send one of the declared scopes, add this one to MEMORY_CONSOLIDATION_SCOPES, or unset that knob to fold any scope on request`);
127
- return;
128
- case "ambiguous":
129
- sendError(res, 400, "request.body_shape", `invalid memory consolidation request — a scope is required: MEMORY_CONSOLIDATION_SCOPES lists more than one scope (${faces.scopes.join(", ")}) and the server never picks which library to spend a whole-library distillation on`);
130
- return;
131
- default: {
132
- const unreachable = decided.refusal;
133
- throw new Error(`unhandled consolidation scope refusal: ${String(unreachable)}`);
134
- }
135
- }
136
- }
137
- try {
138
- const r = await faces.run(decided.scope);
139
- sendJson(res, 200, buildRunSummary(r));
140
- }
141
- catch (err) {
142
- const coreCode = codeOf(err);
143
- if (coreCode !== undefined) {
144
- sendError(res, 409, "memory.consolidation_refused", `memory consolidation refused (${coreCode}): ${err instanceof Error ? err.message : String(err)}`);
85
+ switch (match.decl.id) {
86
+ case "memory-consolidation-status": {
87
+ const q = new URLSearchParams(match.search).get("scope") ?? undefined;
88
+ const decided = decideScope(q, faces.scopes);
89
+ const scope = "scope" in decided ? decided.scope : undefined;
90
+ const row = scope !== undefined ? faces.lastRun(scope) : undefined;
91
+ sendJson(res, 200, {
92
+ enabled: true,
93
+ seat: faces.seat,
94
+ model: faces.model,
95
+ scopes: faces.scopes,
96
+ ...(scope !== undefined ? { scope } : {}),
97
+ ...(row !== undefined ? { lastRun: buildRunRowSummary(row) } : {}),
98
+ });
145
99
  return;
146
100
  }
147
- if (err instanceof ControlPlaneCorruptError) {
148
- sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
101
+ case "memory-consolidation-run": {
102
+ const parsed = RunRequestSchema.safeParse(await ctx.helpers.readJson(req));
103
+ if (!parsed.success) {
104
+ sendError(res, 400, "request.body_shape", "invalid memory consolidation request — expected an object with an optional `scope` string");
105
+ return;
106
+ }
107
+ const decided = decideScope(parsed.data.scope, faces.scopes);
108
+ if ("refusal" in decided) {
109
+ switch (decided.refusal) {
110
+ case "blank":
111
+ sendError(res, 400, "request.body_shape", "invalid memory consolidation request — `scope` must be a non-empty string naming the memory scope to fold");
112
+ return;
113
+ case "none-configured":
114
+ sendError(res, 400, "request.body_shape", "invalid memory consolidation request — `scope` is required: this deployment configured no MEMORY_CONSOLIDATION_SCOPES, so there is no single library to default to, and one consolidation run reads a WHOLE scope (~1e5 prompt tokens). Send a `scope`, or set MEMORY_CONSOLIDATION_SCOPES to the one scope this worker folds");
115
+ return;
116
+ case "off-roster":
117
+ sendError(res, 400, "request.body_shape", `invalid memory consolidation request — this worker declares the scopes it folds in MEMORY_CONSOLIDATION_SCOPES (${faces.scopes.join(", ")}) and the requested scope is not one of them; a stale or mistyped scope would spend a whole-library distillation on a different library and supersede its entries. Send one of the declared scopes, add this one to MEMORY_CONSOLIDATION_SCOPES, or unset that knob to fold any scope on request`);
118
+ return;
119
+ case "ambiguous":
120
+ sendError(res, 400, "request.body_shape", `invalid memory consolidation request — a scope is required: MEMORY_CONSOLIDATION_SCOPES lists more than one scope (${faces.scopes.join(", ")}) and the server never picks which library to spend a whole-library distillation on`);
121
+ return;
122
+ default: {
123
+ const unreachable = decided.refusal;
124
+ throw new Error(`unhandled consolidation scope refusal: ${String(unreachable)}`);
125
+ }
126
+ }
127
+ }
128
+ try {
129
+ const r = await faces.run(decided.scope);
130
+ sendJson(res, 200, buildRunSummary(r));
131
+ }
132
+ catch (err) {
133
+ const coreCode = codeOf(err);
134
+ if (coreCode !== undefined) {
135
+ sendError(res, 409, "memory.consolidation_refused", `memory consolidation refused (${coreCode}): ${err instanceof Error ? err.message : String(err)}`);
136
+ return;
137
+ }
138
+ if (err instanceof ControlPlaneCorruptError) {
139
+ sendError(res, 500, "internal.memory_control_plane_corrupt", err.message);
140
+ return;
141
+ }
142
+ ctx.deps.logger?.warn?.("memory_consolidation_run_failed", { scope: decided.scope, err: String(err) });
143
+ sendError(res, 500, "internal.error", "memory consolidation run failed");
144
+ }
149
145
  return;
150
146
  }
151
- ctx.deps.logger?.warn?.("memory_consolidation_run_failed", { scope: decided.scope, err: String(err) });
152
- sendError(res, 500, "internal.error", "memory consolidation run failed");
147
+ default:
148
+ return assertNever(match.decl.id);
153
149
  }
154
150
  }
151
+ export const MEMORY_CONSOLIDATION_ROUTES = [
152
+ { id: "memory-consolidation-run", path: MEMORY_CONSOLIDATION_RUN_PATH, billable: true, methods: ["POST"] },
153
+ { id: "memory-consolidation-status", path: MEMORY_CONSOLIDATION_STATUS_PATH, methods: ["GET"] },
154
+ ];
155
155
  //# sourceMappingURL=memory-consolidation.js.map
@@ -79,11 +79,9 @@
79
79
  */
80
80
  import type { IncomingMessage, ServerResponse } from "node:http";
81
81
  import { type OriginClearanceRow } from "@sema-agent/core";
82
- import type { RouteCtx } from "../route-ctx.js";
82
+ import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
83
83
  export declare const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
84
84
  export declare const MEMORY_ORIGIN_CLEARANCES_PATH = "/v1/memory/origin/clearances";
85
- /** 带参写口 ⇒ 路由声明表(`http/route-table.ts`)里走 `pattern` 行,不是 `path` 行。 */
86
- export declare const MEMORY_ORIGIN_CLEAR_RE: RegExp;
87
85
  /**
88
86
  * 一条清标行 → wire 的**显式白名单**(F-7)。全文理由见文件头「为什么是白名单」。
89
87
  *
@@ -120,5 +118,26 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
120
118
  taint: "external";
121
119
  };
122
120
  };
123
- export declare function handleMemoryOrigin(req: IncomingMessage, res: ServerResponse, url: string, ctx: RouteCtx): Promise<boolean>;
121
+ export declare function handleMemoryOrigin(req: IncomingMessage, res: ServerResponse, match: RouteMatch<MemoryOriginRouteId>, ctx: RouteCtx): Promise<void>;
122
+ export declare const MEMORY_ORIGIN_ROUTES: readonly [{
123
+ readonly id: "memory-origin-external";
124
+ readonly path: "/v1/memory/origin/external";
125
+ readonly credentialGated: readonly ["GET"];
126
+ readonly methods: readonly ["GET"];
127
+ }, {
128
+ readonly id: "memory-origin-clearances";
129
+ readonly path: "/v1/memory/origin/clearances";
130
+ readonly credentialGated: readonly ["GET"];
131
+ readonly methods: readonly ["GET"];
132
+ }, {
133
+ readonly id: "memory-origin-entry-clear";
134
+ readonly pattern: RegExp;
135
+ readonly label: "/v1/memory/origin/entries/:entryId/clear";
136
+ readonly methods: readonly ["POST"];
137
+ }, {
138
+ readonly prefix: "/v1/memory/origin/entries/";
139
+ readonly credentialGated: readonly ["POST"];
140
+ }];
141
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
142
+ export type MemoryOriginRouteId = RouteIdsOf<typeof MEMORY_ORIGIN_ROUTES>;
124
143
  //# sourceMappingURL=memory-origin.d.ts.map