@springbrand/agent-runtime 0.1.3-alpha.1 → 0.1.3-alpha.11

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.
Files changed (76) hide show
  1. package/package.json +12 -3
  2. package/src/adapter/cloudflare/index.ts +56 -0
  3. package/src/adapter/cloudflare/resources/runtime-resources.ts +89 -0
  4. package/src/adapter/cloudflare/sandbox/adapter.ts +1513 -0
  5. package/src/adapter/cloudflare/sandbox/id.ts +23 -0
  6. package/src/adapter/cloudflare/sandbox/policy.ts +15 -0
  7. package/src/adapter/cloudflare/subagent/definition.ts +574 -0
  8. package/src/adapter/cloudflare/subagent/runner.ts +175 -0
  9. package/src/adapter/cloudflare/subagent/tools.ts +254 -0
  10. package/src/adapter/cloudflare/universal-agent/hooks.ts +35 -0
  11. package/src/adapter/cloudflare/universal-agent/preparation.ts +277 -0
  12. package/src/adapter/cloudflare/universal-agent/tools.ts +80 -0
  13. package/src/adapter/cloudflare/workspace/git-fs.ts +178 -0
  14. package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
  15. package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
  16. package/src/adapter/cloudflare/workspace/version-control.ts +374 -0
  17. package/src/agent-tool-runtime.ts +152 -0
  18. package/src/db/agent-tool.repo.ts +27 -0
  19. package/src/db/index.ts +33 -0
  20. package/src/db/interaction.repo.ts +185 -0
  21. package/src/db/schema.ts +25 -1
  22. package/src/db/submission.repo.ts +63 -1
  23. package/src/index.ts +57 -21
  24. package/src/kernel/approval-lifecycle.ts +41 -6
  25. package/src/kernel/bindings.ts +73 -9
  26. package/src/kernel/interaction-lifecycle.ts +395 -0
  27. package/src/kernel/public-contracts.ts +2 -0
  28. package/src/kernel/recoverable-chat-agent.ts +104 -6
  29. package/src/kernel/runtime-assembly-view.ts +37 -0
  30. package/src/kernel/runtime-assembly.ts +41 -0
  31. package/src/kernel/runtime-config.ts +4 -0
  32. package/src/kernel/runtime-load.ts +191 -0
  33. package/src/kernel/state.ts +12 -1
  34. package/src/kernel/submission-lifecycle.ts +33 -2
  35. package/src/layers/orchestration/temporary-agent/core.ts +12 -1
  36. package/src/layers/orchestration/temporary-agent/runner.ts +1 -2
  37. package/src/lib/mcp.ts +7 -3
  38. package/src/lib/prompt.ts +4 -1
  39. package/src/lib/telemetry-dev.ts +7 -4
  40. package/src/pi/assembly/context.ts +3 -3
  41. package/src/pi/assembly/extensions.ts +11 -22
  42. package/src/pi/assembly/snapshot.ts +6 -3
  43. package/src/pi/message/contract.ts +7 -0
  44. package/src/pi/message/conversion.ts +9 -1
  45. package/src/pi/runtime-adapter/assembly.ts +26 -31
  46. package/src/pi/runtime-adapter/execution.ts +198 -15
  47. package/src/pi/runtime-adapter/index.ts +24 -8
  48. package/src/pi/runtime-adapter/models.ts +382 -35
  49. package/src/pi/runtime-adapter/recovery.ts +188 -1
  50. package/src/pi/runtime-adapter/transcript.ts +61 -3
  51. package/src/pi/tool/ai-adapter.ts +58 -1
  52. package/src/pi/tool/base.ts +190 -12
  53. package/src/pi/tool/compiler.ts +34 -1
  54. package/src/pi/tool/core-host.ts +19 -24
  55. package/src/pi/tool/core.ts +30 -120
  56. package/src/pi/tool/gateway.ts +54 -0
  57. package/src/pi/tool/index.ts +2 -0
  58. package/src/pi/tool/mcp.ts +96 -68
  59. package/src/pi/tool/schedule.ts +41 -19
  60. package/src/pi/tool/skill.ts +126 -420
  61. package/src/pi/tool/subagent.ts +14 -2
  62. package/src/pi/tool/web-fetch.ts +281 -0
  63. package/src/pi/tool/web-search/api.ts +34 -18
  64. package/src/pi/tool/web-search/web-search.ts +0 -1
  65. package/src/pi/tool/workspace-revision.ts +64 -0
  66. package/src/pi/tool/workspace-sandbox.ts +105 -263
  67. package/src/pi/turn/index.ts +20 -0
  68. package/src/pi/turn/interaction.ts +181 -0
  69. package/src/pi/turn/tool-recovery.ts +244 -1
  70. package/src/runtime-agent-context.ts +112 -0
  71. package/src/runtime-agent.ts +568 -321
  72. package/src/{plugins.ts → runtime-assembler.ts} +372 -398
  73. package/src/runtime-definition.ts +175 -0
  74. package/src/runtime.ts +835 -204
  75. package/src/tool-registry.ts +143 -0
  76. package/src/workspace-versioning.ts +46 -0
@@ -1,6 +1,7 @@
1
1
  import type { SkillSource } from "agents/skills";
