@springbrand/agent-runtime 0.2.0-alpha.67 → 0.2.0-alpha.69
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/package.json +3 -3
- package/src/adapter/cloudflare/subagent/runner.ts +23 -22
- package/src/adapter/cloudflare/subagent/tools.ts +1 -1
- package/src/adapter/cloudflare/universal-agent/preparation.ts +0 -7
- package/src/db/schema.ts +0 -12
- package/src/db/submission-admission.repo.ts +5 -20
- package/src/db/submission.repo.ts +5 -21
- package/src/kernel/bindings.ts +8 -7
- package/src/kernel/durable-lifecycle.ts +0 -9
- package/src/kernel/profile.ts +3 -0
- package/src/kernel/public-contracts.ts +0 -1
- package/src/kernel/subagent-runtime.ts +2 -12
- package/src/kernel/submission-authority.ts +4 -24
- package/src/model-usage.ts +52 -0
- package/src/pi/assembly/context.ts +26 -60
- package/src/pi/runtime-adapter/assembly.ts +6 -2
- package/src/pi/runtime-adapter/execution.ts +78 -114
- package/src/pi/runtime-adapter/index.ts +3 -4
- package/src/pi/runtime-adapter/transcript.ts +31 -46
- package/src/pi/session/index.ts +22 -22
- package/src/pi/session/migration.ts +646 -0
- package/src/pi/session/storage.ts +523 -311
- package/src/pi/tool/compiler.ts +2 -2
- package/src/pi/tool/core-host.ts +29 -7
- package/src/pi/tool/mcp.ts +23 -15
- package/src/pi/tool/subagent.ts +0 -9
- package/src/pi/tool/workspace-state-connector.ts +23 -20
- package/src/runtime-agent.ts +11 -0
- package/src/runtime-assembler.ts +13 -2
- package/src/runtime-definition.ts +2 -0
- package/src/runtime.ts +24 -83
package/src/pi/tool/compiler.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
AgentLoopConfig,
|
|
3
3
|
AgentTool,
|
|
4
4
|
AgentToolResult,
|
|
5
5
|
} from "@earendil-works/pi-agent-core";
|
|
@@ -88,7 +88,7 @@ const governanceState = Symbol("PiToolGovernanceState");
|
|
|
88
88
|
|
|
89
89
|
/** 向 Pi Agent 提供工具后置钩子,并为编译器保留同一 Turn 的治理状态。 */
|
|
90
90
|
export interface PiToolGovernance {
|
|
91
|
-
readonly afterToolCall: NonNullable<
|
|
91
|
+
readonly afterToolCall: NonNullable<AgentLoopConfig["afterToolCall"]>;
|
|
92
92
|
readonly wrapUpRequested: () => boolean;
|
|
93
93
|
readonly [governanceState]: PiToolGovernanceState;
|
|
94
94
|
}
|
package/src/pi/tool/core-host.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createWorkspaceStateBackend,
|
|
3
|
+
STATE_TYPES,
|
|
3
4
|
type StateBackend,
|
|
4
5
|
type WorkspaceFsLike,
|
|
5
6
|
} from "@cloudflare/shell";
|
|
@@ -9,7 +10,10 @@ import {
|
|
|
9
10
|
type ProxyToolOutput,
|
|
10
11
|
} from "@cloudflare/codemode";
|
|
11
12
|
import { createBrowserTools } from "@cloudflare/think/tools/browser";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
createExecuteRuntime,
|
|
15
|
+
type CreateExecuteToolOptions,
|
|
16
|
+
} from "@cloudflare/think/tools/execute";
|
|
13
17
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
14
18
|
import type { Usage } from "@earendil-works/pi-ai";
|
|
15
19
|
import type { ToolSet } from "ai";
|
|
@@ -18,7 +22,11 @@ import type {
|
|
|
18
22
|
RuntimeCodeExecutionPort,
|
|
19
23
|
WorkspacePort,
|
|
20
24
|
} from "../../kernel/bindings";
|
|
21
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
WorkspaceStateConnector,
|
|
27
|
+
WORKSPACE_PAGING_INSTRUCTIONS,
|
|
28
|
+
WORKSPACE_STATE_TYPES,
|
|
29
|
+
} from "./workspace-state-connector";
|
|
22
30
|
import type { RuntimeCodeExecutionFactory } from "../../kernel/bindings";
|
|
23
31
|
import { serializeOutput } from "../../lib/artifacts";
|
|
24
32
|
import type { PiToolCandidate } from "./compiler";
|
|
@@ -346,7 +354,10 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
346
354
|
},
|
|
347
355
|
)
|
|
348
356
|
: undefined;
|
|
349
|
-
const createPort = (
|
|
357
|
+
const createPort = (
|
|
358
|
+
innerResults: AgentToolResult<unknown>[],
|
|
359
|
+
stateOptions: Pick<CreateExecuteToolOptions, "state" | "connectors">,
|
|
360
|
+
) => {
|
|
350
361
|
let nestedCallOrdinal = 0;
|
|
351
362
|
const { tool } = createExecuteRuntime({
|
|
352
363
|
ctx: options.ctx,
|
|
@@ -358,7 +369,7 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
358
369
|
onResult: (value) => innerResults.push(value),
|
|
359
370
|
}),
|
|
360
371
|
timeout: CODEMODE_SANDBOX_TIMEOUT_MS,
|
|
361
|
-
...
|
|
372
|
+
...stateOptions,
|
|
362
373
|
name: "execute",
|
|
363
374
|
});
|
|
364
375
|
return toCodeExecutionPort(
|
|
@@ -370,10 +381,21 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
370
381
|
),
|
|
371
382
|
);
|
|
372
383
|
};
|
|
373
|
-
|
|
384
|
+
// Inherit the complete official state description; custom connectors otherwise lose its hints.
|
|
385
|
+
const description = createPort([], state ? { state } : {}).description;
|
|
374
386
|
return {
|
|
375
|
-
description:
|
|
376
|
-
|
|
387
|
+
description: [
|
|
388
|
+
description,
|
|
389
|
+
...(state ? [
|
|
390
|
+
"## Workspace type reference\nOnly methods exposed by connector discovery are callable.\n\n```ts\n" +
|
|
391
|
+
(workspaceState ? WORKSPACE_STATE_TYPES : STATE_TYPES) + "\n```",
|
|
392
|
+
] : []),
|
|
393
|
+
...(workspaceState ? [WORKSPACE_PAGING_INSTRUCTIONS] : []),
|
|
394
|
+
SPRINGBRAND_CODE_MODE_POLICY,
|
|
395
|
+
].join("\n\n"),
|
|
396
|
+
execute: (input) => createPort(
|
|
397
|
+
[], workspaceState ? { connectors: [workspaceState] } : state ? { state } : {},
|
|
398
|
+
).execute(input),
|
|
377
399
|
};
|
|
378
400
|
},
|
|
379
401
|
};
|
package/src/pi/tool/mcp.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { MCPClientManager } from "agents/mcp/client";
|
|
2
1
|
import type { RuntimeMcpServer } from "../../kernel/profile";
|
|
3
2
|
import type { PiToolCandidate } from "./compiler";
|
|
4
3
|
import { createPiDeclaredToolCandidate } from "./declared";
|
|
@@ -51,7 +50,8 @@ export interface PiMcpHost {
|
|
|
51
50
|
}>;
|
|
52
51
|
};
|
|
53
52
|
/** 提供原始工具发现,以及对指定 MCP Server 的一次工具调用。 */
|
|
54
|
-
readonly mcp:
|
|
53
|
+
readonly mcp: {
|
|
54
|
+
listTools(filter?: { serverId: string[]; state: 'ready' }): PiDiscoveredMcpTool[];
|
|
55
55
|
/**
|
|
56
56
|
* 调用一个已发现的 MCP 工具。
|
|
57
57
|
*
|
|
@@ -59,7 +59,7 @@ export interface PiMcpHost {
|
|
|
59
59
|
* 参数和中止信号必须原样交给 Agents SDK,避免另造一套传输生命周期。
|
|
60
60
|
*/
|
|
61
61
|
callTool(
|
|
62
|
-
params:
|
|
62
|
+
params: { serverId: string; name: string; arguments?: Record<string, unknown> },
|
|
63
63
|
options?: { signal?: AbortSignal },
|
|
64
64
|
): Promise<unknown>;
|
|
65
65
|
};
|
|
@@ -68,8 +68,12 @@ export interface PiMcpHost {
|
|
|
68
68
|
// 作用:给一个远端 MCP 工具生成 Pi 模型可见的稳定名称。
|
|
69
69
|
// 调用:候选工厂在映射每个已发现工具时调用。
|
|
70
70
|
// 原因:服务器前缀隔离同名工具,去掉连字符则保持现有 Pi 工具名格式。
|
|
71
|
-
function modelVisibleName(serverId: string, toolName: string): string {
|
|
72
|
-
|
|
71
|
+
async function modelVisibleName(serverId: string, toolName: string): Promise<string> {
|
|
72
|
+
const name = `tool_${serverId.replace(/-/g, "")}_${toolName}`;
|
|
73
|
+
if (/^[A-Za-z0-9_-]{1,64}$/.test(name)) return name;
|
|
74
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(JSON.stringify([serverId, toolName])));
|
|
75
|
+
const hash = Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
76
|
+
return `mcp_${toolName.replace(/[^A-Za-z0-9_]/g, '_').slice(0, 26)}_${hash.slice(0, 32)}`;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
// 作用:把任意 MCP 返回值尽量转换成可展示文本。
|
|
@@ -245,6 +249,9 @@ export function createPiMcpToolCandidate(
|
|
|
245
249
|
requiredExecutionLevel: policy.requiredExecutionLevel,
|
|
246
250
|
requiredExecutionLevelForInput: policy.requiredExecutionLevelForInput,
|
|
247
251
|
source: "action",
|
|
252
|
+
// Human approval must park a first-class Pi Tool, not a nested Code Mode
|
|
253
|
+
// RPC whose execution timer continues while the person is deciding.
|
|
254
|
+
exposureMode: 'direct',
|
|
248
255
|
project: (reply) => {
|
|
249
256
|
const result = normalizeCallResult(reply);
|
|
250
257
|
if (result.isError) throw new Error(errorMessage(result));
|
|
@@ -279,16 +286,17 @@ export function createPiMcpToolCandidate(
|
|
|
279
286
|
*
|
|
280
287
|
* `Host`、`Pi`、`Runtime Snapshot` 与“候选工具”见 `../../index.ts`。
|
|
281
288
|
*/
|
|
282
|
-
export function createPiMcpToolCandidates(
|
|
289
|
+
export async function createPiMcpToolCandidates(
|
|
283
290
|
host: PiMcpHost,
|
|
284
291
|
configured: readonly RuntimeMcpServer[],
|
|
285
|
-
): PiToolCandidate[] {
|
|
292
|
+
): Promise<PiToolCandidate[]> {
|
|
286
293
|
// TODO(待确认):Runtime 配置目前只 trim URL,而 agents@0.19.0 会用
|
|
287
294
|
// new URL(url).href 保存连接地址;`https://x` 与 `https://x/`
|
|
288
295
|
// 可能因此精确匹配失败。
|
|
289
296
|
const configuredUrls = new Set(
|
|
290
|
-
configured.map((server) => server.url),
|
|
297
|
+
configured.filter(server => !server.id).map((server) => server.url),
|
|
291
298
|
);
|
|
299
|
+
const configuredIds = new Map(configured.filter(server => server.id).map(server => [server.id!, server.url]));
|
|
292
300
|
const selectedUrls = new Set<string>();
|
|
293
301
|
const selectedServerIds: string[] = [];
|
|
294
302
|
|
|
@@ -297,24 +305,24 @@ export function createPiMcpToolCandidates(
|
|
|
297
305
|
)) {
|
|
298
306
|
if (
|
|
299
307
|
server.state !== "ready" ||
|
|
300
|
-
!configuredUrls.has(server.server_url) ||
|
|
301
|
-
selectedUrls.has(server.server_url)
|
|
308
|
+
!(configuredIds.get(serverId) === server.server_url || configuredUrls.has(server.server_url)) ||
|
|
309
|
+
(!configuredIds.has(serverId) && selectedUrls.has(server.server_url))
|
|
302
310
|
) {
|
|
303
311
|
continue;
|
|
304
312
|
}
|
|
305
|
-
selectedUrls.add(server.server_url);
|
|
313
|
+
if (!configuredIds.has(serverId)) selectedUrls.add(server.server_url);
|
|
306
314
|
selectedServerIds.push(serverId);
|
|
307
315
|
}
|
|
308
316
|
if (selectedServerIds.length === 0) return [];
|
|
309
317
|
|
|
310
318
|
const selectedIds = new Set(selectedServerIds);
|
|
311
|
-
return host.mcp
|
|
319
|
+
return Promise.all(host.mcp
|
|
312
320
|
.listTools({
|
|
313
321
|
serverId: selectedServerIds,
|
|
314
322
|
state: "ready",
|
|
315
323
|
})
|
|
316
324
|
.filter((tool) => selectedIds.has(tool.serverId))
|
|
317
|
-
.map((mcpTool) => createPiMcpToolCandidate(
|
|
325
|
+
.map(async (mcpTool) => createPiMcpToolCandidate(
|
|
318
326
|
mcpTool,
|
|
319
327
|
(args, signal) => host.mcp.callTool(
|
|
320
328
|
{
|
|
@@ -326,10 +334,10 @@ export function createPiMcpToolCandidates(
|
|
|
326
334
|
),
|
|
327
335
|
{
|
|
328
336
|
owner: `mcp:${mcpTool.serverId}`,
|
|
329
|
-
modelName: modelVisibleName(mcpTool.serverId, mcpTool.name),
|
|
337
|
+
modelName: await modelVisibleName(mcpTool.serverId, mcpTool.name),
|
|
330
338
|
// Remote MCP annotations are untrusted; all user-configured methods
|
|
331
339
|
// retain the existing high-risk approval policy.
|
|
332
340
|
requiredExecutionLevel: "high",
|
|
333
341
|
},
|
|
334
|
-
));
|
|
342
|
+
)));
|
|
335
343
|
}
|
package/src/pi/tool/subagent.ts
CHANGED
|
@@ -82,9 +82,6 @@ async function recordSubagentResult(
|
|
|
82
82
|
runId: run.runId,
|
|
83
83
|
subagentRunId: run.runId,
|
|
84
84
|
parentRunId: lifecycle.parentRunId,
|
|
85
|
-
...(lifecycle.accountId ? { accountId: lifecycle.accountId } : {}),
|
|
86
|
-
...(lifecycle.rateVersion !== undefined ? { rateVersion: lifecycle.rateVersion } : {}),
|
|
87
|
-
...(lifecycle.slotIdentity ? { slotIdentity: lifecycle.slotIdentity } : {}),
|
|
88
85
|
kind: "subagent",
|
|
89
86
|
status,
|
|
90
87
|
usage: run.usage,
|
|
@@ -157,9 +154,6 @@ export function subagentPiToolCandidates(
|
|
|
157
154
|
parentRunId: lifecycle.parentRunId,
|
|
158
155
|
submissionId: lifecycle.submissionId,
|
|
159
156
|
...(reserved ? { subagentRunId: reserved.runId } : {}),
|
|
160
|
-
...(lifecycle.accountId ? { accountId: lifecycle.accountId } : {}),
|
|
161
|
-
...(lifecycle.rateVersion !== undefined ? { rateVersion: lifecycle.rateVersion } : {}),
|
|
162
|
-
...(lifecycle.slotIdentity ? { slotIdentity: lifecycle.slotIdentity } : {}),
|
|
163
157
|
}
|
|
164
158
|
: undefined;
|
|
165
159
|
const run = lifecycle && reserved
|
|
@@ -239,9 +233,6 @@ export function subagentPiToolCandidates(
|
|
|
239
233
|
parentRunId: lifecycle.parentRunId,
|
|
240
234
|
submissionId: lifecycle.submissionId,
|
|
241
235
|
...(reserved ? { subagentRunId: reserved.runId } : {}),
|
|
242
|
-
...(lifecycle.accountId ? { accountId: lifecycle.accountId } : {}),
|
|
243
|
-
...(lifecycle.rateVersion !== undefined ? { rateVersion: lifecycle.rateVersion } : {}),
|
|
244
|
-
...(lifecycle.slotIdentity ? { slotIdentity: lifecycle.slotIdentity } : {}),
|
|
245
236
|
}
|
|
246
237
|
: {}),
|
|
247
238
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STATE_TYPES } from "@cloudflare/shell";
|
|
1
2
|
import type { StateBackend } from "@cloudflare/shell/workers";
|
|
2
3
|
import { StateConnector } from "@cloudflare/shell/workers";
|
|
3
4
|
import type { ConnectorTools } from "@cloudflare/codemode";
|
|
@@ -11,6 +12,25 @@ type WorkspaceFilePage =
|
|
|
11
12
|
| { kind: "csv"; size: number; nextOffset: number | null; columns: string[] | null; rows: string[][] };
|
|
12
13
|
`;
|
|
13
14
|
|
|
15
|
+
export const WORKSPACE_STATE_TYPES = `${READ_FILE_PAGE_TYPES}\n${STATE_TYPES.replace(
|
|
16
|
+
/\n\};$/u,
|
|
17
|
+
'\n /** CSV pages have columns and rows; never data. Omit offset first, then pass nextOffset until null. */\n readFilePage(args: { path: string; offset?: number }): Promise<WorkspaceFilePage | null>;\n};',
|
|
18
|
+
)}`;
|
|
19
|
+
|
|
20
|
+
export const WORKSPACE_PAGING_INSTRUCTIONS = `For CSV files, do not make a separate probe read or return page contents. Inspect columns and aggregate rows in the same execution with this loop shape:
|
|
21
|
+
|
|
22
|
+
let page = await state.readFilePage({ path });
|
|
23
|
+
if (!page || page.kind !== "csv") throw new Error("CSV not found");
|
|
24
|
+
const columns = page.columns;
|
|
25
|
+
for (;;) {
|
|
26
|
+
// Aggregate page.rows here. Do not return raw rows.
|
|
27
|
+
if (page.nextOffset === null) break;
|
|
28
|
+
page = await state.readFilePage({ path, offset: page.nextOffset });
|
|
29
|
+
}
|
|
30
|
+
return summary;
|
|
31
|
+
|
|
32
|
+
CSV pages use page.columns and page.rows; never page.data or page.content. Only the first page has columns. Do not use console.log to inspect a page; explicitly return the final summary.`;
|
|
33
|
+
|
|
14
34
|
type PagedWorkspacePort = WorkspacePort & {
|
|
15
35
|
readFilePage: NonNullable<WorkspacePort["readFilePage"]>;
|
|
16
36
|
};
|
|
@@ -28,19 +48,7 @@ export class WorkspaceStateConnector extends StateConnector {
|
|
|
28
48
|
const base = super.instructions();
|
|
29
49
|
return `${base}
|
|
30
50
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
let page = await state.readFilePage({ path });
|
|
34
|
-
if (!page || page.kind !== "csv") throw new Error("CSV not found");
|
|
35
|
-
const columns = page.columns;
|
|
36
|
-
for (;;) {
|
|
37
|
-
// Aggregate page.rows here. Do not return raw rows.
|
|
38
|
-
if (page.nextOffset === null) break;
|
|
39
|
-
page = await state.readFilePage({ path, offset: page.nextOffset });
|
|
40
|
-
}
|
|
41
|
-
return summary;
|
|
42
|
-
|
|
43
|
-
CSV pages use page.columns and page.rows; never page.data or page.content. Only the first page has columns. Do not use console.log to inspect a page; explicitly return the final summary.`;
|
|
51
|
+
${WORKSPACE_PAGING_INSTRUCTIONS}`;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
protected override tools(): ConnectorTools {
|
|
@@ -51,7 +59,7 @@ CSV pages use page.columns and page.rows; never page.data or page.content. Only
|
|
|
51
59
|
readFile: {
|
|
52
60
|
...readFile,
|
|
53
61
|
description:
|
|
54
|
-
|
|
62
|
+
`${readFile.description} CSV and files over ${WORKSPACE_DIRECT_TEXT_READ_MAX_BYTES} bytes must use readFilePage.`,
|
|
55
63
|
execute: async (args, context) => {
|
|
56
64
|
const path = requiredPath(args, "readFile");
|
|
57
65
|
const file = await this.workspace.stat(path);
|
|
@@ -95,12 +103,7 @@ CSV pages use page.columns and page.rows; never page.data or page.content. Only
|
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
override async getTypeScriptTypes(): Promise<string> {
|
|
98
|
-
|
|
99
|
-
const addition = [
|
|
100
|
-
" /** CSV pages have columns and rows; never data. Omit offset first, then pass nextOffset until null. */",
|
|
101
|
-
" readFilePage(args: { path: string; offset?: number }): Promise<WorkspaceFilePage | null>;",
|
|
102
|
-
].join("\n");
|
|
103
|
-
return `${READ_FILE_PAGE_TYPES}\n${types.replace(/\n\};$/u, `\n${addition}\n};`)}`;
|
|
106
|
+
return WORKSPACE_STATE_TYPES;
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
109
|
|
package/src/runtime-agent.ts
CHANGED
|
@@ -96,6 +96,11 @@ interface RuntimeAgentDefinitionBase<
|
|
|
96
96
|
/** 必须等于 Worker 导出名(Wrangler `class_name`)。 */
|
|
97
97
|
readonly name: string;
|
|
98
98
|
|
|
99
|
+
/** Refresh Host-owned inputs before a new Submission is pinned. Never runs on continuation/recovery. */
|
|
100
|
+
readonly beforeSubmission?: (
|
|
101
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
102
|
+
) => Promise<boolean>;
|
|
103
|
+
|
|
99
104
|
readonly load: (
|
|
100
105
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
101
106
|
requestedRuntimeKey?: string,
|
|
@@ -444,6 +449,12 @@ export function defineRuntimeAgent<
|
|
|
444
449
|
return this.ensureConfig();
|
|
445
450
|
}
|
|
446
451
|
|
|
452
|
+
protected async prepareRuntimeForSubmission(): Promise<void> {
|
|
453
|
+
const beforeSubmission = definition.beforeSubmission as RuntimeAgentDefinitionBase<Env, Config, Command, Change>["beforeSubmission"];
|
|
454
|
+
const changed = await beforeSubmission?.(this.createDefinitionContext());
|
|
455
|
+
await this.ensureConfig(undefined, changed === true);
|
|
456
|
+
}
|
|
457
|
+
|
|
447
458
|
// 作用:加载默认 Runtime,或切换到调用方指定的 key。
|
|
448
459
|
// 调用:RPC 控制面在业务配置需要刷新时调用。
|
|
449
460
|
// 原因:复用 `ensureConfig`,让手动重载遵守和首次启动相同的原子规则。
|
package/src/runtime-assembler.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SkillSource } from "agents/skills";
|
|
2
2
|
import type {
|
|
3
3
|
RuntimeGatewayPort,
|
|
4
|
+
RuntimeMcpHost,
|
|
4
5
|
RuntimeMemoryPort,
|
|
5
6
|
RuntimePlatformPort,
|
|
6
7
|
RuntimeProviderPort,
|
|
@@ -95,6 +96,7 @@ export interface RuntimeAssemblyInput {
|
|
|
95
96
|
readonly skills: readonly RuntimeSkillContribution[];
|
|
96
97
|
readonly connectors: readonly RuntimeMcpServer[];
|
|
97
98
|
readonly gateway?: RuntimeGatewayPort;
|
|
99
|
+
readonly managedMcp?: RuntimeMcpHost;
|
|
98
100
|
readonly extensions: readonly RuntimeExtensionContribution[];
|
|
99
101
|
readonly subagents?: RuntimeSubagentPort;
|
|
100
102
|
readonly schedule?: RuntimeSchedulePort;
|
|
@@ -368,6 +370,7 @@ class RuntimeBuilder {
|
|
|
368
370
|
private provider?: RuntimeProviderPort;
|
|
369
371
|
private platform?: RuntimePlatformPort;
|
|
370
372
|
private gateway?: RuntimeGatewayPort;
|
|
373
|
+
private managedMcp?: RuntimeMcpHost;
|
|
371
374
|
private workspace?: WorkspacePort;
|
|
372
375
|
private codeExecution?: RuntimeCodeExecutionFactory;
|
|
373
376
|
private sandbox?: RuntimeSandboxPort;
|
|
@@ -416,6 +419,10 @@ class RuntimeBuilder {
|
|
|
416
419
|
for (const skill of input.skills) this.addSkill(skill);
|
|
417
420
|
for (const connector of input.connectors) this.addConnector(connector);
|
|
418
421
|
this.gateway = input.gateway;
|
|
422
|
+
this.managedMcp = input.managedMcp;
|
|
423
|
+
for (const server of input.managedMcp?.servers ?? []) {
|
|
424
|
+
this.addConnector({ ...server, hostManaged: true });
|
|
425
|
+
}
|
|
419
426
|
for (const { name, extension } of input.extensions) {
|
|
420
427
|
this.addExtension(name, extension);
|
|
421
428
|
}
|
|
@@ -478,13 +485,15 @@ class RuntimeBuilder {
|
|
|
478
485
|
|
|
479
486
|
private addConnector(server: RuntimeMcpServer): void {
|
|
480
487
|
const name = requiredName(server.name, "Connector");
|
|
481
|
-
|
|
488
|
+
const identity = server.id ?? name;
|
|
489
|
+
if (this.connectors.has(identity)) {
|
|
482
490
|
throw new Error(`Duplicate Runtime Connector: ${name}`);
|
|
483
491
|
}
|
|
484
492
|
if (!server.url.trim()) {
|
|
485
493
|
throw new Error(`Runtime Connector "${name}" URL must not be empty`);
|
|
486
494
|
}
|
|
487
|
-
this.connectors.set(
|
|
495
|
+
this.connectors.set(identity, Object.freeze({
|
|
496
|
+
...server,
|
|
488
497
|
name,
|
|
489
498
|
url: server.url.trim(),
|
|
490
499
|
}));
|
|
@@ -644,6 +653,7 @@ class RuntimeBuilder {
|
|
|
644
653
|
platform: Object.freeze({ ...this.platform }),
|
|
645
654
|
...(this.codeExecution ? { codeExecution: this.codeExecution } : {}),
|
|
646
655
|
...(this.gateway ? { gateway: this.gateway } : {}),
|
|
656
|
+
...(this.managedMcp ? { mcp: this.managedMcp } : {}),
|
|
647
657
|
...(this.workspace ? { workspace: this.workspace } : {}),
|
|
648
658
|
...(this.memoryProfile.enabled && this.memory
|
|
649
659
|
? { memory: this.memory }
|
|
@@ -805,6 +815,7 @@ export async function assembleRuntimeSnapshot<
|
|
|
805
815
|
? [...BUILT_IN_RUNTIME_SKILLS, ...resources.skills]
|
|
806
816
|
: resources.skills,
|
|
807
817
|
connectors: resources.connectors.servers,
|
|
818
|
+
managedMcp: resources.connectors.managed,
|
|
808
819
|
...(resources.connectors.gateway
|
|
809
820
|
? { gateway: resources.connectors.gateway }
|
|
810
821
|
: {}),
|
|
@@ -2,6 +2,7 @@ import type { SkillSource } from "agents/skills";
|
|
|
2
2
|
import type { ExecutionLevel } from "./lib/execution-level";
|
|
3
3
|
import type {
|
|
4
4
|
RuntimeGatewayPort,
|
|
5
|
+
RuntimeMcpHost,
|
|
5
6
|
RuntimePlatformPort,
|
|
6
7
|
RuntimeProviderPort,
|
|
7
8
|
RuntimeSkillScriptPolicy,
|
|
@@ -79,6 +80,7 @@ export type { RuntimeAgentContext, RuntimeAssemblyContext } from "./runtime-agen
|
|
|
79
80
|
export interface ResolvedConnectors {
|
|
80
81
|
readonly servers: readonly RuntimeMcpServer[];
|
|
81
82
|
readonly gateway?: RuntimeGatewayPort;
|
|
83
|
+
readonly managed?: RuntimeMcpHost;
|
|
82
84
|
readonly degradations: readonly RuntimeDegradation[];
|
|
83
85
|
}
|
|
84
86
|
|