@sema-agent/server 7.79.3 → 7.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http/dispatch.js +28 -8
- 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 +26 -39
- 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 +37 -38
- 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 +71 -88
- package/dist/http/routes/session-sync.d.ts +41 -2
- package/dist/http/routes/session-sync.js +25 -23
- 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/package.json +1 -1
|
@@ -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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
@@ -7,6 +7,7 @@ import { recordFailOpen } from "../../observability/fail-open.js";
|
|
|
7
7
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_PERMISSION_RULES, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
|
|
8
8
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
9
9
|
import { gatedPrincipal, explicitOperatorOk, runOwnerReadable, sendPrincipalRefusal } from "../principal-gate.js";
|
|
10
|
+
import { assertNever } from "../route-ctx.js";
|
|
10
11
|
const MCP_STATUS_TTL_MS = 5_000;
|
|
11
12
|
const MCP_MATERIALIZE_TIMEOUT_MS = 10_000;
|
|
12
13
|
const A2A_STATUS_TTL_MS = 5_000;
|
|
@@ -61,26 +62,20 @@ async function readLastLegMcp(deps, sessionId, principal) {
|
|
|
61
62
|
}
|
|
62
63
|
return undefined;
|
|
63
64
|
}
|
|
64
|
-
export async function handleSessions(req, res,
|
|
65
|
-
const miss = { fell: false };
|
|
66
|
-
await handleSessionsBody(req, res, url, ctx, miss);
|
|
67
|
-
return !miss.fell;
|
|
68
|
-
}
|
|
69
|
-
async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
65
|
+
export async function handleSessions(req, res, match, ctx) {
|
|
70
66
|
const { deps } = ctx;
|
|
71
67
|
const { readJson, safeDecode, runOwnerOk, sessionOwnerScope, sessionOwnerScopeForWrite } = ctx.helpers;
|
|
72
68
|
const sse = ctx.local.sessions;
|
|
73
69
|
const { mcpStatusCache, a2aStatusCache } = ctx.local.sessions;
|
|
74
70
|
{
|
|
75
|
-
|
|
76
|
-
if (im && req.method === "GET") {
|
|
71
|
+
if (match.decl.id === "session-init") {
|
|
77
72
|
const principal = gatedPrincipal(req, deps.config);
|
|
78
73
|
if (deps.config.requirePrincipal && !principal) {
|
|
79
74
|
sendPrincipalRefusal(res, req, deps.config);
|
|
80
75
|
return;
|
|
81
76
|
}
|
|
82
77
|
sendJson(res, 200, {
|
|
83
|
-
sessionId:
|
|
78
|
+
sessionId: match.params.sessionId,
|
|
84
79
|
model: { provider: deps.config.model.provider, modelId: deps.config.model.id },
|
|
85
80
|
...(deps.config.autonomy ? { mode: deps.config.autonomy } : {}),
|
|
86
81
|
});
|
|
@@ -88,8 +83,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
88
83
|
}
|
|
89
84
|
}
|
|
90
85
|
{
|
|
91
|
-
|
|
92
|
-
if (stm && req.method === "GET") {
|
|
86
|
+
if (match.decl.id === "session-settings") {
|
|
93
87
|
const principal = gatedPrincipal(req, deps.config);
|
|
94
88
|
if (deps.config.requirePrincipal && !principal) {
|
|
95
89
|
sendPrincipalRefusal(res, req, deps.config);
|
|
@@ -109,14 +103,13 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
109
103
|
}
|
|
110
104
|
}
|
|
111
105
|
{
|
|
112
|
-
|
|
113
|
-
if (mm && req.method === "GET") {
|
|
106
|
+
if (match.decl.id === "session-mcp") {
|
|
114
107
|
const principal = gatedPrincipal(req, deps.config);
|
|
115
108
|
if (deps.config.requirePrincipal && !principal) {
|
|
116
109
|
sendPrincipalRefusal(res, req, deps.config);
|
|
117
110
|
return;
|
|
118
111
|
}
|
|
119
|
-
const lastLegMcp = await lastLegMcpFor(deps, safeDecode(
|
|
112
|
+
const lastLegMcp = await lastLegMcpFor(deps, safeDecode(match.params.sessionId), principal);
|
|
120
113
|
const specs = mcpForScenario(deps.config.mcpServers, deps.config.defaultScenario);
|
|
121
114
|
const cacheKey = `${deps.config.defaultScenario}::${principal ?? ""}`;
|
|
122
115
|
if (!specs || specs.length === 0) {
|
|
@@ -160,8 +153,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
160
153
|
}
|
|
161
154
|
}
|
|
162
155
|
{
|
|
163
|
-
|
|
164
|
-
if (am && req.method === "GET") {
|
|
156
|
+
if (match.decl.id === "session-a2a") {
|
|
165
157
|
if (deps.config.requirePrincipal && !gatedPrincipal(req, deps.config)) {
|
|
166
158
|
sendPrincipalRefusal(res, req, deps.config);
|
|
167
159
|
return;
|
|
@@ -220,15 +212,14 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
220
212
|
}
|
|
221
213
|
}
|
|
222
214
|
{
|
|
223
|
-
|
|
224
|
-
if (hm) {
|
|
215
|
+
if (match.decl.id === "session-head") {
|
|
225
216
|
const getLeafId = deps.sessionStorage?.getLeafId?.bind(deps.sessionStorage);
|
|
226
217
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
227
218
|
if (!getLeafId || !ownerOf) {
|
|
228
219
|
sendError(res, 501, "capability.session_store_required", "session head probe requires a durable session store (DB_BACKEND=mysql|pg|local)");
|
|
229
220
|
return;
|
|
230
221
|
}
|
|
231
|
-
const headSession = decodeURIComponent(
|
|
222
|
+
const headSession = decodeURIComponent(match.params.sessionId);
|
|
232
223
|
const getHead = deps.sessionStorage?.getHead?.bind(deps.sessionStorage);
|
|
233
224
|
let owner;
|
|
234
225
|
let leafId;
|
|
@@ -264,15 +255,14 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
264
255
|
}
|
|
265
256
|
}
|
|
266
257
|
{
|
|
267
|
-
|
|
268
|
-
if (em) {
|
|
258
|
+
if (match.decl.id === "session-events") {
|
|
269
259
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
270
260
|
const watch = deps.sessionWatch;
|
|
271
261
|
if (!deps.sessionStorage?.getLeafId || !ownerOf || !watch) {
|
|
272
262
|
sendError(res, 501, "capability.session_store_required", "session events require a durable session store (DB_BACKEND=mysql|pg|local)");
|
|
273
263
|
return;
|
|
274
264
|
}
|
|
275
|
-
const evSession = decodeURIComponent(
|
|
265
|
+
const evSession = decodeURIComponent(match.params.sessionId);
|
|
276
266
|
if (sse.sseConnections >= (deps.sessionEventsMaxConnections ?? 256)) {
|
|
277
267
|
res.setHeader("retry-after", "5");
|
|
278
268
|
sendError(res, 503, "state.sse_connection_cap", "session-events connection cap reached — fall back to /head polling");
|
|
@@ -333,7 +323,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
333
323
|
cleanup();
|
|
334
324
|
return;
|
|
335
325
|
}
|
|
336
|
-
const q = new
|
|
326
|
+
const q = new URLSearchParams(match.search);
|
|
337
327
|
const lastLeafId = q.get("lastLeafId");
|
|
338
328
|
let first = true;
|
|
339
329
|
let lastAccepted;
|
|
@@ -453,9 +443,8 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
453
443
|
}
|
|
454
444
|
}
|
|
455
445
|
{
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const sessionId = safeDecode(em[1]);
|
|
446
|
+
if (match.decl.id === "session-memory-erase") {
|
|
447
|
+
const sessionId = safeDecode(match.params.sessionId);
|
|
459
448
|
if (sessionId === null) {
|
|
460
449
|
sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
|
|
461
450
|
return;
|
|
@@ -513,9 +502,8 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
513
502
|
}
|
|
514
503
|
}
|
|
515
504
|
{
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const sessionId = safeDecode(msm[1]);
|
|
505
|
+
if (match.decl.id === "session-memory-status") {
|
|
506
|
+
const sessionId = safeDecode(match.params.sessionId);
|
|
519
507
|
if (sessionId === null) {
|
|
520
508
|
sendError(res, 400, "request.path_malformed", "session id segment is not valid percent-encoding");
|
|
521
509
|
return;
|
|
@@ -570,17 +558,20 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
570
558
|
return;
|
|
571
559
|
}
|
|
572
560
|
}
|
|
573
|
-
if (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
561
|
+
if (match.decl.id === "session-detail") {
|
|
562
|
+
{
|
|
563
|
+
if (!deps.sessionAudit) {
|
|
564
|
+
sendError(res, 404, "not_found.route", "not found");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const audit = await deps.sessionAudit(match.params.sessionId);
|
|
577
568
|
if (!audit) {
|
|
578
569
|
sendError(res, 404, "not_found.session", "session not found");
|
|
579
570
|
return;
|
|
580
571
|
}
|
|
581
572
|
if (!runOwnerOk(req, res, audit.owner))
|
|
582
573
|
return;
|
|
583
|
-
const q = new
|
|
574
|
+
const q = new URLSearchParams(match.search);
|
|
584
575
|
const qi = (k) => {
|
|
585
576
|
const v = q.get(k);
|
|
586
577
|
if (v === null)
|
|
@@ -619,8 +610,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
619
610
|
}
|
|
620
611
|
}
|
|
621
612
|
{
|
|
622
|
-
|
|
623
|
-
if (fm) {
|
|
613
|
+
if (match.decl.id === "session-fork") {
|
|
624
614
|
const fork = deps.sessionStorage?.fork?.bind(deps.sessionStorage);
|
|
625
615
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
626
616
|
if (!fork || !ownerOf) {
|
|
@@ -632,7 +622,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
632
622
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
633
623
|
return;
|
|
634
624
|
}
|
|
635
|
-
const sourceId = safeDecode(
|
|
625
|
+
const sourceId = safeDecode(match.params.sessionId);
|
|
636
626
|
if (sourceId === null || !isUuidV7(sourceId)) {
|
|
637
627
|
sendError(res, 400, "request.id_invalid", "invalid session id (must be a uuidv7)");
|
|
638
628
|
return;
|
|
@@ -657,8 +647,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
657
647
|
}
|
|
658
648
|
}
|
|
659
649
|
{
|
|
660
|
-
|
|
661
|
-
if (dm) {
|
|
650
|
+
if (match.decl.id === "session-delete") {
|
|
662
651
|
const purge = deps.purgeSession;
|
|
663
652
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
664
653
|
if (!purge || !ownerOf) {
|
|
@@ -670,7 +659,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
670
659
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
671
660
|
return;
|
|
672
661
|
}
|
|
673
|
-
const sessionId = safeDecode(
|
|
662
|
+
const sessionId = safeDecode(match.params.sessionId);
|
|
674
663
|
if (sessionId === null || !isUuidV7(sessionId)) {
|
|
675
664
|
sendError(res, 400, "request.id_invalid", "invalid session id (must be a uuidv7)");
|
|
676
665
|
return;
|
|
@@ -699,8 +688,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
699
688
|
}
|
|
700
689
|
}
|
|
701
690
|
{
|
|
702
|
-
|
|
703
|
-
if (pm) {
|
|
691
|
+
if (match.decl.id === "session-policy-get" || match.decl.id === "session-policy-put") {
|
|
704
692
|
const store = deps.sessionPolicyStore;
|
|
705
693
|
const ownerOf = deps.sessionStorage?.ownerOf?.bind(deps.sessionStorage);
|
|
706
694
|
if (!store || !ownerOf) {
|
|
@@ -712,7 +700,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
712
700
|
sendError(res, 401, "auth.unauthorized", "unauthorized");
|
|
713
701
|
return;
|
|
714
702
|
}
|
|
715
|
-
const sessionId = safeDecode(
|
|
703
|
+
const sessionId = safeDecode(match.params.sessionId);
|
|
716
704
|
if (sessionId === null || !isUuidV7(sessionId)) {
|
|
717
705
|
sendError(res, 400, "request.id_invalid", "invalid session id (must be a uuidv7)");
|
|
718
706
|
return;
|
|
@@ -732,7 +720,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
732
720
|
return;
|
|
733
721
|
}
|
|
734
722
|
const target = owner;
|
|
735
|
-
if (
|
|
723
|
+
if (match.decl.id === "session-policy-get") {
|
|
736
724
|
sendJson(res, 200, { rules: (await store.getRules(sessionId, target)) ?? { rev: 0 } });
|
|
737
725
|
return;
|
|
738
726
|
}
|
|
@@ -778,12 +766,27 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
778
766
|
}
|
|
779
767
|
}
|
|
780
768
|
{
|
|
781
|
-
|
|
782
|
-
if (wm) {
|
|
769
|
+
if (match.decl.id === "session-workspace") {
|
|
783
770
|
sendError(res, 501, "capability.workspace_retired", "workspace browse is retired: the whole-tree snapshot epoch it projected was replaced by per-edited-file rewind history (which holds only files the agent edited, not the workspace). There is no whole-tree manifest to browse anymore.");
|
|
784
771
|
return;
|
|
785
772
|
}
|
|
786
773
|
}
|
|
787
|
-
|
|
774
|
+
return assertNever(match.decl.id);
|
|
788
775
|
}
|
|
776
|
+
export const SESSIONS_ROUTES = [
|
|
777
|
+
{ id: "session-init", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/init$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
778
|
+
{ id: "session-settings", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/settings$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
779
|
+
{ id: "session-mcp", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/mcp$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
780
|
+
{ id: "session-a2a", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/a2a$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
781
|
+
{ id: "session-head", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/head$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
782
|
+
{ id: "session-events", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/events$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
783
|
+
{ id: "session-fork", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/fork$/, label: "/v1/sessions/:id/$verb", methods: ["POST"] },
|
|
784
|
+
{ id: "session-policy-get", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/policy$/, label: "/v1/sessions/:id/$verb", methods: ["GET"] },
|
|
785
|
+
{ id: "session-policy-put", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/policy$/, label: "/v1/sessions/:id/$verb", methods: ["PUT"] },
|
|
786
|
+
{ id: "session-memory-erase", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/memory\/erase$/, label: "/v1/sessions/:id/memory/erase", methods: ["POST"] },
|
|
787
|
+
{ id: "session-memory-status", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/memory-status$/, label: "/v1/sessions/:id/memory-status", methods: ["GET"] },
|
|
788
|
+
{ id: "session-workspace", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)\/workspace(?:\/(?<key>[^/]+)(?:\/(?<sub>tree|file|archive))?)?$/, label: "/v1/sessions/:id/workspace", methods: ["GET"] },
|
|
789
|
+
{ id: "session-detail", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)$/, label: "/v1/sessions/:id", methods: ["GET"] },
|
|
790
|
+
{ id: "session-delete", pattern: /^\/v1\/sessions\/(?<sessionId>[^/]+)$/, label: "/v1/sessions/:id", methods: ["DELETE"] },
|
|
791
|
+
];
|
|
789
792
|
//# sourceMappingURL=sessions.js.map
|
|
@@ -26,6 +26,23 @@
|
|
|
26
26
|
* 「库里没有这份文件」对他不是秘密,把它和「你不是成员」合并反而会让成员无法区分打错路径和权限问题。
|
|
27
27
|
*/
|
|
28
28
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
29
|
-
import type { RouteCtx } from "../route-ctx.js";
|
|
30
|
-
export declare function handleSharedMemory(req: IncomingMessage, res: ServerResponse,
|
|
29
|
+
import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
|
|
30
|
+
export declare function handleSharedMemory(req: IncomingMessage, res: ServerResponse, match: RouteMatch<SharedMemoryRouteId>, ctx: RouteCtx): Promise<void>;
|
|
31
|
+
export declare const SHARED_MEMORY_ROUTES: readonly [{
|
|
32
|
+
readonly id: "shared-memory-stores";
|
|
33
|
+
readonly path: "/v1/shared-memory/stores";
|
|
34
|
+
readonly methods: readonly ["GET"];
|
|
35
|
+
}, {
|
|
36
|
+
readonly id: "shared-memory-store-documents";
|
|
37
|
+
readonly pattern: RegExp;
|
|
38
|
+
readonly label: "/v1/shared-memory/stores/:store/documents";
|
|
39
|
+
readonly methods: readonly ["GET"];
|
|
40
|
+
}, {
|
|
41
|
+
readonly id: "shared-memory-store-document";
|
|
42
|
+
readonly pattern: RegExp;
|
|
43
|
+
readonly label: "/v1/shared-memory/stores/:store/document";
|
|
44
|
+
readonly methods: readonly ["GET"];
|
|
45
|
+
}];
|
|
46
|
+
/** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
|
|
47
|
+
export type SharedMemoryRouteId = RouteIdsOf<typeof SHARED_MEMORY_ROUTES>;
|
|
31
48
|
//# sourceMappingURL=shared-memory.d.ts.map
|