2
2
  import type {
3
3
  RuntimeCodeExecutionPort,
4
+ RuntimeGatewayPort,
4
5
  RuntimeMemoryPort,
5
6
  RuntimePlatformPort,
6
7
  RuntimeProviderPort,
@@ -32,209 +33,65 @@ import {
32
33
  type PiToolSurface,
33
34
  type PiRuntimeAssembly,
34
35
  } from "./pi/assembly/snapshot";
36
+ import type { AgentTool } from "@earendil-works/pi-agent-core";
35
37
  import type { PiToolCandidate } from "./pi/tool/compiler";
36
- import { basePiToolCandidates } from "./pi/tool/base";
38
+ import type {
39
+ AssembleRuntimeSnapshotInput,
40
+ RuntimeAgentHooks,
41
+ RuntimeSettings,
42
+ RuntimeExtensionContribution,
43
+ RuntimeProfileContribution,
44
+ RuntimeSkillContribution,
45
+ RuntimeToolSurfacePolicy,
46
+ } from "./runtime-definition";
47
+ import type { ToolRegistry } from "./tool-registry";
48
+ import { withRuntimeLoadTimeout } from "./kernel/runtime-load";
49
+ import {
50
+ basePiToolCandidates,
51
+ memoryPiToolCandidate,
52
+ } from "./pi/tool/base";
37
53
  import {
38
54
  browserQuickActionPiToolCandidates,
39
- codeExecutionPiToolCandidate,
40
55
  } from "./pi/tool/core";
41
- import {
42
- sandboxPiToolCandidates,
43
- workspacePiToolCandidates,
44
- } from "./pi/tool/workspace-sandbox";
45
- import { schedulePiToolCandidates } from "./pi/tool/schedule";
46
56
  import { skillPiToolCandidates } from "./pi/tool/skill";
47
- import { subagentPiToolCandidates } from "./pi/tool/subagent";
48
57
  import { createWebSearch } from "./pi/tool/web-search";
49
58
  import { resolvePiModel } from "./pi/runtime-adapter/models";
50
59
 
51
- /**
52
- * 本文件实现 Plugin 的准备、声明合并、校验和原子候选装配。
53
- *
54
- * @remarks
55
- * 核心术语见包入口 `index.ts`,这里不重复定义。
56
- */
57
-
58
- // #region Plugin 公开契约
59
-
60
- /**
61
- * 标明一个 Plugin 负责哪一类能力。
62
- *
63
- * @remarks
64
- * 应用创建 Plugin 时必须选择其中一个值。
65
- *
66
- * Runtime 用它拒绝重复能力并确定贡献顺序,所以不要把它当作自由文本。
67
- */
68
- export type PluginKind =
69
- | "scope"
70
- | "profile"
71
- | "provider"
72
- | "platform"
73
- | "workspace"
74
- | "sandbox"
75
- | "memory"
76
- | "tool"
77
- | "skill"
78
- | "connector"
79
- | "extension"
80
- | "schedule"
81
- | "subagent"
82
- | "turn-events";
60
+ /** Runtime Assembler:校验一份扁平输入并生成可原子提交的 Snapshot。 */
83
61
 
84
62
  export type { RuntimeDegradation } from "./kernel/degradation";
85
-
86
- /**
87
- * 提供 Runtime 核心运行参数。
88
- *
89
- * @remarks
90
- * `profile` Plugin 在准备结果中返回它。
91
- *
92
- * 这里只放业务可配置字段,冻结和最终结构由 Runtime 负责。
93
- */
94
- export interface RuntimeProfileContribution {
95
- readonly model: string;
96
- readonly thinking: ThinkingEffort;
97
- readonly systemPrompt?: string;
98
- readonly executionLevel: ExecutionLevel;
99
- }
100
-
101
- /**
102
- * 描述一个已准备 Skill。
103
- */
104
- export interface RuntimeSkillContribution {
105
- readonly name: string;
106
- readonly description: string;
107
- readonly source: SkillSource;
108
- readonly script?: Partial<RuntimeSkillScriptPolicy>;
109
- }
110
-
111
- /**
112
- * 描述一个已准备 Extension。
113
- */
114
- export interface RuntimeExtensionContribution {
115
- readonly name: string;
116
- readonly extension: RuntimeExtensionConfig;
117
- }
118
-
119
- /**
120
- * 描述 Agent 对最终 Tool Surface 的可见性约束。
121
- *
122
- * Host 只提供策略;Runtime 在唯一 Tool Surface seam 中对所有
123
- * Port 和 Resource 生成的候选项统一应用。
124
- */
125
- export interface RuntimeToolSurfacePolicy {
126
- readonly denyPolicy?: RuntimeDenyPolicy;
127
- readonly allowsTool?: (name: string) => boolean;
128
- readonly allowsExtension?: (extension: RuntimeExtensionConfig) => boolean;
129
- }
130
-
131
- interface PluginResultByKind {
132
- readonly scope: {
133
- readonly commitGuards: readonly (() => Promise<void>)[];
134
- };
135
- readonly profile: { readonly profile: RuntimeProfileContribution };
136
- readonly provider: { readonly provider: RuntimeProviderPort };
137
- readonly platform: {
138
- readonly platform: RuntimePlatformPort;
139
- };
140
- readonly workspace: {
141
- readonly workspace?: WorkspacePort;
142
- readonly codeExecution?: RuntimeCodeExecutionPort;
143
- };
144
- readonly sandbox: {
145
- readonly sandbox?: RuntimeSandboxPort;
146
- };
147
- readonly memory: {
148
- readonly memoryProfile: RuntimeMemoryProfile;
149
- readonly memoryPort?: RuntimeMemoryPort;
150
- };
151
- readonly tool: {
152
- readonly hostTools: readonly PiToolCandidate[];
153
- readonly policy?: RuntimeToolSurfacePolicy;
154
- };
155
- readonly skill: {
156
- readonly skills: readonly RuntimeSkillContribution[];
157
- };
158
- readonly connector: {
159
- readonly connectors: readonly RuntimeMcpServer[];
160
- };
161
- readonly extension: {
162
- readonly extensions: readonly RuntimeExtensionContribution[];
163
- };
164
- readonly schedule: {
165
- readonly schedule: RuntimeSchedulePort;
166
- };
167
- readonly subagent: {
168
- readonly enabledSubagents: readonly string[];
169
- readonly subagents?: RuntimeSubagentPort;
170
- };
171
- readonly "turn-events": {
172
- readonly turnEvents: RuntimeTurnEventsPort;
173
- };
174
- }
175
-
176
- export type PluginPreparation<K extends PluginKind> = Readonly<
177
- PluginResultByKind[K] & {
178
- readonly degradations: readonly RuntimeDegradation[];
179
- }
180
- >;
181
-
182
- export type PreparedPlugin<K extends PluginKind = PluginKind> =
183
- K extends PluginKind
184
- ? Readonly<PluginPreparation<K> & { readonly kind: K }>
185
- : never;
186
-
187
- interface AgentPluginContract<K extends PluginKind> {
188
- readonly kind: K;
189
- prepare(): Promise<PreparedPlugin<K>>;
190
- }
191
-
192
- export type AgentPlugin<K extends PluginKind = PluginKind> =
193
- K extends PluginKind ? AgentPluginContract<K> : never;
194
-
195
- export interface AgentPluginSpec<K extends PluginKind> {
196
- readonly kind: K;
197
- prepare(): Promise<PluginPreparation<K>>;
198
- }
199
-
200
- /**
201
- * 汇总一次 Runtime 装配要使用的全部 Plugin。
202
- */
203
- export interface AgentConfig {
204
- readonly plugins: readonly AgentPlugin[];
205
- }
206
-
207
- /**
208
- * 定义一个只准备声明式结果的 Runtime Plugin。
209
- *
210
- * Runtime 内部负责排序、合并、冲突检查和提交。
211
- */
212
- export function definePlugin<K extends PluginKind>(
213
- spec: AgentPluginSpec<K>,
214
- ): AgentPlugin<K> {
215
- return Object.freeze({
216
- kind: spec.kind,
217
-
218
- async prepare(): Promise<PreparedPlugin<K>> {
219
- const prepared = await spec.prepare();
220
- const degradations = Object.freeze(
221
- prepared.degradations.map((degradation) =>
222
- Object.freeze({ ...degradation }),
223
- ),
224
- );
225
-
226
- return Object.freeze({
227
- ...prepared,
228
- kind: spec.kind,
229
- degradations,
230
- }) as PreparedPlugin<K>;
231
- },
232
- }) as AgentPlugin<K>;
63
+ export type {
64
+ RuntimeExtensionContribution,
65
+ RuntimeProfileContribution,
66
+ RuntimeSkillContribution,
67
+ RuntimeToolSurfacePolicy,
68
+ } from "./runtime-definition";
69
+
70
+ /** Host Planner Runtime Assembler 之间的一次性内部 seam。 */
71
+ export interface RuntimeAssemblyInput {
72
+ readonly profile: RuntimeProfileContribution;
73
+ readonly memoryProfile: RuntimeMemoryProfile;
74
+ readonly toolPolicy?: RuntimeToolSurfacePolicy;
75
+ readonly enabledSubagents: readonly string[];
76
+ readonly provider: RuntimeProviderPort;
77
+ readonly platform: RuntimePlatformPort;
78
+ readonly workspace?: WorkspacePort;
79
+ readonly codeExecution?: RuntimeCodeExecutionPort;
80
+ readonly sandbox?: RuntimeSandboxPort;
81
+ readonly memory?: RuntimeMemoryPort;
82
+ readonly hostTools: readonly PiToolCandidate[];
83
+ readonly skills: readonly RuntimeSkillContribution[];
84
+ readonly connectors: readonly RuntimeMcpServer[];
85
+ readonly gateway?: RuntimeGatewayPort;
86
+ readonly extensions: readonly RuntimeExtensionContribution[];
87
+ readonly subagents?: RuntimeSubagentPort;
88
+ readonly schedule?: RuntimeSchedulePort;
89
+ readonly turnEvents?: RuntimeTurnEventsPort;
90
+ readonly commitGuards: readonly (() => Promise<void>)[];
91
+ readonly degradations: readonly RuntimeDegradation[];
233
92
  }
234
93
 
235
- // #endregion
236
-
237
- // #region 内部候选契约与装配顺序
94
+ // #region 内部候选契约
238
95
 
239
96
  /**
240
97
  * 保存一次已经完成校验、可供 Kernel 使用的 Runtime 结果。
@@ -256,31 +113,10 @@ export interface RuntimeSnapshot {
256
113
  export interface RuntimeCandidate {
257
114
  readonly snapshot: RuntimeSnapshot;
258
115
  readonly commitGuards: readonly (() => Promise<void>)[];
116
+ readonly hooks?: RuntimeAgentHooks<Cloudflare.Env, unknown, never, never>;
117
+ readonly turnEvents?: RuntimeTurnEventsPort;
259
118
  }
260
119
 
261
- const PLUGIN_ORDER: readonly PluginKind[] = Object.freeze([
262
- "scope",
263
- "profile",
264
- "provider",
265
- "platform",
266
- "workspace",
267
- "sandbox",
268
- "memory",
269
- "tool",
270
- "skill",
271
- "connector",
272
- "extension",
273
- "schedule",
274
- "subagent",
275
- "turn-events",
276
- ]);
277
-
278
- const REQUIRED_PLUGIN_KINDS: readonly PluginKind[] = Object.freeze([
279
- "profile",
280
- "provider",
281
- "platform",
282
- ]);
283
-
284
120
  const DISABLED_MEMORY: RuntimeMemoryProfile = Object.freeze({
285
121
  enabled: false,
286
122
  memoryTokens: 2_000,
@@ -315,14 +151,14 @@ function assertMemoryProfile(profile: RuntimeMemoryProfile): void {
315
151
  interface ToolSurfaceInput {
316
152
  readonly platform: RuntimePlatformPort;
317
153
  readonly workspace?: WorkspacePort;
318
- readonly codeExecution?: RuntimeCodeExecutionPort;
319
- readonly sandbox?: RuntimeSandboxPort;
154
+ readonly memory?: {
155
+ readonly port: RuntimeMemoryPort;
156
+ readonly profile: RuntimeMemoryProfile;
157
+ };
320
158
  readonly hostTools: readonly PiToolCandidate[];
321
159
  readonly skills: readonly RuntimeSkillSourceBinding[];
322
- readonly schedule?: RuntimeSchedulePort;
323
- readonly subagents?: RuntimeSubagentPort;
324
160
  readonly enabledSubagents: readonly string[];
325
- readonly webSearch?: Parameters<typeof basePiToolCandidates>[0];
161
+ readonly webSearch: Parameters<typeof basePiToolCandidates>[0];
326
162
  readonly extensions: readonly RuntimeExtensionConfig[];
327
163
  readonly policy?: RuntimeToolSurfacePolicy;
328
164
  }
@@ -333,45 +169,53 @@ interface ToolSurface {
333
169
  readonly degradations: readonly RuntimeDegradation[];
334
170
  }
335
171
 
336
- // 唯一 Tool Surface seam:只读取已授权 Port、Resource 和 Agent policy,
172
+ // 唯一 Tool Surface seam:只读取已配置 Port、Resource 和 Agent policy,
337
173
  // 统一生成可见 Tool,并在一处做名称、执行档位与冲突校验。
338
- function createToolSurface(input: ToolSurfaceInput): ToolSurface {
174
+ async function createToolSurface(
175
+ input: ToolSurfaceInput,
176
+ ): Promise<ToolSurface> {
177
+ const deny = new Set(input.policy?.denyPolicy?.deny ?? []);
178
+ const allowsTool = input.policy?.allowsTool;
179
+ const allowsExtension = input.policy?.allowsExtension;
180
+ const visible = (candidate: PiToolCandidate) =>
181
+ !deny.has(candidate.tool.name) &&
182
+ allowsTool?.(candidate.tool.name) !== false;
183
+ const browserCandidates = input.platform.browser
184
+ ? browserQuickActionPiToolCandidates(input.platform.browser)
185
+ : [];
186
+ const baseCandidates = basePiToolCandidates(input.webSearch);
187
+ const memoryCandidates = input.memory
188
+ ? [memoryPiToolCandidate(input.memory.port, input.memory.profile)]
189
+ : [];
190
+ const scriptCandidates = [
191
+ ...browserCandidates,
192
+ ...input.hostTools,
193
+ ...baseCandidates,
194
+ ...memoryCandidates,
195
+ ].filter(visible);
339
196
  const staticCandidates = [
340
- ...(input.platform.browser
341
- ? browserQuickActionPiToolCandidates(input.platform.browser)
342
- : []),
343
- ...(input.workspace ? workspacePiToolCandidates(input.workspace) : []),
344
- ...(input.workspace && input.codeExecution
345
- ? [codeExecutionPiToolCandidate(input.codeExecution)]
346
- : []),
347
- ...(input.sandbox ? sandboxPiToolCandidates(input.sandbox) : []),
197
+ ...browserCandidates,
348
198
  ...input.hostTools,
349
- ...skillPiToolCandidates(input.skills, {
199
+ ...(await skillPiToolCandidates(input.skills, {
350
200
  loader: input.platform.loader,
351
- }),
352
- ...(input.schedule ? schedulePiToolCandidates(input.schedule) : []),
353
- ...subagentPiToolCandidates(input.subagents, input.enabledSubagents),
354
- ...basePiToolCandidates(input.webSearch),
201
+ ...(input.workspace ? { workspace: input.workspace } : {}),
202
+ tools: scriptCandidates,
203
+ })),
204
+ ...baseCandidates,
205
+ ...memoryCandidates,
355
206
  ];
356
- const deny = new Set(input.policy?.denyPolicy?.deny ?? []);
357
- const allowsTool = input.policy?.allowsTool;
358
- const allowsExtension = input.policy?.allowsExtension;
359
207
 
360
208
  return {
361
209
  surface: Object.freeze({
362
- finalize(authorizedCandidates: readonly PiToolCandidate[]) {
210
+ finalize(candidates: readonly PiToolCandidate[]) {
363
211
  const tools = new Map<string, PiToolCandidate>();
364
212
 
365
213
  for (const candidate of [
366
214
  ...staticCandidates,
367
- ...authorizedCandidates,
215
+ ...candidates,
368
216
  ]) {
369
217
  const name = requiredName(candidate.tool.name, "Pi Tool");
370
- if (
371
- !candidate.authorized ||
372
- deny.has(name) ||
373
- allowsTool?.(name) === false
374
- ) continue;
218
+ if (!visible(candidate)) continue;
375
219
  if (!EXECUTION_LEVELS.includes(candidate.requiredExecutionLevel)) {
376
220
  throw new Error(`Pi Tool ${name} execution level is invalid`);
377
221
  }
@@ -387,13 +231,7 @@ function createToolSurface(input: ToolSurfaceInput): ToolSurface {
387
231
  extensions: input.extensions.filter(
388
232
  (extension) => allowsExtension?.(extension) !== false,
389
233
  ),
390
- degradations: input.webSearch
391
- ? []
392
- : [{
393
- capability: "web_search",
394
- reason: "unavailable",
395
- detail: "Native web search is unavailable for openrouter-chat",
396
- }],
234
+ degradations: [],
397
235
  };
398
236
  }
399
237
 
@@ -405,6 +243,7 @@ class RuntimeBuilder {
405
243
  private profile?: RuntimeProfileContribution;
406
244
  private provider?: RuntimeProviderPort;
407
245
  private platform?: RuntimePlatformPort;
246
+ private gateway?: RuntimeGatewayPort;
408
247
  private workspace?: WorkspacePort;
409
248
  private codeExecution?: RuntimeCodeExecutionPort;
410
249
  private sandbox?: RuntimeSandboxPort;
@@ -430,68 +269,36 @@ class RuntimeBuilder {
430
269
  private readonly commitGuards: Array<() => Promise<void>> = [];
431
270
  private readonly degradations: RuntimeDegradation[] = [];
432
271
 
433
- merge(prepared: PreparedPlugin): void {
434
- for (const degradation of prepared.degradations) {
272
+ merge(input: RuntimeAssemblyInput): void {
273
+ for (const degradation of input.degradations) {
435
274
  this.reportDegradation(degradation);
436
275
  }
437
-
438
- switch (prepared.kind) {
439
- case "scope":
440
- this.commitGuards.push(...prepared.commitGuards);
441
- break;
442
- case "profile":
443
- if (!EXECUTION_LEVELS.includes(prepared.profile.executionLevel)) {
444
- throw new Error("Runtime execution level is invalid");
445
- }
446
- this.profile = prepared.profile;
447
- break;
448
- case "provider":
449
- this.provider = prepared.provider;
450
- break;
451
- case "platform":
452
- this.platform = prepared.platform;
453
- break;
454
- case "workspace":
455
- this.workspace = prepared.workspace;
456
- this.codeExecution = prepared.codeExecution;
457
- break;
458
- case "sandbox":
459
- this.sandbox = prepared.sandbox;
460
- break;
461
- case "memory":
462
- this.memoryProfile = prepared.memoryProfile;
463
- this.memory = prepared.memoryPort;
464
- break;
465
- case "tool":
466
- this.hostTools.push(...prepared.hostTools);
467
- this.toolPolicy = prepared.policy;
468
- break;
469
- case "skill":
470
- for (const skill of prepared.skills) this.addSkill(skill);
471
- break;
472
- case "connector":
473
- for (const connector of prepared.connectors) {
474
- this.addConnector(connector);
475
- }
476
- break;
477
- case "extension":
478
- for (const { name, extension } of prepared.extensions) {
479
- this.addExtension(name, extension);
480
- }
481
- break;
482
- case "schedule":
483
- this.schedule = prepared.schedule;
484
- break;
485
- case "subagent":
486
- for (const name of prepared.enabledSubagents) {
487
- this.enabledSubagents.add(requiredName(name, "Subagent"));
488
- }
489
- this.subagents = prepared.subagents;
490
- break;
491
- case "turn-events":
492
- this.turnEvents = prepared.turnEvents;
493
- break;
276
+ if (!EXECUTION_LEVELS.includes(input.profile.executionLevel)) {
277
+ throw new Error("Runtime execution level is invalid");
278
+ }
279
+ this.profile = input.profile;
280
+ this.provider = input.provider;
281
+ this.platform = input.platform;
282
+ this.workspace = input.workspace;
283
+ this.codeExecution = input.codeExecution;
284
+ this.sandbox = input.sandbox;
285
+ this.memoryProfile = input.memoryProfile;
286
+ this.memory = input.memory;
287
+ this.hostTools.push(...input.hostTools);
288
+ this.toolPolicy = input.toolPolicy;
289
+ for (const skill of input.skills) this.addSkill(skill);
290
+ for (const connector of input.connectors) this.addConnector(connector);
291
+ this.gateway = input.gateway;
292
+ for (const { name, extension } of input.extensions) {
293
+ this.addExtension(name, extension);
294
+ }
295
+ this.schedule = input.schedule;
296
+ for (const name of input.enabledSubagents) {
297
+ this.enabledSubagents.add(requiredName(name, "Subagent"));
494
298
  }
299
+ this.subagents = input.subagents;
300
+ this.turnEvents = input.turnEvents;
301
+ this.commitGuards.push(...input.commitGuards);
495
302
  }
496
303
 
497
304
  private addSkill(skill: RuntimeSkillContribution): void {
@@ -578,17 +385,17 @@ class RuntimeBuilder {
578
385
  // #region RuntimeBuilder 校验与候选生成
579
386
 
580
387
  // 作用:校验全部声明,并生成一个不可变候选 Runtime。
581
- // 调用:所有 Plugin 按固定顺序完成合并后调用一次。
388
+ // 调用:扁平 Assembly Input 合并完成后调用一次。
582
389
  // 原因:所有交叉约束都在这里通过后才返回候选,旧 Snapshot 不会半更新。
583
- build(): RuntimeCandidate {
390
+ async build(): Promise<RuntimeCandidate> {
584
391
  if (!this.profile) {
585
- throw new Error("Runtime profile Plugin did not configure a profile");
392
+ throw new Error("Runtime assembly did not configure a profile");
586
393
  }
587
394
  if (!this.platform) {
588
- throw new Error("Runtime platform Plugin did not bind a platform");
395
+ throw new Error("Runtime assembly did not bind a platform");
589
396
  }
590
397
  if (!this.provider) {
591
- throw new Error("Runtime provider Plugin did not bind a provider");
398
+ throw new Error("Runtime assembly did not bind a provider");
592
399
  }
593
400
  if (!this.profile.model.trim()) {
594
401
  throw new Error("Runtime model must not be empty");
@@ -617,17 +424,7 @@ class RuntimeBuilder {
617
424
  assertMemoryProfile(this.memoryProfile);
618
425
  if (this.memoryProfile.enabled && (!this.workspace || !this.memory)) {
619
426
  throw new Error(
620
- "Enabled Runtime memory requires Workspace and Memory ports",
621
- );
622
- }
623
- if (this.sandbox && !this.workspace) {
624
- throw new Error(
625
- "Runtime Sandbox requires a persistent Workspace port",
626
- );
627
- }
628
- if (this.enabledSubagents.size > 0 && !this.subagents) {
629
- throw new Error(
630
- "Enabled Runtime Subagents require a Subagent port",
427
+ "Enabled Runtime memory requires Workspace and Memory bindings",
631
428
  );
632
429
  }
633
430
  const profile = freezeRuntimeProfile({
@@ -664,26 +461,23 @@ class RuntimeBuilder {
664
461
  }
665
462
  const resolvedModel = resolvePiModel(provider, profile.model);
666
463
  const endpoint = endpoints[0]!;
667
- const webSearch = endpoint.protocol === "openrouter-chat"
668
- ? undefined
669
- : createWebSearch({
670
- endpoint,
671
- model: profile.model,
672
- maxTokens: resolvedModel.maxTokens,
673
- reasoning: resolvedModel.reasoning,
674
- });
464
+ const webSearch = createWebSearch({
465
+ endpoint,
466
+ model: profile.model,
467
+ maxTokens: resolvedModel.maxTokens,
468
+ reasoning: resolvedModel.reasoning,
469
+ });
675
470
  const skillSources = [...this.skillSources.values()];
676
- const toolSurface = createToolSurface({
471
+ const toolSurface = await createToolSurface({
677
472
  platform: this.platform,
678
473
  ...(this.workspace ? { workspace: this.workspace } : {}),
679
- ...(this.codeExecution ? { codeExecution: this.codeExecution } : {}),
680
- ...(this.sandbox ? { sandbox: this.sandbox } : {}),
474
+ ...(this.memoryProfile.enabled && this.memory
475
+ ? { memory: { port: this.memory, profile: this.memoryProfile } }
476
+ : {}),
681
477
  hostTools: this.hostTools,
682
478
  skills: skillSources,
683
- ...(this.schedule ? { schedule: this.schedule } : {}),
684
- ...(this.subagents ? { subagents: this.subagents } : {}),
685
479
  enabledSubagents: [...this.enabledSubagents],
686
- ...(webSearch ? { webSearch } : {}),
480
+ webSearch,
687
481
  extensions: [...this.extensions.values()],
688
482
  ...(this.toolPolicy ? { policy: this.toolPolicy } : {}),
689
483
  });
@@ -693,6 +487,7 @@ class RuntimeBuilder {
693
487
  const bindings: RuntimeBindings = Object.freeze({
694
488
  provider,
695
489
  platform: Object.freeze({ ...this.platform }),
490
+ ...(this.gateway ? { gateway: this.gateway } : {}),
696
491
  ...(this.workspace ? { workspace: this.workspace } : {}),
697
492
  ...(this.memoryProfile.enabled && this.memory
698
493
  ? { memory: this.memory }
@@ -727,75 +522,23 @@ class RuntimeBuilder {
727
522
 
728
523
  }
729
524
 
730
- // 作用:检查 Plugin kind 合法、唯一,并包含所有必需能力。
731
- // 调用:任何 prepare 启动前由 `prepareRuntimeCandidate` 调用。
732
- // 原因:先校验结构可避免为注定失败的配置执行外部读取。
733
- function validatePluginKinds(
734
- plugins: readonly AgentPlugin[],
735
- ): void {
736
- const kinds = new Set<PluginKind>();
737
- const knownKinds = new Set(PLUGIN_ORDER);
738
-
739
- for (const plugin of plugins) {
740
- if (!knownKinds.has(plugin.kind)) {
741
- throw new Error(`Unknown Agent Plugin kind: ${plugin.kind}`);
742
- }
743
- if (kinds.has(plugin.kind)) {
744
- throw new Error(
745
- `Duplicate Agent Plugin kind: ${plugin.kind}`,
746
- );
747
- }
748
- kinds.add(plugin.kind);
749
- }
750
-
751
- for (const required of REQUIRED_PLUGIN_KINDS) {
752
- if (!kinds.has(required)) {
753
- throw new Error(
754
- `Missing required Agent Plugin: ${required}`,
755
- );
756
- }
757
- }
758
- }
759
-
760
525
  /**
761
526
  * 在不改动当前 Runtime 的前提下生成一个隔离候选。
762
527
  *
763
528
  * @remarks
764
529
  * `AgentRuntimeKernel` 在初始化或重载时调用它。
765
530
  *
766
- * prepare 并行执行,声明按固定 kind 顺序合并,任何失败都不会产生 commit。
531
+ * 任何合并、校验或 Tool Surface 构建失败都不会产生 commit。
767
532
  *
768
533
  * 该函数有意不从包入口导出,提交生命周期只能由 Kernel 控制。
769
534
  *
770
535
  * @internal
771
536
  */
772
537
  export async function prepareRuntimeCandidate(
773
- config: AgentConfig,
538
+ input: RuntimeAssemblyInput,
774
539
  ): Promise<RuntimeCandidate> {
775
- validatePluginKinds(config.plugins);
776
-
777
- const prepared = await Promise.all(
778
- config.plugins.map(async (plugin) => {
779
- const result = await plugin.prepare();
780
- if (result.kind !== plugin.kind) {
781
- throw new Error(
782
- `Prepared Agent Plugin kind mismatch: expected ${plugin.kind}, got ${result.kind}`,
783
- );
784
- }
785
- return result;
786
- }),
787
- );
788
- const byKind = new Map(
789
- prepared.map((plugin) => [plugin.kind, plugin]),
790
- );
791
540
  const builder = new RuntimeBuilder();
792
-
793
- for (const kind of PLUGIN_ORDER) {
794
- const plugin = byKind.get(kind);
795
- if (!plugin) continue;
796
- builder.merge(plugin);
797
- }
798
-
541
+ builder.merge(input);
799
542
  return builder.build();
800
543
  }
801
544
 
@@ -809,15 +552,246 @@ export async function prepareRuntimeCandidate(
809
552
  *
810
553
  * @internal
811
554
  */
812
- export async function initializeRuntimeConfig(
813
- config: AgentConfig,
555
+ export async function assembleRuntime(
556
+ input: RuntimeAssemblyInput,
814
557
  commit: (snapshot: RuntimeSnapshot) => void,
815
558
  ): Promise<void> {
816
- const candidate = await prepareRuntimeCandidate(config);
559
+ const candidate = await prepareRuntimeCandidate(input);
817
560
  for (const guard of candidate.commitGuards) {
818
561
  await guard();
819
562
  }
820
563
  commit(candidate.snapshot);
821
564
  }
822
565
 
566
+ export function toolRegistryPiToolCandidates(
567
+ registry: ToolRegistry,
568
+ settings?: Pick<RuntimeSettings, "toolPolicy">,
569
+ ): PiToolCandidate[] {
570
+ const deny = new Set(settings?.toolPolicy?.denyPolicy?.deny ?? []);
571
+ const allowsTool = settings?.toolPolicy?.allowsTool;
572
+ const candidates: PiToolCandidate[] = [];
573
+ for (const [name, spec] of Object.entries(registry)) {
574
+ if (deny.has(name)) continue;
575
+ if (allowsTool?.(name) === false) continue;
576
+ const normalized = requiredName(name, "Platform Tool");
577
+ candidates.push(Object.freeze({
578
+ owner: "platform",
579
+ requiredExecutionLevel: spec.requiredExecutionLevel,
580
+ ...(spec.alwaysRequiresApproval
581
+ ? { alwaysRequiresApproval: true }
582
+ : {}),
583
+ tool: Object.freeze({
584
+ name: normalized,
585
+ label: spec.label,
586
+ description: spec.description,
587
+ parameters: spec.parameters,
588
+ async execute(
589
+ toolCallId: string,
590
+ input: unknown,
591
+ signal?: AbortSignal,
592
+ ) {
593
+ const result = await spec.execute(input, {
594
+ toolCallId,
595
+ signal: signal ?? new AbortController().signal,
596
+ });
597
+ if (
598
+ result != null &&
599
+ typeof result === "object" &&
600
+ "content" in result &&
601
+ Array.isArray((result as { content?: unknown }).content)
602
+ ) {
603
+ return result as import("@earendil-works/pi-agent-core").AgentToolResult<unknown>;
604
+ }
605
+ let text: string;
606
+ try {
607
+ text = JSON.stringify(result) ?? String(result);
608
+ } catch {
609
+ text = String(result);
610
+ }
611
+ return {
612
+ content: [{ type: "text" as const, text }],
613
+ details: result,
614
+ };
615
+ },
616
+ } as AgentTool<any, any>),
617
+ }));
618
+ }
619
+ return candidates;
620
+ }
621
+
622
+ function hooksToTurnEventsPort<
623
+ Env extends Cloudflare.Env,
624
+ Config,
625
+ Command,
626
+ Change,
627
+ >(
628
+ ctx: AssembleRuntimeSnapshotInput<Env, Config, Command, Change>["ctx"],
629
+ hooks?: RuntimeAgentHooks<Env, Config, Command, Change>,
630
+ ): RuntimeTurnEventsPort | undefined {
631
+ if (!hooks) return undefined;
632
+ if (
633
+ !hooks.onTurnEnd &&
634
+ !hooks.onModelUsage &&
635
+ !hooks.onToolStart &&
636
+ !hooks.onToolSettled &&
637
+ !hooks.onApproval &&
638
+ !hooks.onActivityChanged &&
639
+ !hooks.onSubmissionTerminal
640
+ ) {
641
+ return undefined;
642
+ }
643
+ return {
644
+ onResponse: hooks.onTurnEnd
645
+ ? (messages) => hooks.onTurnEnd!(ctx, messages)
646
+ : async () => undefined,
647
+ ...(hooks.onModelUsage ? { onModelUsage: hooks.onModelUsage } : {}),
648
+ ...(hooks.onToolStart ? { onToolStart: hooks.onToolStart } : {}),
649
+ ...(hooks.onToolSettled ? { onToolSettled: hooks.onToolSettled } : {}),
650
+ ...(hooks.onApproval ? { onApproval: hooks.onApproval } : {}),
651
+ ...(hooks.onActivityChanged
652
+ ? { onActivityChanged: hooks.onActivityChanged }
653
+ : {}),
654
+ ...(hooks.onSubmissionTerminal
655
+ ? { onSubmissionTerminal: hooks.onSubmissionTerminal }
656
+ : {}),
657
+ };
658
+ }
659
+
660
+ /**
661
+ * 从 Config、Tool 和已加载 Resource 装配 Runtime 候选 Snapshot。
662
+ *
663
+ * @internal
664
+ */
665
+ export async function assembleRuntimeSnapshot<
666
+ Env extends Cloudflare.Env,
667
+ Config,
668
+ Command = never,
669
+ Change = never,
670
+ >(
671
+ input: AssembleRuntimeSnapshotInput<Env, Config, Command, Change>,
672
+ ): Promise<RuntimeCandidate> {
673
+ const resources = input.resources;
674
+ const toolAssembly = input.tools;
675
+ const settings = resources.settings;
676
+ const provider = resources.provider;
677
+ let profile = settings.profile;
678
+ if (input.hooks?.profileOverrides) {
679
+ const overrides = await withRuntimeLoadTimeout(
680
+ "profile-overrides",
681
+ () => input.hooks!.profileOverrides!(
682
+ input.ctx,
683
+ input.config,
684
+ ),
685
+ );
686
+ if (overrides.systemPrompt !== undefined) {
687
+ profile = { ...profile, systemPrompt: overrides.systemPrompt };
688
+ }
689
+ if (overrides.executionLevel !== undefined) {
690
+ profile = { ...profile, executionLevel: overrides.executionLevel };
691
+ }
692
+ }
693
+
694
+ const hostTools = toolRegistryPiToolCandidates(
695
+ toolAssembly.tools,
696
+ settings,
697
+ );
698
+
699
+ const commitGuards: Array<() => Promise<void>> = [];
700
+ if (toolAssembly.commitGuard) commitGuards.push(toolAssembly.commitGuard);
701
+ if (input.hooks?.beforeCommit) {
702
+ for (const guard of input.hooks.beforeCommit) {
703
+ commitGuards.push(() => guard(input.ctx, input.config));
704
+ }
705
+ }
706
+
707
+ const platform: RuntimePlatformPort = input.hooks?.gateTool
708
+ ? Object.freeze({
709
+ ...resources.platform,
710
+ gateTool: (request: {
711
+ toolCallId: string;
712
+ toolName: string;
713
+ input: unknown;
714
+ requiredExecutionLevel: import("./lib/execution-level").ExecutionLevel;
715
+ signal: AbortSignal;
716
+ }) => input.hooks!.gateTool!(request),
717
+ })
718
+ : resources.platform;
719
+
720
+ const memoryProfile =
721
+ toolAssembly.memoryProfile ?? settings.memoryProfile;
722
+ const enabledSubagents =
723
+ toolAssembly.enabledSubagents ?? settings.enabledSubagents;
724
+ const turnEvents = hooksToTurnEventsPort(input.ctx, input.hooks);
725
+
726
+ const toolPolicy: RuntimeToolSurfacePolicy | undefined =
727
+ settings.toolPolicy || toolAssembly.surfacePolicy
728
+ ? {
729
+ ...(settings.toolPolicy?.denyPolicy
730
+ ? { denyPolicy: settings.toolPolicy.denyPolicy }
731
+ : {}),
732
+ ...(settings.toolPolicy?.allowsTool
733
+ ? { allowsTool: settings.toolPolicy.allowsTool }
734
+ : {}),
735
+ ...(settings.toolPolicy?.allowsExtension
736
+ ? { allowsExtension: settings.toolPolicy.allowsExtension }
737
+ : {}),
738
+ ...(toolAssembly.surfacePolicy?.allowsTool
739
+ ? { allowsTool: toolAssembly.surfacePolicy.allowsTool }
740
+ : {}),
741
+ ...(toolAssembly.surfacePolicy?.allowsExtension
742
+ ? { allowsExtension: toolAssembly.surfacePolicy.allowsExtension }
743
+ : {}),
744
+ }
745
+ : undefined;
746
+
747
+ const assemblyInput: RuntimeAssemblyInput = {
748
+ profile,
749
+ memoryProfile,
750
+ ...(toolPolicy ? { toolPolicy } : {}),
751
+ enabledSubagents: [...enabledSubagents],
752
+ provider,
753
+ platform,
754
+ ...(toolAssembly.bindings?.workspace
755
+ ? { workspace: toolAssembly.bindings.workspace }
756
+ : {}),
757
+ ...(memoryProfile.enabled && toolAssembly.bindings?.memory
758
+ ? { memory: toolAssembly.bindings.memory }
759
+ : {}),
760
+ hostTools,
761
+ skills: resources.skills,
762
+ connectors: resources.connectors.servers,
763
+ ...(resources.connectors.gateway
764
+ ? { gateway: resources.connectors.gateway }
765
+ : {}),
766
+ extensions: resources.extensions,
767
+ ...(turnEvents ? { turnEvents } : {}),
768
+ commitGuards: [],
769
+ degradations: [
770
+ ...(toolAssembly.degradations ?? []),
771
+ ...(resources.degradations ?? []),
772
+ ...resources.connectors.degradations,
773
+ ],
774
+ };
775
+
776
+ const candidate = await prepareRuntimeCandidate(assemblyInput);
777
+ return Object.freeze({
778
+ snapshot: candidate.snapshot,
779
+ commitGuards: Object.freeze([
780
+ ...candidate.commitGuards,
781
+ ...commitGuards,
782
+ ]),
783
+ ...(input.hooks
784
+ ? {
785
+ hooks: input.hooks as unknown as RuntimeAgentHooks<
786
+ Cloudflare.Env,
787
+ unknown,
788
+ never,
789
+ never
790
+ >,
791
+ }
792
+ : {}),
793
+ ...(turnEvents ? { turnEvents } : {}),
794
+ });
795
+ }
796
+
823
797
  // #endregion