@springbrand/agent-runtime 0.2.0-alpha.68 → 0.2.0-alpha.70
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 +6 -0
- package/src/pi/tool/mcp.ts +23 -15
- package/src/pi/tool/subagent.ts +0 -9
- package/src/pi/tool/workspace-state-connector.ts +7 -6
- package/src/runtime-agent-context.ts +2 -0
- package/src/runtime-agent.ts +24 -0
- package/src/runtime-assembler.ts +13 -2
- package/src/runtime-definition.ts +2 -0
- package/src/runtime.ts +30 -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";
|
|
@@ -24,6 +25,7 @@ import type {
|
|
|
24
25
|
import {
|
|
25
26
|
WorkspaceStateConnector,
|
|
26
27
|
WORKSPACE_PAGING_INSTRUCTIONS,
|
|
28
|
+
WORKSPACE_STATE_TYPES,
|
|
27
29
|
} from "./workspace-state-connector";
|
|
28
30
|
import type { RuntimeCodeExecutionFactory } from "../../kernel/bindings";
|
|
29
31
|
import { serializeOutput } from "../../lib/artifacts";
|
|
@@ -384,6 +386,10 @@ export function createWorkspaceCodeExecutionFactory(options: {
|
|
|
384
386
|
return {
|
|
385
387
|
description: [
|
|
386
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
|
+
] : []),
|
|
387
393
|
...(workspaceState ? [WORKSPACE_PAGING_INSTRUCTIONS] : []),
|
|
388
394
|
SPRINGBRAND_CODE_MODE_POLICY,
|
|
389
395
|
].join("\n\n"),
|
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,11 @@ 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
|
+
|
|
14
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:
|
|
15
21
|
|
|
16
22
|
let page = await state.readFilePage({ path });
|
|
@@ -97,12 +103,7 @@ ${WORKSPACE_PAGING_INSTRUCTIONS}`;
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
override async getTypeScriptTypes(): Promise<string> {
|
|
100
|
-
|
|
101
|
-
const addition = [
|
|
102
|
-
" /** CSV pages have columns and rows; never data. Omit offset first, then pass nextOffset until null. */",
|
|
103
|
-
" readFilePage(args: { path: string; offset?: number }): Promise<WorkspaceFilePage | null>;",
|
|
104
|
-
].join("\n");
|
|
105
|
-
return `${READ_FILE_PAGE_TYPES}\n${types.replace(/\n\};$/u, `\n${addition}\n};`)}`;
|
|
106
|
+
return WORKSPACE_STATE_TYPES;
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -60,6 +60,8 @@ export interface RuntimeAgentConfigContext<
|
|
|
60
60
|
readonly parentPath: readonly RuntimeAgentPathStep[];
|
|
61
61
|
/** Runtime 根据 Agent facet 父子关系自动判定,不是外部配置项。 */
|
|
62
62
|
readonly role: RuntimeAgentRole;
|
|
63
|
+
/** Current/preparing Submission and durable identities that may still resume. */
|
|
64
|
+
readonly submission?: { readonly id: string | undefined; readonly unfinishedIds: readonly string[] };
|
|
63
65
|
/** 向当前 Agent facet 的已连接客户端发送实时消息。 */
|
|
64
66
|
broadcast(message: string): void;
|
|
65
67
|
/**
|
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,17 @@ export function defineRuntimeAgent<
|
|
|
444
449
|
return this.ensureConfig();
|
|
445
450
|
}
|
|
446
451
|
|
|
452
|
+
protected async prepareRuntimeForSubmission(submissionId: string): Promise<void> {
|
|
453
|
+
const beforeSubmission = definition.beforeSubmission as RuntimeAgentDefinitionBase<Env, Config, Command, Change>["beforeSubmission"];
|
|
454
|
+
try {
|
|
455
|
+
const changed = await beforeSubmission?.(this.createDefinitionContext(undefined, submissionId));
|
|
456
|
+
await this.ensureConfig(undefined, changed === true);
|
|
457
|
+
} catch (error) {
|
|
458
|
+
this.hasLoadedRuntime = false;
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
447
463
|
// 作用:加载默认 Runtime,或切换到调用方指定的 key。
|
|
448
464
|
// 调用:RPC 控制面在业务配置需要刷新时调用。
|
|
449
465
|
// 原因:复用 `ensureConfig`,让手动重载遵守和首次启动相同的原子规则。
|
|
@@ -658,6 +674,9 @@ export function defineRuntimeAgent<
|
|
|
658
674
|
// Host 的会话列表把装配后的第一次投影当作「facet 已就绪」的信号。
|
|
659
675
|
await this.broadcastApprovals();
|
|
660
676
|
})().catch((error) => {
|
|
677
|
+
// A failed replacement must be retried even when Host inputs have
|
|
678
|
+
// not changed again since that failed attempt.
|
|
679
|
+
this.hasLoadedRuntime = false;
|
|
661
680
|
logRuntimeLoadFailure("runtime", error);
|
|
662
681
|
this.runtimeLoad.fail();
|
|
663
682
|
throw error;
|
|
@@ -820,6 +839,7 @@ export function defineRuntimeAgent<
|
|
|
820
839
|
|
|
821
840
|
protected createDefinitionContext(
|
|
822
841
|
runtimeConfig?: { value: Config },
|
|
842
|
+
submissionId?: string,
|
|
823
843
|
): RuntimeAgentPlanningContext<
|
|
824
844
|
Env,
|
|
825
845
|
Command,
|
|
@@ -838,6 +858,10 @@ export function defineRuntimeAgent<
|
|
|
838
858
|
get role() {
|
|
839
859
|
return agent.role;
|
|
840
860
|
},
|
|
861
|
+
get submission() {
|
|
862
|
+
const current = agent.submissionContext;
|
|
863
|
+
return { ...current, id: submissionId ?? current.id };
|
|
864
|
+
},
|
|
841
865
|
broadcast: (message) => agent.broadcast(message),
|
|
842
866
|
...(agent.role === "primary"
|
|
843
867
|
? {
|
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
|
|
package/src/runtime.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { summarizeModelUsage } from "./model-usage";
|
|
1
2
|
import type {
|
|
2
3
|
AgentToolLifecycleResult,
|
|
3
4
|
AgentToolRunInfo,
|
|
@@ -181,9 +182,6 @@ interface StoredSubmission extends SubmissionReceipt {
|
|
|
181
182
|
recoveryErrorCount: number;
|
|
182
183
|
recoveryReason?: SubmissionRecoveryReason;
|
|
183
184
|
runId: string | null;
|
|
184
|
-
accountId: string | null;
|
|
185
|
-
rateVersion: number | null;
|
|
186
|
-
slotIdentity: string | null;
|
|
187
185
|
/** 跨执行片累计的模型回合数,全局回合预算据此判断。 */
|
|
188
186
|
modelTurns: number;
|
|
189
187
|
/** 当前让步执行片的标识;没有未完成让步时为 null。 */
|
|
@@ -281,14 +279,6 @@ function validateAdmissionResult(
|
|
|
281
279
|
}
|
|
282
280
|
return result;
|
|
283
281
|
}
|
|
284
|
-
if (
|
|
285
|
-
typeof result.accountId !== "string" || !result.accountId.trim() ||
|
|
286
|
-
typeof result.slotIdentity !== "string" || !result.slotIdentity.trim() ||
|
|
287
|
-
!Number.isSafeInteger(result.rateVersion) ||
|
|
288
|
-
result.rateVersion <= 0
|
|
289
|
-
) {
|
|
290
|
-
throw new Error("Runtime admission context is invalid");
|
|
291
|
-
}
|
|
292
282
|
return result;
|
|
293
283
|
}
|
|
294
284
|
|
|
@@ -306,24 +296,14 @@ function assistantUsageEvent(
|
|
|
306
296
|
eventId: string,
|
|
307
297
|
submissionId: string,
|
|
308
298
|
message: AssistantMessage,
|
|
309
|
-
|
|
299
|
+
requestedModel: RuntimeSnapshot["pi"]["model"],
|
|
310
300
|
): RuntimeModelUsageEvent {
|
|
311
301
|
return {
|
|
312
302
|
eventId,
|
|
313
303
|
submissionId,
|
|
314
304
|
runId: submissionId,
|
|
315
|
-
...(pinned?.accountId ? { accountId: pinned.accountId } : {}),
|
|
316
|
-
...(pinned?.rateVersion !== null && pinned?.rateVersion !== undefined
|
|
317
|
-
? { rateVersion: pinned.rateVersion }
|
|
318
|
-
: {}),
|
|
319
|
-
...(pinned?.slotIdentity ? { slotIdentity: pinned.slotIdentity } : {}),
|
|
320
305
|
kind: "assistant",
|
|
321
|
-
|
|
322
|
-
provider: message.provider,
|
|
323
|
-
model: message.model,
|
|
324
|
-
...(message.responseModel ? { responseModel: message.responseModel } : {}),
|
|
325
|
-
stopReason: message.stopReason,
|
|
326
|
-
usage: message.usage,
|
|
306
|
+
...summarizeModelUsage(requestedModel, [message]),
|
|
327
307
|
};
|
|
328
308
|
}
|
|
329
309
|
|
|
@@ -437,6 +417,16 @@ export abstract class AgentRuntimeKernel<
|
|
|
437
417
|
};
|
|
438
418
|
|
|
439
419
|
protected abstract ensureRuntimeReady(): Promise<void>;
|
|
420
|
+
/** Runs only before the first pin of a newly activated Submission. */
|
|
421
|
+
protected prepareRuntimeForSubmission(_submissionId: string): Promise<void> {
|
|
422
|
+
return this.ensureRuntimeReady();
|
|
423
|
+
}
|
|
424
|
+
protected get submissionContext() {
|
|
425
|
+
return {
|
|
426
|
+
id: this.submissions.currentActive()?.submissionId ?? this.db.submissions.findRunning()?.submissionId,
|
|
427
|
+
unfinishedIds: this.db.submissions.listUnfinishedIds(),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
440
430
|
|
|
441
431
|
/** Let the Host prepare a validated client response before Tool settlement. */
|
|
442
432
|
protected prepareToolInteractionResponse(
|
|
@@ -579,7 +569,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
579
569
|
interruptActive: (turn) => turn.agent.interrupt(),
|
|
580
570
|
});
|
|
581
571
|
this.transcript = this.pi.createTranscript({
|
|
582
|
-
|
|
572
|
+
storage: this.ctx.storage,
|
|
583
573
|
durability: {
|
|
584
574
|
transaction: (run) => this.db.transaction(run),
|
|
585
575
|
upsertUserSidecar: (id, body) =>
|
|
@@ -763,7 +753,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
763
753
|
this.runtimeLoad.advance("mcp");
|
|
764
754
|
await connectConfiguredMcpServers(
|
|
765
755
|
this,
|
|
766
|
-
candidateSnapshot.profile.mcpServers,
|
|
756
|
+
candidateSnapshot.profile.mcpServers.filter(server => !server.hostManaged),
|
|
767
757
|
);
|
|
768
758
|
const gatewayDegradations = [];
|
|
769
759
|
let gatewaySession: RuntimeGatewaySession | undefined;
|
|
@@ -812,7 +802,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
812
802
|
// 新能力,同一轮接着跑」所依赖的那一步。真正在执行的 Turn 仍然被挡住。
|
|
813
803
|
const contested = Boolean(previous) &&
|
|
814
804
|
revision !== previous?.revision &&
|
|
815
|
-
this.submissions.isBusy()
|
|
805
|
+
this.submissions.isBusy() &&
|
|
806
|
+
this.db.submissions.listUnfinishedIds().some(id => Boolean(this.readSubmission(id)?.assemblyRevision));
|
|
816
807
|
const parked = contested && this.db.everyUnfinishedSubmissionParked();
|
|
817
808
|
if (contested && !parked) {
|
|
818
809
|
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.contested");
|
|
@@ -902,13 +893,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
902
893
|
const runMetadata: RuntimeSubagentRunMetadata = {
|
|
903
894
|
submissionId: submission.submissionId,
|
|
904
895
|
parentRunId: submission.runId ?? submission.submissionId,
|
|
905
|
-
...(submission.accountId ? { accountId: submission.accountId } : {}),
|
|
906
|
-
...(submission.rateVersion !== null
|
|
907
|
-
? { rateVersion: submission.rateVersion }
|
|
908
|
-
: {}),
|
|
909
|
-
...(submission.slotIdentity
|
|
910
|
-
? { slotIdentity: submission.slotIdentity }
|
|
911
|
-
: {}),
|
|
912
896
|
};
|
|
913
897
|
const lifecycle = this.subagentLifecycle(runMetadata);
|
|
914
898
|
const toolExecutors = snapshot.bindings.subagents
|
|
@@ -1077,7 +1061,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1077
1061
|
persisted.id,
|
|
1078
1062
|
submission.submissionId,
|
|
1079
1063
|
commit.message,
|
|
1080
|
-
|
|
1064
|
+
snapshot.pi.model,
|
|
1081
1065
|
);
|
|
1082
1066
|
this.db.runtimeEvents.insert({
|
|
1083
1067
|
eventId: event.eventId,
|
|
@@ -1107,7 +1091,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
1107
1091
|
: undefined;
|
|
1108
1092
|
this.transcript.append(commit.id, commit.message, {
|
|
1109
1093
|
submissionId: submission.submissionId,
|
|
1110
|
-
createdAt: commit.message.timestamp,
|
|
1111
1094
|
...(uiMessage ? { userMessage: uiMessage } : {}),
|
|
1112
1095
|
});
|
|
1113
1096
|
this.db.steers.deleteByMessageId(commit.id);
|
|
@@ -1558,11 +1541,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
1558
1541
|
// 作用:为新 Submission 生成包含当前 Context 的不可变装配描述。
|
|
1559
1542
|
// 调用:Submission 通过去重和忙碌检查后,在真正写入前调用。
|
|
1560
1543
|
// 原因:把动态 Context 和执行档位固定在准入时,恢复时才能按原配置继续。
|
|
1561
|
-
private async admissionPin(): Promise<{
|
|
1544
|
+
private async admissionPin(fresh: boolean, submissionId: string): Promise<{
|
|
1562
1545
|
revision: string;
|
|
1563
1546
|
descriptor: string;
|
|
1564
1547
|
}> {
|
|
1565
|
-
await this.
|
|
1548
|
+
if (fresh) await this.prepareRuntimeForSubmission(submissionId);
|
|
1549
|
+
else await this.ensureRuntimeReady();
|
|
1566
1550
|
return this.currentAssemblyPin();
|
|
1567
1551
|
}
|
|
1568
1552
|
|
|
@@ -1606,6 +1590,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1606
1590
|
|
|
1607
1591
|
private async pinSubmissionAssembly(
|
|
1608
1592
|
submission: StoredSubmission,
|
|
1593
|
+
mode: SubmissionExecutionMode,
|
|
1609
1594
|
): Promise<StoredSubmission> {
|
|
1610
1595
|
if (submission.assemblyRevision && submission.assemblyDescriptor) {
|
|
1611
1596
|
return submission;
|
|
@@ -1613,7 +1598,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1613
1598
|
if (submission.assemblyRevision || submission.assemblyDescriptor) {
|
|
1614
1599
|
throw new Error("Submission assembly pin is incomplete");
|
|
1615
1600
|
}
|
|
1616
|
-
const { revision, descriptor } = await this.admissionPin();
|
|
1601
|
+
const { revision, descriptor } = await this.admissionPin(mode === 'fresh', submission.submissionId);
|
|
1617
1602
|
this.db.submissions.pinAssembly(
|
|
1618
1603
|
submission.submissionId,
|
|
1619
1604
|
revision,
|
|
@@ -1744,7 +1729,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1744
1729
|
model: snapshot.pi.model,
|
|
1745
1730
|
signal,
|
|
1746
1731
|
submissionId,
|
|
1747
|
-
|
|
1732
|
+
onCompactionUsage: (event, didCompact) => {
|
|
1748
1733
|
this.db.runtimeEvents.insert({
|
|
1749
1734
|
eventId: event.eventId,
|
|
1750
1735
|
body: json({
|
|
@@ -1752,21 +1737,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
1752
1737
|
event: {
|
|
1753
1738
|
...event,
|
|
1754
1739
|
runId: submissionId,
|
|
1755
|
-
...(this.readSubmission(submissionId)?.accountId
|
|
1756
|
-
? { accountId: this.readSubmission(submissionId)!.accountId! }
|
|
1757
|
-
: {}),
|
|
1758
|
-
...(this.readSubmission(submissionId)?.rateVersion !== null &&
|
|
1759
|
-
this.readSubmission(submissionId)?.rateVersion !== undefined
|
|
1760
|
-
? { rateVersion: this.readSubmission(submissionId)!.rateVersion! }
|
|
1761
|
-
: {}),
|
|
1762
|
-
...(this.readSubmission(submissionId)?.slotIdentity
|
|
1763
|
-
? { slotIdentity: this.readSubmission(submissionId)!.slotIdentity! }
|
|
1764
|
-
: {}),
|
|
1765
1740
|
},
|
|
1766
1741
|
}),
|
|
1767
1742
|
createdAt: Date.now(),
|
|
1768
1743
|
});
|
|
1769
|
-
this.telemetry.capture("contextCompacted", {
|
|
1744
|
+
if (didCompact) this.telemetry.capture("contextCompacted", {
|
|
1770
1745
|
submissionId,
|
|
1771
1746
|
compactionId: event.eventId,
|
|
1772
1747
|
inputTokens: event.usage.input,
|
|
@@ -1907,9 +1882,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
1907
1882
|
let admission = existing?.status === "accepted"
|
|
1908
1883
|
? {
|
|
1909
1884
|
accepted: true as const,
|
|
1910
|
-
accountId: existing.accountId!,
|
|
1911
|
-
rateVersion: existing.rateVersion!,
|
|
1912
|
-
slotIdentity: existing.slotIdentity!,
|
|
1913
1885
|
}
|
|
1914
1886
|
: existing?.status === "rejected"
|
|
1915
1887
|
? {
|
|
@@ -1951,9 +1923,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
1951
1923
|
this.db.submissionAdmissions.saveAccepted(
|
|
1952
1924
|
admissionRequestId,
|
|
1953
1925
|
runId,
|
|
1954
|
-
admission.accountId,
|
|
1955
|
-
admission.rateVersion,
|
|
1956
|
-
admission.slotIdentity,
|
|
1957
1926
|
);
|
|
1958
1927
|
}
|
|
1959
1928
|
this.db.submissions.insert({
|
|
@@ -1973,9 +1942,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
1973
1942
|
? options.userMessageId ?? null
|
|
1974
1943
|
: null,
|
|
1975
1944
|
runId,
|
|
1976
|
-
accountId: admission?.accepted ? admission.accountId : null,
|
|
1977
|
-
rateVersion: admission?.accepted ? admission.rateVersion : null,
|
|
1978
|
-
slotIdentity: admission?.accepted ? admission.slotIdentity : null,
|
|
1979
1945
|
});
|
|
1980
1946
|
if (admission?.accepted) {
|
|
1981
1947
|
const fact: RuntimeLifecycleFact = {
|
|
@@ -1983,9 +1949,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
1983
1949
|
eventId: `${submissionId}:admitted`,
|
|
1984
1950
|
submissionId,
|
|
1985
1951
|
runId: submissionId,
|
|
1986
|
-
accountId: admission.accountId,
|
|
1987
|
-
rateVersion: admission.rateVersion,
|
|
1988
|
-
slotIdentity: admission.slotIdentity,
|
|
1989
1952
|
};
|
|
1990
1953
|
this.db.runtimeEvents.insert({
|
|
1991
1954
|
eventId: fact.eventId,
|
|
@@ -2086,13 +2049,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
2086
2049
|
zeroUsage: !this.db.runtimeEvents.hasNonZeroModelUsage(
|
|
2087
2050
|
submission.submissionId,
|
|
2088
2051
|
),
|
|
2089
|
-
...(submission.accountId ? { accountId: submission.accountId } : {}),
|
|
2090
|
-
...(submission.rateVersion !== null
|
|
2091
|
-
? { rateVersion: submission.rateVersion }
|
|
2092
|
-
: {}),
|
|
2093
|
-
...(submission.slotIdentity
|
|
2094
|
-
? { slotIdentity: submission.slotIdentity }
|
|
2095
|
-
: {}),
|
|
2096
2052
|
};
|
|
2097
2053
|
this.db.runtimeEvents.insert({
|
|
2098
2054
|
eventId: terminalFact.eventId,
|
|
@@ -2859,7 +2815,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
2859
2815
|
userMessage,
|
|
2860
2816
|
{
|
|
2861
2817
|
submissionId: submission.submissionId,
|
|
2862
|
-
createdAt: userMessage.timestamp,
|
|
2863
2818
|
...(uiMessage ? { userMessage: uiMessage } : {}),
|
|
2864
2819
|
},
|
|
2865
2820
|
);
|
|
@@ -2991,7 +2946,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
2991
2946
|
submission.abortReason,
|
|
2992
2947
|
);
|
|
2993
2948
|
}
|
|
2994
|
-
submission = await this.pinSubmissionAssembly(submission);
|
|
2949
|
+
submission = await this.pinSubmissionAssembly(submission, mode);
|
|
2995
2950
|
await this.assertPinnedSubmission(submission);
|
|
2996
2951
|
if (submission.status === "pending" && mode === "fresh") {
|
|
2997
2952
|
submission = await this.activateQueuedSubmission(submission);
|
|
@@ -3667,7 +3622,9 @@ export abstract class AgentRuntimeKernel<
|
|
|
3667
3622
|
private async hydrationMessages(): Promise<UIMessage[]> {
|
|
3668
3623
|
const messages = await this.getMessages();
|
|
3669
3624
|
const running = this.db.submissions.findRunning();
|
|
3670
|
-
|
|
3625
|
+
// After a restart there is no in-memory replay stream. In particular, a
|
|
3626
|
+
// parked approval cannot recreate one until the user can see and decide it.
|
|
3627
|
+
return running?.assistantMessageId && this.streamBySubmission.has(running.submissionId)
|
|
3671
3628
|
? messages.filter((message) =>
|
|
3672
3629
|
message.role !== "assistant" ||
|
|
3673
3630
|
(message.id !== running.assistantMessageId &&
|
|
@@ -3804,9 +3761,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
3804
3761
|
await this.broadcastApprovals();
|
|
3805
3762
|
return;
|
|
3806
3763
|
}
|
|
3807
|
-
this.transcript.append(crypto.randomUUID(), message
|
|
3808
|
-
createdAt: message.timestamp,
|
|
3809
|
-
});
|
|
3764
|
+
this.transcript.append(crypto.randomUUID(), message);
|
|
3810
3765
|
}
|
|
3811
3766
|
|
|
3812
3767
|
// 作用:返回扩展可见的当前 Session 摘要。
|
|
@@ -4260,7 +4215,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
4260
4215
|
: undefined;
|
|
4261
4216
|
this.transcript.append(steer.messageId, message, {
|
|
4262
4217
|
submissionId: current.submissionId,
|
|
4263
|
-
createdAt: steer.createdAt,
|
|
4264
4218
|
...(userMessage ? { userMessage } : {}),
|
|
4265
4219
|
});
|
|
4266
4220
|
this.db.steers.deleteByMessageId(steer.messageId);
|
|
@@ -4613,13 +4567,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
4613
4567
|
runId: run.runId,
|
|
4614
4568
|
subagentRunId: run.runId,
|
|
4615
4569
|
parentRunId: metadata.parentRunId,
|
|
4616
|
-
...(metadata.accountId ? { accountId: metadata.accountId } : {}),
|
|
4617
|
-
...(metadata.rateVersion !== undefined
|
|
4618
|
-
? { rateVersion: metadata.rateVersion }
|
|
4619
|
-
: {}),
|
|
4620
|
-
...(metadata.slotIdentity
|
|
4621
|
-
? { slotIdentity: metadata.slotIdentity }
|
|
4622
|
-
: {}),
|
|
4623
4570
|
kind: "subagent",
|
|
4624
4571
|
status,
|
|
4625
4572
|
usage: completion.usage,
|