@springbrand/agent-runtime 0.1.3-alpha.3 → 0.1.3-alpha.5

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