@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.
- package/dist/boot/stage-07-capability-layer.js +2 -1
- package/dist/capabilities/skills.d.ts +10 -0
- package/dist/capabilities/skills.js +3 -1
- package/dist/http/admission.js +3 -1
- package/dist/http/dispatch.js +28 -8
- package/dist/http/resume-legs.js +4 -3
- package/dist/http/route-ctx.d.ts +101 -4
- package/dist/http/route-ctx.js +4 -0
- package/dist/http/route-table.d.ts +71 -43
- package/dist/http/route-table.js +111 -187
- package/dist/http/routes/a2a-serve.d.ts +18 -3
- package/dist/http/routes/a2a-serve.js +25 -23
- package/dist/http/routes/admin-config-refresh.d.ts +9 -2
- package/dist/http/routes/admin-config-refresh.js +32 -31
- package/dist/http/routes/admin-drain.d.ts +9 -2
- package/dist/http/routes/admin-drain.js +32 -31
- package/dist/http/routes/admin-memory-optout.d.ts +28 -2
- package/dist/http/routes/admin-memory-optout.js +27 -40
- package/dist/http/routes/adoption.d.ts +15 -2
- package/dist/http/routes/adoption.js +52 -52
- package/dist/http/routes/agents-roster.d.ts +9 -2
- package/dist/http/routes/agents-roster.js +82 -80
- package/dist/http/routes/approvals-assistant.d.ts +58 -5
- package/dist/http/routes/approvals-assistant.js +38 -39
- package/dist/http/routes/attachments.d.ts +22 -2
- package/dist/http/routes/attachments.js +81 -72
- package/dist/http/routes/capabilities.d.ts +21 -2
- package/dist/http/routes/capabilities.js +174 -170
- package/dist/http/routes/config-catalog.d.ts +9 -2
- package/dist/http/routes/config-catalog.js +28 -31
- package/dist/http/routes/devices.d.ts +24 -2
- package/dist/http/routes/devices.js +19 -21
- package/dist/http/routes/diagnostics.d.ts +9 -2
- package/dist/http/routes/diagnostics.js +41 -43
- package/dist/http/routes/fleet.d.ts +11 -2
- package/dist/http/routes/fleet.js +23 -22
- package/dist/http/routes/images.d.ts +63 -9
- package/dist/http/routes/images.js +53 -46
- package/dist/http/routes/leader.d.ts +20 -2
- package/dist/http/routes/leader.js +43 -40
- package/dist/http/routes/memory-bundle.d.ts +15 -2
- package/dist/http/routes/memory-bundle.js +38 -38
- package/dist/http/routes/memory-compliance.d.ts +16 -4
- package/dist/http/routes/memory-compliance.js +44 -45
- package/dist/http/routes/memory-consolidation.d.ts +14 -2
- package/dist/http/routes/memory-consolidation.js +66 -66
- package/dist/http/routes/memory-origin.d.ts +23 -4
- package/dist/http/routes/memory-origin.js +65 -65
- package/dist/http/routes/memory-policy.d.ts +21 -2
- package/dist/http/routes/memory-policy.js +119 -115
- package/dist/http/routes/notify-wake.d.ts +21 -11
- package/dist/http/routes/notify-wake.js +118 -116
- package/dist/http/routes/observability.d.ts +16 -2
- package/dist/http/routes/observability.js +54 -51
- package/dist/http/routes/retention-ops.d.ts +19 -2
- package/dist/http/routes/retention-ops.js +20 -22
- package/dist/http/routes/rules.d.ts +36 -2
- package/dist/http/routes/rules.js +22 -24
- package/dist/http/routes/run-memory-capture.d.ts +10 -3
- package/dist/http/routes/run-memory-capture.js +84 -84
- package/dist/http/routes/runs.d.ts +102 -22
- package/dist/http/routes/runs.js +79 -96
- package/dist/http/routes/session-sync.d.ts +41 -2
- package/dist/http/routes/session-sync.js +26 -24
- package/dist/http/routes/sessions-list.d.ts +16 -2
- package/dist/http/routes/sessions-list.js +78 -75
- package/dist/http/routes/sessions.d.ts +77 -2
- package/dist/http/routes/sessions.js +50 -47
- package/dist/http/routes/shared-memory.d.ts +19 -2
- package/dist/http/routes/shared-memory.js +65 -63
- package/dist/http/routes/side-query.d.ts +11 -2
- package/dist/http/routes/side-query.js +81 -79
- package/dist/http/routes/tasks.d.ts +17 -2
- package/dist/http/routes/tasks.js +18 -11
- package/dist/http/routes/trace-usage.d.ts +40 -2
- package/dist/http/routes/trace-usage.js +46 -35
- package/dist/http/routes/workflows.d.ts +35 -3
- package/dist/http/routes/workflows.js +29 -29
- package/dist/http/wire-types.d.ts +15 -9
- package/dist/parked-decide.d.ts +9 -0
- package/dist/parked-decide.js +3 -3
- package/dist/run-local.js +2 -1
- package/dist/spec-fields.js +33 -30
- package/dist/task-cwd.d.ts +21 -0
- package/dist/task-cwd.js +3 -0
- package/dist/trace/core-keyset-guard.d.ts +6 -1
- package/dist/trace/project.d.ts +20 -0
- package/dist/trace/project.js +15 -1
- package/package.json +2 -2
|
@@ -3,9 +3,9 @@ import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
4
|
import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
|
|
5
5
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
6
|
+
import { assertNever } from "../route-ctx.js";
|
|
6
7
|
export const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
|
|
7
8
|
export const MEMORY_ORIGIN_CLEARANCES_PATH = "/v1/memory/origin/clearances";
|
|
8
|
-
export const MEMORY_ORIGIN_CLEAR_RE = /^\/v1\/memory\/origin\/entries\/([^/]+)\/clear$/;
|
|
9
9
|
const ClearRequestSchema = z.object({
|
|
10
10
|
requestId: z.string().min(1),
|
|
11
11
|
reason: z.string(),
|
|
@@ -106,21 +106,8 @@ function parseScopes(raw) {
|
|
|
106
106
|
}
|
|
107
107
|
return out.length > 0 ? out : null;
|
|
108
108
|
}
|
|
109
|
-
export async function handleMemoryOrigin(req, res,
|
|
110
|
-
const miss = { fell: false };
|
|
111
|
-
await handleMemoryOriginBody(req, res, url, ctx, miss);
|
|
112
|
-
return !miss.fell;
|
|
113
|
-
}
|
|
114
|
-
async function handleMemoryOriginBody(req, res, url, ctx, miss) {
|
|
109
|
+
export async function handleMemoryOrigin(req, res, match, ctx) {
|
|
115
110
|
const { deps } = ctx;
|
|
116
|
-
const path = url.split("?")[0] ?? url;
|
|
117
|
-
const isExternal = req.method === "GET" && path === MEMORY_ORIGIN_EXTERNAL_PATH;
|
|
118
|
-
const isClearances = req.method === "GET" && path === MEMORY_ORIGIN_CLEARANCES_PATH;
|
|
119
|
-
const clearMatch = req.method === "POST" ? MEMORY_ORIGIN_CLEAR_RE.exec(path) : null;
|
|
120
|
-
if (!isExternal && !isClearances && clearMatch === null) {
|
|
121
|
-
miss.fell = true;
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
111
|
const principal = gatedPrincipal(req, deps.config);
|
|
125
112
|
if (deps.config.requirePrincipal && !principal) {
|
|
126
113
|
sendPrincipalRefusal(res, req, deps.config);
|
|
@@ -135,61 +122,74 @@ async function handleMemoryOriginBody(req, res, url, ctx, miss) {
|
|
|
135
122
|
sendError(res, 501, "capability.memory_engine_required", "memory origin marking and clearance are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the clearance ledger and its write-ahead custody rows live in that plane, and on a backend without it they would land on one replica's local disk");
|
|
136
123
|
return;
|
|
137
124
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
125
|
+
switch (match.decl.id) {
|
|
126
|
+
case "memory-origin-external": {
|
|
127
|
+
const scopes = parseScopes(new URLSearchParams(match.search).getAll("scopes"));
|
|
128
|
+
if (scopes === null) {
|
|
129
|
+
sendError(res, 400, "request.query_invalid", "`scopes` is required and must name at least one memory scope (comma-separated, e.g. `?scopes=user:alice,org:acme`) — this deployment has no scope-enumeration read face yet, so name the same scope keys you use on `GET /v1/memory/export?scope=`; an answer here covers ONLY the scopes you named and an empty result must never be read as \"the store is clean\"");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const fence = buildOriginFence(await faces.listExternal(scopes), readOriginAware(new URLSearchParams(match.search)));
|
|
134
|
+
sendJson(res, 200, { scopes, entries: fence.entries, ...(fence.withheldOriginTagged !== undefined ? { withheldOriginTagged: fence.withheldOriginTagged } : {}) });
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (!sentClassifiedFailure(res, ctx, "external", err))
|
|
138
|
+
sendError(res, 500, "internal.error", "memory external-origin listing failed");
|
|
139
|
+
}
|
|
142
140
|
return;
|
|
143
141
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
if (isClearances) {
|
|
155
|
-
try {
|
|
156
|
-
sendJson(res, 200, { clearances: faces.listClearances().map(buildClearanceRowWire) });
|
|
157
|
-
}
|
|
158
|
-
catch (err) {
|
|
159
|
-
if (!sentClassifiedFailure(res, ctx, "clearances", err))
|
|
160
|
-
sendError(res, 500, "internal.error", "memory origin clearance listing failed");
|
|
142
|
+
case "memory-origin-clearances": {
|
|
143
|
+
try {
|
|
144
|
+
sendJson(res, 200, { clearances: faces.listClearances().map(buildClearanceRowWire) });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (!sentClassifiedFailure(res, ctx, "clearances", err))
|
|
148
|
+
sendError(res, 500, "internal.error", "memory origin clearance listing failed");
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
161
151
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
152
|
+
case "memory-origin-entry-clear": {
|
|
153
|
+
const entryId = ctx.helpers.safeDecode(match.params.entryId);
|
|
154
|
+
if (entryId === null) {
|
|
155
|
+
sendError(res, 400, "request.id_invalid", "invalid memory entry id");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const raw = await ctx.helpers.readJson(req);
|
|
159
|
+
const parsed = ClearRequestSchema.safeParse(raw);
|
|
160
|
+
if (!parsed.success) {
|
|
161
|
+
if (parsed.error.issues.some((i) => i.path.length > 0 && i.path[0] === "requestId")) {
|
|
162
|
+
sendError(res, 400, "request.request_id_required", "memory origin clearance requires a non-empty `requestId` string — it is the audit attribution of WHO cleared this marker and the server never mints one for you");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
sendError(res, 400, "request.body_shape", "invalid memory origin clearance request — expected an object with a non-empty `requestId` and a `reason` string (whether an EMPTY reason is acceptable is the engine's judgment, not this layer's)");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const receipt = await faces.clear(entryId, { requestId: parsed.data.requestId, reason: parsed.data.reason });
|
|
170
|
+
ctx.deps.logger?.info?.("memory_origin_cleared", {
|
|
171
|
+
principal: principal ?? null,
|
|
172
|
+
entryId,
|
|
173
|
+
clearanceId: receipt.clearanceId,
|
|
174
|
+
claimedRequestId: parsed.data.requestId,
|
|
175
|
+
landedSlug: receipt.landedSlug,
|
|
176
|
+
});
|
|
177
|
+
sendJson(res, 200, receipt);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (!sentClassifiedFailure(res, ctx, "clear", err))
|
|
181
|
+
sendError(res, 500, "internal.error", "memory origin clearance failed");
|
|
182
|
+
}
|
|
174
183
|
return;
|
|
175
184
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
const receipt = await faces.clear(entryId, { requestId: parsed.data.requestId, reason: parsed.data.reason });
|
|
181
|
-
ctx.deps.logger?.info?.("memory_origin_cleared", {
|
|
182
|
-
principal: principal ?? null,
|
|
183
|
-
entryId,
|
|
184
|
-
clearanceId: receipt.clearanceId,
|
|
185
|
-
claimedRequestId: parsed.data.requestId,
|
|
186
|
-
landedSlug: receipt.landedSlug,
|
|
187
|
-
});
|
|
188
|
-
sendJson(res, 200, receipt);
|
|
189
|
-
}
|
|
190
|
-
catch (err) {
|
|
191
|
-
if (!sentClassifiedFailure(res, ctx, "clear", err))
|
|
192
|
-
sendError(res, 500, "internal.error", "memory origin clearance failed");
|
|
185
|
+
default:
|
|
186
|
+
return assertNever(match.decl.id);
|
|
193
187
|
}
|
|
194
188
|
}
|
|
189
|
+
export const MEMORY_ORIGIN_ROUTES = [
|
|
190
|
+
{ id: "memory-origin-external", path: MEMORY_ORIGIN_EXTERNAL_PATH, credentialGated: ["GET"], methods: ["GET"] },
|
|
191
|
+
{ id: "memory-origin-clearances", path: MEMORY_ORIGIN_CLEARANCES_PATH, credentialGated: ["GET"], methods: ["GET"] },
|
|
192
|
+
{ id: "memory-origin-entry-clear", pattern: /^\/v1\/memory\/origin\/entries\/(?<entryId>[^/]+)\/clear$/, label: "/v1/memory/origin/entries/:entryId/clear", methods: ["POST"] },
|
|
193
|
+
{ prefix: "/v1/memory/origin/entries/", credentialGated: ["POST"] },
|
|
194
|
+
];
|
|
195
195
|
//# sourceMappingURL=memory-origin.js.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 越界一律 404(零存在性 oracle)。本域零可变状态。
|
|
7
7
|
*/
|
|
8
8
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
9
|
-
import type { RouteCtx } from "../route-ctx.js";
|
|
9
|
+
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
10
10
|
/**
|
|
11
11
|
* [ref] 件3([ref] A2 车立的欠账,[ref] 我方认领)—— `GET /v1/memory/export` 的 keyset 分页上限。
|
|
12
12
|
*
|
|
@@ -20,5 +20,24 @@ import type { RouteCtx } from "../route-ctx.js";
|
|
|
20
20
|
* 本形解决,库侧的读放大另案。
|
|
21
21
|
*/
|
|
22
22
|
export declare const MEMORY_EXPORT_MAX_LIMIT = 500;
|
|
23
|
-
|
|
23
|
+
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
24
|
+
* 的语义原样 =「本域已应答」;走到函数尾才是「没匹配上」,置 `miss.fell` 交回装配器继续下一个域。
|
|
25
|
+
* 跨域的东西(deps / 助手 / 单域状态)一律在首行解构出来,于是下面每一行都逐字不变。 */
|
|
26
|
+
export declare function handleMemoryPolicy(req: IncomingMessage, res: ServerResponse, match: RouteMatch<MemoryPolicyRouteId>, ctx: RouteCtx): Promise<void>;
|
|
27
|
+
export declare const MEMORY_POLICY_ROUTES: readonly [{
|
|
28
|
+
readonly id: "memory-export";
|
|
29
|
+
readonly path: "/v1/memory/export";
|
|
30
|
+
readonly methods: readonly ["GET"];
|
|
31
|
+
}, {
|
|
32
|
+
readonly id: "policy";
|
|
33
|
+
readonly path: "/v1/policy";
|
|
34
|
+
readonly methods: readonly ["GET"];
|
|
35
|
+
}, {
|
|
36
|
+
readonly id: "memory-sync-scope";
|
|
37
|
+
readonly pattern: RegExp;
|
|
38
|
+
readonly label: "/v1/memory/sync/:scope";
|
|
39
|
+
readonly methods: readonly ["POST"];
|
|
40
|
+
}];
|
|
41
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
42
|
+
export type MemoryPolicyRouteId = RouteIdsOf<typeof MEMORY_POLICY_ROUTES>;
|
|
24
43
|
//# sourceMappingURL=memory-policy.d.ts.map
|
|
@@ -3,6 +3,7 @@ import { parseMemorySyncRequest } from "../../memory-sync.js";
|
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
4
|
import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
|
|
5
5
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
6
|
+
import { assertNever } from "../route-ctx.js";
|
|
6
7
|
const ORG_PREFIX = "org:";
|
|
7
8
|
export const MEMORY_EXPORT_MAX_LIMIT = 500;
|
|
8
9
|
function encodeExportCursor(c) {
|
|
@@ -60,133 +61,136 @@ function sendScopeGateRefusal(res, gate) {
|
|
|
60
61
|
sendError(res, 404, "not_found.memory_scope", "not found");
|
|
61
62
|
return true;
|
|
62
63
|
}
|
|
63
|
-
export async function handleMemoryPolicy(req, res,
|
|
64
|
-
const miss = { fell: false };
|
|
65
|
-
await handleMemoryPolicyBody(req, res, url, ctx, miss);
|
|
66
|
-
return !miss.fell;
|
|
67
|
-
}
|
|
68
|
-
async function handleMemoryPolicyBody(req, res, url, ctx, miss) {
|
|
64
|
+
export async function handleMemoryPolicy(req, res, match, ctx) {
|
|
69
65
|
const { deps } = ctx;
|
|
70
66
|
const { readJson } = ctx.helpers;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
67
|
+
switch (match.decl.id) {
|
|
68
|
+
case "memory-export": {
|
|
69
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
70
|
+
if (!principal) {
|
|
71
|
+
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (deps.config.memoryEngineBackend === "file" || !deps.memoryExport) {
|
|
75
|
+
sendError(res, 501, "capability.memory_store_required", "memory export requires a DB memory backend (MEMORY_ENGINE_BACKEND=pg|tidb)");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const q = new URLSearchParams(match.search);
|
|
79
|
+
const scope = q.get("scope");
|
|
80
|
+
if (!scope) {
|
|
81
|
+
sendError(res, 400, "request.query_invalid", "missing ?scope=<key>");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "read")))
|
|
85
|
+
return;
|
|
86
|
+
const limitRaw = q.get("limit");
|
|
87
|
+
if (limitRaw !== null && !(/^[0-9]+$/.test(limitRaw) && Number(limitRaw) >= 1 && Number(limitRaw) <= MEMORY_EXPORT_MAX_LIMIT)) {
|
|
88
|
+
sendError(res, 400, "request.query_invalid", `limit must be a decimal integer in 1..${MEMORY_EXPORT_MAX_LIMIT}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const beforeRaw = q.get("before");
|
|
92
|
+
if (beforeRaw !== null && limitRaw === null) {
|
|
93
|
+
sendError(res, 400, "request.query_invalid", "before requires an explicit limit — without one this endpoint returns the whole scope by contract");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const decoded = beforeRaw === null ? undefined : decodeExportCursor(beforeRaw);
|
|
97
|
+
if (decoded !== undefined && !decoded.ok) {
|
|
98
|
+
sendError(res, 400, "request.query_invalid", "before must be the opaque `nextBefore` cursor from a previous page of this endpoint", { detail: decoded.why });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const fence = buildOriginFence(await deps.memoryExport(scope), readOriginAware(new URLSearchParams(match.search)));
|
|
103
|
+
const entries = fence.entries;
|
|
104
|
+
const withheld = fence.withheldOriginTagged !== undefined ? { withheldOriginTagged: fence.withheldOriginTagged } : {};
|
|
105
|
+
if (limitRaw === null) {
|
|
106
|
+
sendJson(res, 200, { scope, exportedAt: new Date().toISOString(), entries, ...withheld });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const sorted = [...entries].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
110
|
+
const after = decoded?.ok === true ? sorted.filter((e) => e.id > decoded.cursor.id) : sorted;
|
|
111
|
+
const page = after.slice(0, Number(limitRaw));
|
|
112
|
+
const last = page[page.length - 1];
|
|
113
|
+
const hasMore = last !== undefined && after.length > page.length;
|
|
114
|
+
sendJson(res, 200, {
|
|
115
|
+
scope,
|
|
116
|
+
exportedAt: new Date().toISOString(),
|
|
117
|
+
entries: page,
|
|
118
|
+
...(hasMore ? { truncated: true, nextBefore: encodeExportCursor({ id: last.id }) } : {}),
|
|
119
|
+
...withheld,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
deps.logger?.warn?.("memory_export_failed", { scope, err: String(err) });
|
|
124
|
+
sendError(res, 500, "internal.error", "memory export failed");
|
|
125
|
+
}
|
|
97
126
|
return;
|
|
98
127
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
case "memory-sync-scope": {
|
|
129
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
130
|
+
if (!principal) {
|
|
131
|
+
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (deps.config.memoryEngineBackend === "file" || !deps.memorySync) {
|
|
135
|
+
sendError(res, 501, "capability.memory_store_required", "memory sync requires a DB memory backend (MEMORY_ENGINE_BACKEND=pg|tidb)");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let scope;
|
|
139
|
+
try {
|
|
140
|
+
scope = decodeURIComponent(match.params.scope);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
sendError(res, 400, "memory_sync_invalid_scope", "malformed percent-encoding in :scope");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (scope.length === 0) {
|
|
147
|
+
sendError(res, 400, "memory_sync_invalid_scope", "missing :scope");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "write")))
|
|
151
|
+
return;
|
|
152
|
+
const body = await readJson(req);
|
|
153
|
+
const parsed = parseMemorySyncRequest(body, scope);
|
|
154
|
+
if (!parsed.ok) {
|
|
155
|
+
sendError(res, 422, "memory_sync_invalid_body", parsed.error);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const out = await deps.memorySync(scope, parsed.value);
|
|
160
|
+
sendJson(res, 200, out);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
deps.logger?.warn?.("memory_sync_failed", { scope, peer: parsed.value.peer, err: String(err) });
|
|
164
|
+
sendError(res, 500, "internal.error", "memory sync failed");
|
|
165
|
+
}
|
|
102
166
|
return;
|
|
103
167
|
}
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (limitRaw === null) {
|
|
109
|
-
sendJson(res, 200, { scope, exportedAt: new Date().toISOString(), entries, ...withheld });
|
|
168
|
+
case "policy": {
|
|
169
|
+
const principal = gatedPrincipal(req, deps.config);
|
|
170
|
+
if (deps.config.requirePrincipal && !principal) {
|
|
171
|
+
sendPrincipalRefusal(res, req, deps.config);
|
|
110
172
|
return;
|
|
111
173
|
}
|
|
112
|
-
const sorted = [...entries].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
113
|
-
const after = decoded?.ok === true ? sorted.filter((e) => e.id > decoded.cursor.id) : sorted;
|
|
114
|
-
const page = after.slice(0, Number(limitRaw));
|
|
115
|
-
const last = page[page.length - 1];
|
|
116
|
-
const hasMore = last !== undefined && after.length > page.length;
|
|
117
174
|
sendJson(res, 200, {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
175
|
+
autonomy: deps.config.autonomy ?? null,
|
|
176
|
+
commandPolicy: deps.config.commandPolicy ?? [],
|
|
177
|
+
approvalRequire: deps.config.approvalRequire,
|
|
178
|
+
limits: {
|
|
179
|
+
maxTaskCostUsd: deps.config.maxTaskCostUsd,
|
|
180
|
+
maxTaskTokens: deps.config.maxTaskTokens,
|
|
181
|
+
maxPrincipalCostUsd: deps.config.maxPrincipalCostUsd,
|
|
182
|
+
costQuotaWindowSec: deps.config.costQuotaWindowSec,
|
|
183
|
+
},
|
|
123
184
|
});
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
deps.logger?.warn?.("memory_export_failed", { scope, err: String(err) });
|
|
127
|
-
sendError(res, 500, "internal.error", "memory export failed");
|
|
128
|
-
}
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (req.method === "POST" && url.startsWith("/v1/memory/sync/")) {
|
|
132
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
133
|
-
if (!principal) {
|
|
134
|
-
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
if (deps.config.memoryEngineBackend === "file" || !deps.memorySync) {
|
|
138
|
-
sendError(res, 501, "capability.memory_store_required", "memory sync requires a DB memory backend (MEMORY_ENGINE_BACKEND=pg|tidb)");
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
let scope;
|
|
142
|
-
try {
|
|
143
|
-
scope = decodeURIComponent(url.slice("/v1/memory/sync/".length));
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
sendError(res, 400, "memory_sync_invalid_scope", "malformed percent-encoding in :scope");
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (scope.length === 0) {
|
|
150
|
-
sendError(res, 400, "memory_sync_invalid_scope", "missing :scope");
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (sendScopeGateRefusal(res, await gateMemoryScope(deps, principal, scope, "write")))
|
|
154
|
-
return;
|
|
155
|
-
const body = await readJson(req);
|
|
156
|
-
const parsed = parseMemorySyncRequest(body, scope);
|
|
157
|
-
if (!parsed.ok) {
|
|
158
|
-
sendError(res, 422, "memory_sync_invalid_body", parsed.error);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
try {
|
|
162
|
-
const out = await deps.memorySync(scope, parsed.value);
|
|
163
|
-
sendJson(res, 200, out);
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
deps.logger?.warn?.("memory_sync_failed", { scope, peer: parsed.value.peer, err: String(err) });
|
|
167
|
-
sendError(res, 500, "internal.error", "memory sync failed");
|
|
168
|
-
}
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (url === "/v1/policy" && req.method === "GET") {
|
|
172
|
-
const principal = gatedPrincipal(req, deps.config);
|
|
173
|
-
if (deps.config.requirePrincipal && !principal) {
|
|
174
|
-
sendPrincipalRefusal(res, req, deps.config);
|
|
175
185
|
return;
|
|
176
186
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
commandPolicy: deps.config.commandPolicy ?? [],
|
|
180
|
-
approvalRequire: deps.config.approvalRequire,
|
|
181
|
-
limits: {
|
|
182
|
-
maxTaskCostUsd: deps.config.maxTaskCostUsd,
|
|
183
|
-
maxTaskTokens: deps.config.maxTaskTokens,
|
|
184
|
-
maxPrincipalCostUsd: deps.config.maxPrincipalCostUsd,
|
|
185
|
-
costQuotaWindowSec: deps.config.costQuotaWindowSec,
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
return;
|
|
187
|
+
default:
|
|
188
|
+
return assertNever(match.decl.id);
|
|
189
189
|
}
|
|
190
|
-
miss.fell = true;
|
|
191
190
|
}
|
|
191
|
+
export const MEMORY_POLICY_ROUTES = [
|
|
192
|
+
{ id: "memory-export", path: "/v1/memory/export", methods: ["GET"] },
|
|
193
|
+
{ id: "policy", path: "/v1/policy", methods: ["GET"] },
|
|
194
|
+
{ id: "memory-sync-scope", pattern: /^\/v1\/memory\/sync\/(?<scope>.*)$/, label: "/v1/memory/sync/:scope", methods: ["POST"] },
|
|
195
|
+
];
|
|
192
196
|
//# sourceMappingURL=memory-policy.js.map
|
|
@@ -9,17 +9,27 @@
|
|
|
9
9
|
* 都在腿里),而不是在本域自建第二条驱动路径。本域零可变状态。
|
|
10
10
|
*/
|
|
11
11
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
12
|
-
import { type RouteCtx } from "../route-ctx.js";
|
|
12
|
+
import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
13
13
|
/**
|
|
14
|
-
* `POST /v1/sessions/:id/wake`
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* 名单在 server.ts 里,wake 从来没进过名单,于是同一条 `driveResumeIntoRunLog` 模型腿
|
|
20
|
-
* 从 `/v1/assistant/tasks/:id/resume` 进来受三道门管、从 wake 进来一道都不受。
|
|
21
|
-
* 共用一个常量之后,再想漏就得两处一起改。
|
|
14
|
+
* 🔴 `POST /v1/sessions/:id/wake` 的匹配形与它的 `billable` 位**同住一行声明**(下面的
|
|
15
|
+
* `NOTIFY_WAKE_ROUTES`)。历史:2026-07-31 的缝合审查出这两件事**曾经不同步** —— 匹配形写死在本文件的
|
|
16
|
+
* 判路代码里、`isBillableSubmitPath` 的名单写在 `server.ts` 里,wake 从来没进过名单,于是同一条
|
|
17
|
+
* `driveResumeIntoRunLog` 模型腿从 `/v1/assistant/tasks/:id/resume` 进来受三道提交门管、从 wake 进来
|
|
18
|
+
* 一道都不受。S-146 让两件事变成同一行的两列,再想漏就得把一行拆成两处才做得到。
|
|
22
19
|
*/
|
|
23
|
-
export declare
|
|
24
|
-
export declare
|
|
20
|
+
export declare function handleNotifyWake(req: IncomingMessage, res: ServerResponse, match: RouteMatch<NotifyWakeRouteId>, ctx: RouteCtx): Promise<void>;
|
|
21
|
+
export declare const NOTIFY_WAKE_ROUTES: readonly [{
|
|
22
|
+
readonly id: "session-wake";
|
|
23
|
+
readonly pattern: RegExp;
|
|
24
|
+
readonly label: "/v1/sessions/:id/$verb";
|
|
25
|
+
readonly billable: true;
|
|
26
|
+
readonly methods: readonly ["POST"];
|
|
27
|
+
}, {
|
|
28
|
+
readonly id: "session-notify";
|
|
29
|
+
readonly pattern: RegExp;
|
|
30
|
+
readonly label: "/v1/sessions/:id/$verb";
|
|
31
|
+
readonly methods: readonly ["POST"];
|
|
32
|
+
}];
|
|
33
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
34
|
+
export type NotifyWakeRouteId = RouteIdsOf<typeof NOTIFY_WAKE_ROUTES>;
|
|
25
35
|
//# sourceMappingURL=notify-wake.d.ts.map
|