@nylorun/runtime 0.20.0-beta → 0.21.1-beta
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/CHANGELOG.md +86 -0
- package/README.md +19 -12
- package/dist/adapters/execution/restate.js +26 -1
- package/dist/api/components.d.ts +155 -36
- package/dist/api/components.js +3 -3
- package/dist/api/http/app.js +2 -0
- package/dist/api/http/routes/mcp.d.ts +9 -0
- package/dist/api/http/routes/mcp.js +32 -0
- package/dist/api/http/routes/vaults.d.ts +1 -9
- package/dist/api/http/routes/vaults.js +3 -103
- package/dist/api/openapi.js +6 -3
- package/dist/artifacts/media-types.d.ts +2 -0
- package/dist/artifacts/media-types.js +5 -0
- package/dist/core/flow-host.d.ts +2 -3
- package/dist/core/flow-host.js +10 -7
- package/dist/execution/types.d.ts +13 -5
- package/dist/execution/types.js +13 -5
- package/dist/gates/http-tool.d.ts +7 -0
- package/dist/gates/http-tool.js +34 -3
- package/dist/gates/mcp-handler.d.ts +4 -1
- package/dist/gates/mcp-handler.js +64 -8
- package/dist/gates/tenant-vaults.d.ts +9 -15
- package/dist/gates/tenant-vaults.js +9 -17
- package/dist/gates/tool-client.js +36 -17
- package/dist/gates/tool-contract.d.ts +19 -3
- package/dist/gates/tool-contract.js +9 -3
- package/dist/gates/tool-gate.d.ts +2 -2
- package/dist/harness/calls.d.ts +2 -0
- package/dist/harness/calls.js +13 -3
- package/dist/harness-api/record.d.ts +7 -6
- package/dist/harness-api/record.js +99 -17
- package/dist/harness-api/server.js +35 -27
- package/dist/harness-api/start.js +2 -1
- package/dist/host/config-for.d.ts +0 -2
- package/dist/host/config-for.js +0 -1
- package/dist/host/create-host.d.ts +1 -1
- package/dist/host/create-host.js +2 -1
- package/dist/host/gates.js +0 -1
- package/dist/host/http.d.ts +0 -1
- package/dist/host/http.js +0 -6
- package/dist/host/main.js +13 -3
- package/dist/host/stack-config.d.ts +7 -8
- package/dist/host/stack-config.js +50 -92
- package/dist/index.d.ts +0 -1
- package/dist/keys/client.js +1 -9
- package/dist/keys/keys.d.ts +11 -13
- package/dist/keys/keys.js +5 -7
- package/dist/management-openapi.json +315 -345
- package/dist/mcp/connect.d.ts +159 -13
- package/dist/mcp/connect.js +538 -55
- package/dist/mcp/pool.d.ts +11 -2
- package/dist/mcp/pool.js +74 -27
- package/dist/mcp/preview.d.ts +35 -0
- package/dist/mcp/preview.js +215 -0
- package/dist/mcp/snapshot.d.ts +79 -5
- package/dist/mcp/snapshot.js +145 -27
- package/dist/model/catalog.d.ts +14 -0
- package/dist/model/catalog.js +14 -1
- package/dist/model/pi-model.js +36 -2
- package/dist/openapi.json +362 -123
- package/dist/redact.d.ts +19 -1
- package/dist/redact.js +42 -13
- package/dist/sandbox/session-sandbox.d.ts +7 -0
- package/dist/sandbox/session-sandbox.js +89 -28
- package/dist/store/postgres/drizzle/0016_mcp_oauth_removed.sql +5 -0
- package/dist/store/postgres/drizzle/0017_wake_outbox.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +14 -0
- package/dist/store/postgres/schema.d.ts +105 -261
- package/dist/store/postgres/schema.js +18 -36
- package/dist/store/postgres/store.js +54 -21
- package/dist/store/postgres/tenant.d.ts +14 -0
- package/dist/store/postgres/tenant.js +22 -2
- package/dist/store/types.d.ts +48 -13
- package/dist/tenant/advance.js +1 -4
- package/dist/tenant/artifact-tool.d.ts +9 -1
- package/dist/tenant/artifact-tool.js +113 -9
- package/dist/tenant/commands.js +5 -5
- package/dist/tenant/context.d.ts +15 -13
- package/dist/tenant/effects.d.ts +2 -2
- package/dist/tenant/effects.js +20 -5
- package/dist/tenant/ephemeral.d.ts +0 -6
- package/dist/tenant/ephemeral.js +0 -1
- package/dist/tenant/outbound.d.ts +14 -5
- package/dist/tenant/outbound.js +21 -9
- package/dist/tenant/runtime.js +11 -15
- package/dist/tenant/sessions.js +20 -3
- package/dist/tenant/store-pg.js +9 -0
- package/dist/tenant/sweep.d.ts +14 -0
- package/dist/tenant/sweep.js +69 -10
- package/dist/tenant/tool-results.d.ts +57 -0
- package/dist/tenant/tool-results.js +298 -0
- package/dist/tenant/tool-search.d.ts +43 -0
- package/dist/tenant/tool-search.js +100 -0
- package/dist/tenant/transcript.d.ts +108 -0
- package/dist/tenant/transcript.js +13 -0
- package/dist/tenant/types.d.ts +0 -14
- package/dist/vault/crypto.d.ts +0 -7
- package/dist/vault/crypto.js +0 -16
- package/dist/vault/headers.d.ts +17 -0
- package/dist/vault/headers.js +61 -0
- package/dist/vault/service.d.ts +11 -68
- package/dist/vault/service.js +83 -438
- package/dist/vault/sources.d.ts +20 -48
- package/dist/vault/sources.js +25 -189
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/vault/oauth.d.ts +0 -55
- package/dist/vault/oauth.js +0 -186
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tool_search` (R2b C10, Q20): core finds an agent's deferred MCP tools in the session's pinned
|
|
3
|
+
* snapshot, ranked with BM25 over their names and descriptions, and returns each match's name,
|
|
4
|
+
* description and `inputSchema`, for the model to run with `tool_call`. It reads only the pinned
|
|
5
|
+
* snapshot, so the same query finds the same tools for the session's life; a tool the turn's
|
|
6
|
+
* manifest disables is never found.
|
|
7
|
+
*/
|
|
8
|
+
import { type AgentManifest } from "@nylorun/core/define";
|
|
9
|
+
import type { HostEffect } from "@nylorun/harness/run";
|
|
10
|
+
import { type McpToolRecord } from "../mcp/snapshot.js";
|
|
11
|
+
import { type TenantContext } from "./context.js";
|
|
12
|
+
/** True when `request` calls `tool_search` of the `nylorun.tools` capability `manifest` has. */
|
|
13
|
+
export declare function isToolSearchCall(manifest: AgentManifest | undefined, request: HostEffect): boolean;
|
|
14
|
+
/** Runs `tool_search` for the run's agent. */
|
|
15
|
+
export declare function callToolSearch(ctx: TenantContext, request: HostEffect): Promise<{
|
|
16
|
+
output?: undefined;
|
|
17
|
+
kind: string;
|
|
18
|
+
code: string;
|
|
19
|
+
message: string;
|
|
20
|
+
} | {
|
|
21
|
+
code?: undefined;
|
|
22
|
+
message?: undefined;
|
|
23
|
+
kind: string;
|
|
24
|
+
output: {
|
|
25
|
+
tools: {
|
|
26
|
+
name: string;
|
|
27
|
+
description?: string | undefined;
|
|
28
|
+
inputSchema: import("@nylorun/core/define").JsonObject;
|
|
29
|
+
}[];
|
|
30
|
+
message?: string | undefined;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Lower-case words of `text`: `camelCase`, `snake_case` and `kebab-case` split into words, and a
|
|
35
|
+
* plural `s` dropped (`issues` finds `issue`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function tokens(text: string): string[];
|
|
38
|
+
/**
|
|
39
|
+
* The tools that match `query`, best first, by BM25 over each tool's name (counted twice, with
|
|
40
|
+
* the server's own name for it) and description. A tool no word of the query is in is left out;
|
|
41
|
+
* ties keep the snapshot's order.
|
|
42
|
+
*/
|
|
43
|
+
export declare function rank(tools: readonly McpToolRecord[], query: string): McpToolRecord[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tool_search` (R2b C10, Q20): core finds an agent's deferred MCP tools in the session's pinned
|
|
3
|
+
* snapshot, ranked with BM25 over their names and descriptions, and returns each match's name,
|
|
4
|
+
* description and `inputSchema`, for the model to run with `tool_call`. It reads only the pinned
|
|
5
|
+
* snapshot, so the same query finds the same tools for the session's life; a tool the turn's
|
|
6
|
+
* manifest disables is never found.
|
|
7
|
+
*/
|
|
8
|
+
import { TOOL_SEARCH_DEFAULT_LIMIT, TOOL_SEARCH_MAX_LIMIT, TOOL_SEARCH_TOOL, TOOLS_CAPABILITY_ID, } from "@nylorun/core/define";
|
|
9
|
+
import { isEnabledIn } from "../mcp/snapshot.js";
|
|
10
|
+
import { sessionOf } from "./context.js";
|
|
11
|
+
import { turnManifestOf } from "./session.js";
|
|
12
|
+
/** BM25's term saturation and length normalization, the usual values. */
|
|
13
|
+
const K1 = 1.2;
|
|
14
|
+
const B = 0.75;
|
|
15
|
+
/** True when `request` calls `tool_search` of the `nylorun.tools` capability `manifest` has. */
|
|
16
|
+
export function isToolSearchCall(manifest, request) {
|
|
17
|
+
return (request.kind === "tool" &&
|
|
18
|
+
request.capabilityId === TOOLS_CAPABILITY_ID &&
|
|
19
|
+
request.toolName === TOOL_SEARCH_TOOL &&
|
|
20
|
+
manifest?.capabilities.some((capability) => capability.id === TOOLS_CAPABILITY_ID) === true);
|
|
21
|
+
}
|
|
22
|
+
/** Runs `tool_search` for the run's agent. */
|
|
23
|
+
export async function callToolSearch(ctx, request) {
|
|
24
|
+
const input = (request.input ?? {});
|
|
25
|
+
const query = typeof input.query === "string" ? input.query : "";
|
|
26
|
+
const limit = typeof input.limit === "number" && Number.isSafeInteger(input.limit)
|
|
27
|
+
? Math.min(Math.max(input.limit, 1), TOOL_SEARCH_MAX_LIMIT)
|
|
28
|
+
: TOOL_SEARCH_DEFAULT_LIMIT;
|
|
29
|
+
if (query.trim() === "")
|
|
30
|
+
return { kind: "failed", code: "tools.invalid_input", message: "Give a query: words of what the tool does." };
|
|
31
|
+
const session = await ctx.store.tx((t) => sessionOf(t, request.sessionId));
|
|
32
|
+
const manifest = turnManifestOf(session);
|
|
33
|
+
const deferred = (session.mcpSnapshot?.mcpTools ?? []).filter((tool) => tool.deferred === true &&
|
|
34
|
+
tool.agentId === request.agent?.id &&
|
|
35
|
+
// Enabled in the turn's manifest, which a variant may have tightened (R2b C9).
|
|
36
|
+
isEnabledIn(manifest, tool));
|
|
37
|
+
const found = rank(deferred, query).slice(0, limit);
|
|
38
|
+
return {
|
|
39
|
+
kind: "completed",
|
|
40
|
+
output: {
|
|
41
|
+
tools: found.map((tool) => ({
|
|
42
|
+
name: tool.name,
|
|
43
|
+
...(tool.description === undefined ? {} : { description: tool.description }),
|
|
44
|
+
inputSchema: tool.inputSchema,
|
|
45
|
+
})),
|
|
46
|
+
...(found.length === 0
|
|
47
|
+
? { message: `No tool matches '${query}'. Try other words; ${deferred.length} tools are searchable.` }
|
|
48
|
+
: {}),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Lower-case words of `text`: `camelCase`, `snake_case` and `kebab-case` split into words, and a
|
|
54
|
+
* plural `s` dropped (`issues` finds `issue`).
|
|
55
|
+
*/
|
|
56
|
+
export function tokens(text) {
|
|
57
|
+
return text
|
|
58
|
+
.replace(/([a-z0-9])([A-Z])/gu, "$1 $2")
|
|
59
|
+
.toLowerCase()
|
|
60
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
61
|
+
.filter((word) => word.length > 0)
|
|
62
|
+
.map((word) => (word.length > 3 && word.endsWith("s") && !word.endsWith("ss") ? word.slice(0, -1) : word));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The tools that match `query`, best first, by BM25 over each tool's name (counted twice, with
|
|
66
|
+
* the server's own name for it) and description. A tool no word of the query is in is left out;
|
|
67
|
+
* ties keep the snapshot's order.
|
|
68
|
+
*/
|
|
69
|
+
export function rank(tools, query) {
|
|
70
|
+
const words = [...new Set(tokens(query))];
|
|
71
|
+
if (words.length === 0 || tools.length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
const docs = tools.map((tool) => {
|
|
74
|
+
const name = [...tokens(tool.name), ...tokens(tool.serverToolName)];
|
|
75
|
+
const terms = [...name, ...name, ...tokens(tool.description ?? "")];
|
|
76
|
+
const counts = new Map();
|
|
77
|
+
for (const term of terms)
|
|
78
|
+
counts.set(term, (counts.get(term) ?? 0) + 1);
|
|
79
|
+
return { tool, counts, length: terms.length };
|
|
80
|
+
});
|
|
81
|
+
const average = docs.reduce((sum, doc) => sum + doc.length, 0) / docs.length || 1;
|
|
82
|
+
const idf = new Map(words.map((word) => {
|
|
83
|
+
const df = docs.filter((doc) => doc.counts.has(word)).length;
|
|
84
|
+
return [word, Math.log(1 + (docs.length - df + 0.5) / (df + 0.5))];
|
|
85
|
+
}));
|
|
86
|
+
return docs
|
|
87
|
+
.map((doc, order) => {
|
|
88
|
+
let score = 0;
|
|
89
|
+
for (const word of words) {
|
|
90
|
+
const tf = doc.counts.get(word) ?? 0;
|
|
91
|
+
if (tf === 0)
|
|
92
|
+
continue;
|
|
93
|
+
score += idf.get(word) * ((tf * (K1 + 1)) / (tf + K1 * (1 - B + (B * doc.length) / average)));
|
|
94
|
+
}
|
|
95
|
+
return { tool: doc.tool, score, order };
|
|
96
|
+
})
|
|
97
|
+
.filter((item) => item.score > 0)
|
|
98
|
+
.sort((a, b) => b.score - a.score || a.order - b.order)
|
|
99
|
+
.map((item) => item.tool);
|
|
100
|
+
}
|
|
@@ -81,8 +81,12 @@ export declare function toolCompleted(request: HostEffect, value: unknown): {
|
|
|
81
81
|
error: {
|
|
82
82
|
code: string;
|
|
83
83
|
message: string;
|
|
84
|
+
server?: string | undefined;
|
|
85
|
+
vault?: string | undefined;
|
|
86
|
+
retryable?: boolean | undefined;
|
|
84
87
|
};
|
|
85
88
|
output?: undefined;
|
|
89
|
+
redacted: number;
|
|
86
90
|
capabilityId: string;
|
|
87
91
|
toolName: string;
|
|
88
92
|
} | {
|
|
@@ -90,20 +94,96 @@ export declare function toolCompleted(request: HostEffect, value: unknown): {
|
|
|
90
94
|
error: {
|
|
91
95
|
code: string;
|
|
92
96
|
message: string;
|
|
97
|
+
server?: string | undefined;
|
|
98
|
+
vault?: string | undefined;
|
|
99
|
+
retryable?: boolean | undefined;
|
|
93
100
|
};
|
|
94
101
|
output?: undefined;
|
|
102
|
+
redacted: number;
|
|
103
|
+
capabilityId: string;
|
|
104
|
+
toolName: string;
|
|
105
|
+
} | {
|
|
106
|
+
agent: import("@nylorun/core/define").AgentRef;
|
|
107
|
+
error: {
|
|
108
|
+
code: string;
|
|
109
|
+
message: string;
|
|
110
|
+
server?: string | undefined;
|
|
111
|
+
vault?: string | undefined;
|
|
112
|
+
retryable?: boolean | undefined;
|
|
113
|
+
};
|
|
114
|
+
output?: undefined;
|
|
115
|
+
redacted?: undefined;
|
|
116
|
+
capabilityId: string;
|
|
117
|
+
toolName: string;
|
|
118
|
+
} | {
|
|
119
|
+
agent?: undefined;
|
|
120
|
+
error: {
|
|
121
|
+
code: string;
|
|
122
|
+
message: string;
|
|
123
|
+
server?: string | undefined;
|
|
124
|
+
vault?: string | undefined;
|
|
125
|
+
retryable?: boolean | undefined;
|
|
126
|
+
};
|
|
127
|
+
output?: undefined;
|
|
128
|
+
redacted?: undefined;
|
|
95
129
|
capabilityId: string;
|
|
96
130
|
toolName: string;
|
|
97
131
|
} | {
|
|
98
132
|
agent: import("@nylorun/core/define").AgentRef;
|
|
99
133
|
error?: undefined;
|
|
100
134
|
output: unknown;
|
|
135
|
+
redacted: number;
|
|
101
136
|
capabilityId: string;
|
|
102
137
|
toolName: string;
|
|
103
138
|
} | {
|
|
104
139
|
agent?: undefined;
|
|
105
140
|
error?: undefined;
|
|
106
141
|
output: unknown;
|
|
142
|
+
redacted: number;
|
|
143
|
+
capabilityId: string;
|
|
144
|
+
toolName: string;
|
|
145
|
+
} | {
|
|
146
|
+
agent: import("@nylorun/core/define").AgentRef;
|
|
147
|
+
error?: undefined;
|
|
148
|
+
output: unknown;
|
|
149
|
+
redacted?: undefined;
|
|
150
|
+
capabilityId: string;
|
|
151
|
+
toolName: string;
|
|
152
|
+
} | {
|
|
153
|
+
agent?: undefined;
|
|
154
|
+
error?: undefined;
|
|
155
|
+
output: unknown;
|
|
156
|
+
redacted?: undefined;
|
|
157
|
+
capabilityId: string;
|
|
158
|
+
toolName: string;
|
|
159
|
+
} | {
|
|
160
|
+
callId: string;
|
|
161
|
+
invocationId: string;
|
|
162
|
+
agent: import("@nylorun/core/define").AgentRef;
|
|
163
|
+
error: {
|
|
164
|
+
code: string;
|
|
165
|
+
message: string;
|
|
166
|
+
server?: string | undefined;
|
|
167
|
+
vault?: string | undefined;
|
|
168
|
+
retryable?: boolean | undefined;
|
|
169
|
+
};
|
|
170
|
+
output?: undefined;
|
|
171
|
+
redacted: number;
|
|
172
|
+
capabilityId: string;
|
|
173
|
+
toolName: string;
|
|
174
|
+
} | {
|
|
175
|
+
callId: string;
|
|
176
|
+
invocationId: string;
|
|
177
|
+
agent?: undefined;
|
|
178
|
+
error: {
|
|
179
|
+
code: string;
|
|
180
|
+
message: string;
|
|
181
|
+
server?: string | undefined;
|
|
182
|
+
vault?: string | undefined;
|
|
183
|
+
retryable?: boolean | undefined;
|
|
184
|
+
};
|
|
185
|
+
output?: undefined;
|
|
186
|
+
redacted: number;
|
|
107
187
|
capabilityId: string;
|
|
108
188
|
toolName: string;
|
|
109
189
|
} | {
|
|
@@ -113,8 +193,12 @@ export declare function toolCompleted(request: HostEffect, value: unknown): {
|
|
|
113
193
|
error: {
|
|
114
194
|
code: string;
|
|
115
195
|
message: string;
|
|
196
|
+
server?: string | undefined;
|
|
197
|
+
vault?: string | undefined;
|
|
198
|
+
retryable?: boolean | undefined;
|
|
116
199
|
};
|
|
117
200
|
output?: undefined;
|
|
201
|
+
redacted?: undefined;
|
|
118
202
|
capabilityId: string;
|
|
119
203
|
toolName: string;
|
|
120
204
|
} | {
|
|
@@ -124,8 +208,30 @@ export declare function toolCompleted(request: HostEffect, value: unknown): {
|
|
|
124
208
|
error: {
|
|
125
209
|
code: string;
|
|
126
210
|
message: string;
|
|
211
|
+
server?: string | undefined;
|
|
212
|
+
vault?: string | undefined;
|
|
213
|
+
retryable?: boolean | undefined;
|
|
127
214
|
};
|
|
128
215
|
output?: undefined;
|
|
216
|
+
redacted?: undefined;
|
|
217
|
+
capabilityId: string;
|
|
218
|
+
toolName: string;
|
|
219
|
+
} | {
|
|
220
|
+
callId: string;
|
|
221
|
+
invocationId: string;
|
|
222
|
+
agent: import("@nylorun/core/define").AgentRef;
|
|
223
|
+
error?: undefined;
|
|
224
|
+
output: unknown;
|
|
225
|
+
redacted: number;
|
|
226
|
+
capabilityId: string;
|
|
227
|
+
toolName: string;
|
|
228
|
+
} | {
|
|
229
|
+
callId: string;
|
|
230
|
+
invocationId: string;
|
|
231
|
+
agent?: undefined;
|
|
232
|
+
error?: undefined;
|
|
233
|
+
output: unknown;
|
|
234
|
+
redacted: number;
|
|
129
235
|
capabilityId: string;
|
|
130
236
|
toolName: string;
|
|
131
237
|
} | {
|
|
@@ -134,6 +240,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown): {
|
|
|
134
240
|
agent: import("@nylorun/core/define").AgentRef;
|
|
135
241
|
error?: undefined;
|
|
136
242
|
output: unknown;
|
|
243
|
+
redacted?: undefined;
|
|
137
244
|
capabilityId: string;
|
|
138
245
|
toolName: string;
|
|
139
246
|
} | {
|
|
@@ -142,6 +249,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown): {
|
|
|
142
249
|
agent?: undefined;
|
|
143
250
|
error?: undefined;
|
|
144
251
|
output: unknown;
|
|
252
|
+
redacted?: undefined;
|
|
145
253
|
capabilityId: string;
|
|
146
254
|
toolName: string;
|
|
147
255
|
} | undefined;
|
|
@@ -102,14 +102,27 @@ export function toolCompleted(request, value) {
|
|
|
102
102
|
error: {
|
|
103
103
|
code: String(outcome.code ?? "tool.failed"),
|
|
104
104
|
message: String(outcome.message ?? ""),
|
|
105
|
+
// `credential_rejected` (R2b C1): which server, and which vault's credential.
|
|
106
|
+
...(typeof outcome.server === "string" ? { server: outcome.server } : {}),
|
|
107
|
+
...(outcome.vault === "installation" || outcome.vault === "user"
|
|
108
|
+
? { vault: outcome.vault }
|
|
109
|
+
: {}),
|
|
110
|
+
...(typeof outcome.retryable === "boolean" ? { retryable: outcome.retryable } : {}),
|
|
105
111
|
},
|
|
106
112
|
}
|
|
107
113
|
: { output: outcome?.kind === "completed" ? outcome.output : value };
|
|
114
|
+
// Credential values the server echoed, replaced before the outcome was recorded (R2b C8).
|
|
115
|
+
const redacted = (outcome?.kind === "completed" || outcome?.kind === "failed") &&
|
|
116
|
+
typeof outcome.redacted === "number" &&
|
|
117
|
+
outcome.redacted > 0
|
|
118
|
+
? { redacted: outcome.redacted }
|
|
119
|
+
: {};
|
|
108
120
|
return {
|
|
109
121
|
...ids,
|
|
110
122
|
capabilityId: request.capabilityId,
|
|
111
123
|
toolName: request.toolName,
|
|
112
124
|
...result,
|
|
125
|
+
...redacted,
|
|
113
126
|
...agentOf(request),
|
|
114
127
|
};
|
|
115
128
|
}
|
package/dist/tenant/types.d.ts
CHANGED
|
@@ -8,7 +8,6 @@ import type { SessionStore } from "../store/types.js";
|
|
|
8
8
|
import type { SandboxBackend } from "../sandbox/types.js";
|
|
9
9
|
import type { TenantCause } from "./cause.js";
|
|
10
10
|
import type { OutboundPolicy } from "./outbound.js";
|
|
11
|
-
import type { ResolverConfig } from "../vault/sources.js";
|
|
12
11
|
import type { TenantWorker } from "./worker.js";
|
|
13
12
|
export type TenantMode = "shared" | "ephemeral" | "test";
|
|
14
13
|
/**
|
|
@@ -75,24 +74,11 @@ export interface TenantConfig {
|
|
|
75
74
|
* never reads ambient process environment for flow limits.
|
|
76
75
|
*/
|
|
77
76
|
flowEnv?: Readonly<Record<string, string | undefined>>;
|
|
78
|
-
vaultFetch?: typeof fetch;
|
|
79
77
|
/**
|
|
80
78
|
* The trusted issuers whose JWTs the Tenant API accepts (Host feature `trusted-issuers`), from
|
|
81
79
|
* the identity file (`NYLORUN_IDENTITY_FILE`). Absent: none.
|
|
82
80
|
*/
|
|
83
81
|
issuers?: import("./issuers.js").TrustedIssuers;
|
|
84
|
-
/**
|
|
85
|
-
* The operator's credential resolver (F9 C1), asked for a person's MCP credential when the
|
|
86
|
-
* session's vaults hold none. Used where this process authorizes MCP calls itself (embedding,
|
|
87
|
-
* the ephemeral Runtime, tests); the gateway takes `NYLORUN_RESOLVER_*` instead.
|
|
88
|
-
*/
|
|
89
|
-
resolver?: ResolverConfig;
|
|
90
|
-
/**
|
|
91
|
-
* The URL browsers reach this Tenant API at (`NYLORUN_PUBLIC_URL`, no trailing slash): the
|
|
92
|
-
* base of the MCP OAuth callback, `<publicUrl>/v1/oauth/callback` (F9 C2). Absent (embedding,
|
|
93
|
-
* tests): the origin the start request was sent to.
|
|
94
|
-
*/
|
|
95
|
-
publicUrl?: string;
|
|
96
82
|
logger: Logger;
|
|
97
83
|
}
|
|
98
84
|
export interface Logger {
|
package/dist/vault/crypto.d.ts
CHANGED
|
@@ -10,10 +10,3 @@ export declare function encryptSecret(kek: Buffer, aad: Buffer, plaintext: Buffe
|
|
|
10
10
|
kekId: string;
|
|
11
11
|
};
|
|
12
12
|
export declare function decryptSecret(kek: Buffer, aad: Buffer, nonce: Buffer, ciphertext: Buffer, wrappedDek: Buffer, expectedKekId: string): Buffer;
|
|
13
|
-
/**
|
|
14
|
-
* `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
|
|
15
|
-
* seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
|
|
16
|
-
*/
|
|
17
|
-
export declare function sealBytes(kek: Buffer, aad: Buffer, plaintext: Buffer): Buffer;
|
|
18
|
-
/** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
|
|
19
|
-
export declare function openBytes(kek: Buffer, aad: Buffer, sealed: Buffer, expectedKekId: string): Buffer;
|
package/dist/vault/crypto.js
CHANGED
|
@@ -78,19 +78,3 @@ function unwrapKey(kek, wrapped) {
|
|
|
78
78
|
throw new VaultCryptoError();
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
/** The wrapped DEK's length: its nonce, the 32-byte key and the tag. */
|
|
82
|
-
const WRAPPED_DEK_BYTES = IV_BYTES + 32 + TAG_BYTES;
|
|
83
|
-
/**
|
|
84
|
-
* `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
|
|
85
|
-
* seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
|
|
86
|
-
*/
|
|
87
|
-
export function sealBytes(kek, aad, plaintext) {
|
|
88
|
-
const sealed = encryptSecret(kek, aad, plaintext);
|
|
89
|
-
return Buffer.concat([sealed.nonce, sealed.wrappedDek, sealed.ciphertext]);
|
|
90
|
-
}
|
|
91
|
-
/** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
|
|
92
|
-
export function openBytes(kek, aad, sealed, expectedKekId) {
|
|
93
|
-
if (sealed.length < IV_BYTES + WRAPPED_DEK_BYTES + TAG_BYTES)
|
|
94
|
-
throw new VaultCryptoError();
|
|
95
|
-
return decryptSecret(kek, aad, sealed.subarray(0, IV_BYTES), sealed.subarray(IV_BYTES + WRAPPED_DEK_BYTES), sealed.subarray(IV_BYTES, IV_BYTES + WRAPPED_DEK_BYTES), expectedKekId);
|
|
96
|
-
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The secret values of a credential's `headers`, as a server could echo them (R2b C8, Q16): each
|
|
3
|
+
* value of at least 8 characters, and the token of a `Bearer …` value. The identity header names
|
|
4
|
+
* the session owner and is no secret, so it is left out.
|
|
5
|
+
*/
|
|
6
|
+
export declare function credentialSecrets(headers: Readonly<Record<string, string>>, identity?: {
|
|
7
|
+
readonly header: string;
|
|
8
|
+
}): string[];
|
|
9
|
+
/** True when a credential may not send `name`: a transport header or `Nylorun-*`. */
|
|
10
|
+
export declare function isReservedHeader(name: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* The header names a credential sends, lower-cased: a `headers` map's, `authorization` for a
|
|
13
|
+
* `bearer`. Refuses a reserved name, and an identity header that repeats one of them.
|
|
14
|
+
*/
|
|
15
|
+
export declare function checkCredentialHeaders(names: readonly string[], identity: {
|
|
16
|
+
readonly header: string;
|
|
17
|
+
} | undefined): string[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The headers a vault credential may send (R2b C2): a `headers` credential's names and an
|
|
3
|
+
* identity header. The core schema checks their syntax; this module refuses the names the
|
|
4
|
+
* Runtime sets itself, so a credential can never replace them.
|
|
5
|
+
*/
|
|
6
|
+
import { VaultError } from "./error.js";
|
|
7
|
+
/** Headers the MCP transport and the HTTP tool call own. */
|
|
8
|
+
const RESERVED_HEADERS = new Set([
|
|
9
|
+
"host",
|
|
10
|
+
"content-length",
|
|
11
|
+
"content-type",
|
|
12
|
+
"transfer-encoding",
|
|
13
|
+
"connection",
|
|
14
|
+
"accept",
|
|
15
|
+
"mcp-session-id",
|
|
16
|
+
"mcp-protocol-version",
|
|
17
|
+
"last-event-id",
|
|
18
|
+
"idempotency-key",
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* The secret values of a credential's `headers`, as a server could echo them (R2b C8, Q16): each
|
|
22
|
+
* value of at least 8 characters, and the token of a `Bearer …` value. The identity header names
|
|
23
|
+
* the session owner and is no secret, so it is left out.
|
|
24
|
+
*/
|
|
25
|
+
export function credentialSecrets(headers, identity) {
|
|
26
|
+
const skip = identity?.header.toLowerCase();
|
|
27
|
+
const secrets = new Set();
|
|
28
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
29
|
+
if (name.toLowerCase() === skip)
|
|
30
|
+
continue;
|
|
31
|
+
if (value.length >= 8)
|
|
32
|
+
secrets.add(value);
|
|
33
|
+
const bearer = /^bearer\s+(\S+)\s*$/iu.exec(value)?.[1];
|
|
34
|
+
if (bearer !== undefined && bearer.length >= 8)
|
|
35
|
+
secrets.add(bearer);
|
|
36
|
+
}
|
|
37
|
+
return [...secrets];
|
|
38
|
+
}
|
|
39
|
+
/** True when a credential may not send `name`: a transport header or `Nylorun-*`. */
|
|
40
|
+
export function isReservedHeader(name) {
|
|
41
|
+
const lower = name.toLowerCase();
|
|
42
|
+
return RESERVED_HEADERS.has(lower) || lower.startsWith("nylorun-");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The header names a credential sends, lower-cased: a `headers` map's, `authorization` for a
|
|
46
|
+
* `bearer`. Refuses a reserved name, and an identity header that repeats one of them.
|
|
47
|
+
*/
|
|
48
|
+
export function checkCredentialHeaders(names, identity) {
|
|
49
|
+
const lower = names.map((name) => name.toLowerCase());
|
|
50
|
+
for (const name of lower)
|
|
51
|
+
if (isReservedHeader(name))
|
|
52
|
+
throw new VaultError(400, `A credential may not set the header ${name}`);
|
|
53
|
+
if (identity) {
|
|
54
|
+
const header = identity.header.toLowerCase();
|
|
55
|
+
if (isReservedHeader(header))
|
|
56
|
+
throw new VaultError(400, `The identity header may not be ${header}`);
|
|
57
|
+
if (lower.includes(header))
|
|
58
|
+
throw new VaultError(400, `The identity header ${header} is already one of the credential's headers`);
|
|
59
|
+
}
|
|
60
|
+
return lower;
|
|
61
|
+
}
|
package/dist/vault/service.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import type { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, CredentialSelection, HostModelProviderInfo, HostModelView, PutHostModelRequest, CustomModelSettings, RotateCredentialRequest, SelectHostModelRequest, VaultInfo } from "@nylorun/core/contracts";
|
|
1
|
+
import type { CreateCredentialRequest, CreateVaultRequest, CredentialIdentity, CredentialInfo, CredentialSelection, HostModelProviderInfo, HostModelView, PutHostModelRequest, CustomModelSettings, RotateCredentialRequest, SelectHostModelRequest, VaultInfo } from "@nylorun/core/contracts";
|
|
2
2
|
import type { SessionStore, Tx, VaultCredentialRow } from "../store/types.js";
|
|
3
|
-
/** How long an MCP OAuth connect may take between its start and its callback (F9 C2). */
|
|
4
|
-
export declare const OAUTH_PENDING_MS: number;
|
|
5
3
|
export declare const HOST_VAULT_ID = "host";
|
|
6
4
|
export type HostModelSecret = {
|
|
7
5
|
provider: string;
|
|
@@ -27,50 +25,28 @@ export type AuthorizeResult = {
|
|
|
27
25
|
} | {
|
|
28
26
|
status: "authorized";
|
|
29
27
|
url: string;
|
|
30
|
-
/**
|
|
28
|
+
/** The vault credential's headers: `authorization` for a `bearer`, a `headers` map's. */
|
|
31
29
|
headers: Record<string, string>;
|
|
30
|
+
/** Where the request goes instead of `url` (a gateway), when the credential has one. */
|
|
31
|
+
via?: string;
|
|
32
|
+
/** The header that names the session owner; `sessionCredentials` adds it. */
|
|
33
|
+
identity?: CredentialIdentity;
|
|
34
|
+
/** The scope of the vault the credential is in. */
|
|
35
|
+
vault: "installation" | "user";
|
|
32
36
|
} | {
|
|
33
37
|
status: "refused";
|
|
34
38
|
url: string;
|
|
35
39
|
credentialIds: string[];
|
|
36
40
|
reason: string;
|
|
37
41
|
};
|
|
38
|
-
/** `startOAuth`'s input: the route's body, the installation vault and the callback URL. */
|
|
39
|
-
export interface StartOAuthInput {
|
|
40
|
-
vaultId: string;
|
|
41
|
-
/** The MCP server's name, the credential's name. */
|
|
42
|
-
server: string;
|
|
43
|
-
url: string;
|
|
44
|
-
clientId?: string;
|
|
45
|
-
/** `<public URL>/v1/oauth/callback`. */
|
|
46
|
-
redirectUri: string;
|
|
47
|
-
}
|
|
48
|
-
/** The callback's query: `code`, or the authorization server's `error`. */
|
|
49
|
-
export interface FinishOAuthInput {
|
|
50
|
-
state: string;
|
|
51
|
-
code?: string;
|
|
52
|
-
error?: string;
|
|
53
|
-
}
|
|
54
42
|
export interface VaultServiceOptions {
|
|
55
43
|
store: SessionStore;
|
|
56
44
|
/** The Tenant key-encryption key. May read or create the key file. */
|
|
57
45
|
kek: () => Buffer;
|
|
58
|
-
/**
|
|
59
|
-
* OAuth refresh and MCP OAuth connect (discovery, registration, the code exchange), always
|
|
60
|
-
* outside a transaction: the Host's `guardedFetch` (`tenant/outbound.ts`) unless a test or
|
|
61
|
-
* an embedder injects one.
|
|
62
|
-
*/
|
|
63
|
-
fetch: typeof fetch;
|
|
64
|
-
/** How long a token endpoint may take to answer a refresh. Default 30 s. */
|
|
65
|
-
refreshTimeoutMs?: number;
|
|
66
46
|
}
|
|
67
47
|
export declare class VaultService {
|
|
68
48
|
private readonly store;
|
|
69
49
|
private readonly kek;
|
|
70
|
-
private readonly fetchImpl;
|
|
71
|
-
private readonly refreshTimeoutMs;
|
|
72
|
-
/** Refreshes in progress by credential id: concurrent uses share one. */
|
|
73
|
-
private readonly refreshing;
|
|
74
50
|
constructor(options: VaultServiceOptions);
|
|
75
51
|
createVault(body: CreateVaultRequest): Promise<VaultInfo>;
|
|
76
52
|
/**
|
|
@@ -101,54 +77,21 @@ export declare class VaultService {
|
|
|
101
77
|
/** Writes the `attach` audit row. Run in the session PUT transaction. */
|
|
102
78
|
recordAttachment(t: Tx, sessionId: string, vaultIds: readonly string[]): Promise<void>;
|
|
103
79
|
authorize(input: {
|
|
104
|
-
|
|
80
|
+
/** The session the request is made for; absent for a tool preview (R2b C12). */
|
|
81
|
+
sessionId?: string;
|
|
105
82
|
vaultIds: readonly string[];
|
|
106
83
|
credentialSelections: readonly CredentialSelection[];
|
|
107
84
|
url: string;
|
|
108
85
|
serverName?: string;
|
|
109
86
|
}): Promise<AuthorizeResult>;
|
|
110
|
-
/**
|
|
111
|
-
* Starts signing the installation in to the MCP server at `url`: discovery, a client (the
|
|
112
|
-
* given id, or one registered now), an S256 PKCE verifier and a `state`, kept for ten minutes
|
|
113
|
-
* in a pending row (verifier and secret sealed, state hashed). Returns where to send the
|
|
114
|
-
* browser. Installation vaults only.
|
|
115
|
-
*/
|
|
116
|
-
startOAuth(input: StartOAuthInput): Promise<{
|
|
117
|
-
authorizeUrl: string;
|
|
118
|
-
expiresAt: string;
|
|
119
|
-
}>;
|
|
120
|
-
/**
|
|
121
|
-
* Finishes a connect from its callback: takes the pending row of `state` (deleted whatever
|
|
122
|
-
* happens next), exchanges the code, and seals an `oauth` credential bound to the server's
|
|
123
|
-
* URL in the installation vault, rotating the vault's OAuth credential for that URL when it
|
|
124
|
-
* has one.
|
|
125
|
-
*/
|
|
126
|
-
finishOAuth(input: FinishOAuthInput): Promise<{
|
|
127
|
-
vaultId: string;
|
|
128
|
-
credentialId: string;
|
|
129
|
-
}>;
|
|
130
|
-
/** An installation vault, else the 404 of a missing one (a user vault is a 400). */
|
|
131
|
-
private installationVault;
|
|
132
87
|
getHostModel(): Promise<HostModelView>;
|
|
133
88
|
listHostProviders(): Promise<{
|
|
134
89
|
providers: HostModelProviderInfo[];
|
|
135
90
|
}>;
|
|
136
91
|
putHostModel(body: PutHostModelRequest): Promise<HostModelView>;
|
|
137
92
|
selectHostModel(body: SelectHostModelRequest): Promise<HostModelView>;
|
|
138
|
-
/** Calls the token endpoint outside any transaction, then stores the result. */
|
|
139
|
-
/**
|
|
140
|
-
* Refreshes an OAuth grant once for every concurrent use in this process. A rotating refresh
|
|
141
|
-
* token is valid once, so two refreshes with it would fail the second, or make the provider
|
|
142
|
-
* revoke the grant.
|
|
143
|
-
*/
|
|
144
|
-
private refreshShared;
|
|
145
|
-
/** Uses a token another process refreshed instead of refreshing again, before and after. */
|
|
146
|
-
private refreshLatest;
|
|
147
|
-
/** The credential's payload when it was rotated after `row` was read and is not due. */
|
|
148
|
-
private rotatedSince;
|
|
149
|
-
private refresh;
|
|
150
93
|
private replay;
|
|
151
|
-
/** Re-seals a user credential's payload and returns the updated row. */
|
|
94
|
+
/** Re-seals a user credential's payload with its new binding and returns the updated row. */
|
|
152
95
|
private writePayload;
|
|
153
96
|
private vaultInfo;
|
|
154
97
|
private ensureHostVault;
|