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

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 +1 -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
@@ -11,6 +11,7 @@ import type {
11
11
  RuntimeExtensionPermissions,
12
12
  } from "../../kernel/extensions";
13
13
  import type { RuntimeDegradation } from "../../kernel/degradation";
14
+ import { withRuntimeLoadTimeout } from "../../kernel/runtime-load";
14
15
  import { sanitizeExtensionName } from "../../lib/extension-name";
15
16
  import type { PiToolCandidate } from "../tool/compiler";
16
17
 
@@ -42,9 +43,6 @@ export interface PiLoadedExtension {
42
43
 
43
44
  export interface AssemblePiExtensionsOptions {
44
45
  readonly extensions: readonly RuntimeExtensionConfig[];
45
- readonly published: ReadonlySet<string>;
46
- readonly enabled: ReadonlySet<string>;
47
- readonly authorized: ReadonlySet<string>;
48
46
  readonly load: (
49
47
  extension: RuntimeExtensionConfig,
50
48
  ) => Promise<LoadedRuntimeExtension>;
@@ -237,7 +235,7 @@ const EXTENSION_HOOK_NAMES = [
237
235
  * 通过 Cloudflare ExtensionManager 加载一个 Extension,并只暴露 Runtime 需要的描述和执行能力。
238
236
  *
239
237
  * @remarks
240
- * Runtime 准备阶段会对已授权 Extension 调用,资源发布的 smoke test 也会用它校验候选源码;调用方必须提供 WorkerLoader,并在权限需要时提供 Host 绑定工厂。
238
+ * Runtime 准备阶段会对已配置 Extension 调用,资源发布的 smoke test 也会用它校验候选源码;调用方必须提供 WorkerLoader,并在权限需要时提供 Host 绑定工厂。
241
239
  *
242
240
  * discovery 使用剔除权限的 manifest,有外部访问的执行 isolate 则延迟到首次 Tool 执行时创建。这是为了防止 `describe()` 在授权和审批前获得网络或 Host 能力,不能随意合并两条加载路径。
243
241
  *
@@ -267,9 +265,12 @@ export async function loadPiExtension(
267
265
  const { permissions: _ignored, ...manifestWithoutPermissions } =
268
266
  extension.manifest;
269
267
  try {
270
- await discovery.load(
271
- manifestWithoutPermissions as ExtensionManifest,
272
- extension.source,
268
+ await withRuntimeLoadTimeout(
269
+ `extension:${name}:discover`,
270
+ () => discovery.load(
271
+ manifestWithoutPermissions as ExtensionManifest,
272
+ extension.source,
273
+ ),
273
274
  );
274
275
  } catch (cause) {
275
276
  // Upstream assumes describe() yields an array and throws an opaque TypeError
@@ -507,20 +508,17 @@ export function extensionToolRequiredExecutionLevel(
507
508
  }
508
509
 
509
510
  /**
510
- * 把当前已发布、已启用且已授权的 Extension 装配成 Runtime 能力。
511
+ * 把当前已配置的 Extension 装配成 Runtime 能力。
511
512
  *
512
513
  * @remarks
513
- * Runtime 准备阶段和资源 smoke test 调用;调用方传入三个选择集合及一个已经确定加载策略的 `load`。
514
+ * Runtime 准备阶段和资源 smoke test 调用;调用方传入已选 Extension 及已确定加载策略的 `load`。
514
515
  *
515
- * 只有同时通过三道选择的 Extension 才会被加载,单个加载失败记为 degradation 而不影响其他项;不要绕过这个交集直接注册 Tool。
516
+ * 单个加载失败记为 degradation 而不影响其他项。
516
517
  *
517
518
  * 核心术语见包入口 `index.ts`。
518
519
  */
519
520
  export async function assemblePiExtensions({
520
521
  extensions,
521
- published,
522
- enabled,
523
- authorized,
524
522
  load,
525
523
  }: AssemblePiExtensionsOptions): Promise<PiExtensionAssembly> {
526
524
  const loadedExtensions: PiLoadedExtension[] = [];
@@ -530,14 +528,6 @@ export async function assemblePiExtensions({
530
528
 
531
529
  for (const extension of extensions) {
532
530
  const name = extension.manifest.name.trim();
533
- if (
534
- !published.has(name) ||
535
- !enabled.has(name) ||
536
- !authorized.has(name)
537
- ) {
538
- continue;
539
- }
540
-
541
531
  let loaded: LoadedRuntimeExtension;
542
532
  try {
543
533
  loaded = await load(extension);
@@ -608,7 +598,6 @@ export async function assemblePiExtensions({
608
598
  });
609
599
  candidates.push(Object.freeze({
610
600
  owner: `extension:${name}@${extension.manifest.version}`,
611
- authorized: true,
612
601
  requiredExecutionLevel,
613
602
  summary: description,
614
603
  tool,
@@ -3,6 +3,7 @@ import type {
3
3
  ThinkingLevel,
4
4
  } from "@earendil-works/pi-agent-core";
5
5
  import type { Api, Model } from "@earendil-works/pi-ai";
6
+ import { clampReasoning } from "@earendil-works/pi-ai/api/simple-options";
6
7
  import type {
7
8
  RuntimeMcpServer,
8
9
  RuntimeProfile,
@@ -21,7 +22,7 @@ import type { PiToolCandidate } from "../tool/compiler";
21
22
  */
22
23
  export interface PiToolSurface {
23
24
  finalize(
24
- authorizedCandidates: readonly PiToolCandidate[],
25
+ candidates: readonly PiToolCandidate[],
25
26
  ): readonly PiToolCandidate[];
26
27
  }
27
28
 
@@ -79,11 +80,13 @@ function freezeModel(model: Model<Api>): Model<Api> {
79
80
 
80
81
  // 作用:把 Runtime 的思考强度名称转成 Pi Agent Core 接受的名称。
81
82
  // 调用:创建 Runtime 装配快照时对 profile 中的 `thinking` 调用。
82
- // 原因:只有 `none` Pi 的 `off` 命名不同,其他级别应保持原值。
83
+ // 原因:`none` 映射成 `off`;xhigh/max 用 clampReasoning 夹到 API 真正支持的最高档,
84
+ // 避免超出范围的等级在 provider 侧静默失败。
83
85
  function toPiThinkingLevel(
84
86
  thinking: ThinkingEffort,
85
87
  ): ThinkingLevel {
86
- return thinking === "none" ? "off" : thinking;
88
+ if (thinking === "none") return "off";
89
+ return clampReasoning(thinking) ?? "off";
87
90
  }
88
91
 
89
92
  // 作用:复制并冻结一个 Extension 配置的 manifest 和权限集合。
@@ -2,6 +2,13 @@ import type { UIMessage } from "ai";
2
2
 
3
3
  export type UIChatTrigger = "submit-message" | "regenerate-message";
4
4
 
5
+ /** A user-selected Runtime capability persisted with the visible UIMessage. */
6
+ export interface RequestedCapability {
7
+ readonly kind: "skill" | "plan";
8
+ readonly name: string;
9
+ readonly label: string;
10
+ }
11
+
5
12
  export interface UIChatRequestBody {
6
13
  readonly messages: readonly UIMessage[];
7
14
  readonly trigger: UIChatTrigger;
@@ -26,6 +26,7 @@ function attachmentText(
26
26
 
27
27
  function userContent(
28
28
  parts: ReadonlyArray<UIMessage["parts"][number]>,
29
+ privateContext?: string,
29
30
  ): UserMessage["content"] {
30
31
  const content: Exclude<UserMessage["content"], string> = [];
31
32
  for (const part of parts) {
@@ -46,16 +47,23 @@ function userContent(
46
47
  }
47
48
  }
48
49
  if (content.length === 0) throw new Error("User message content is required");
50
+ if (privateContext) {
51
+ content.unshift({
52
+ type: "text",
53
+ text: `[Agent private context]\n${privateContext}\n[/Agent private context]`,
54
+ });
55
+ }
49
56
  return content;
50
57
  }
51
58
 
52
59
  /** Convert an official user UIMessage into canonical Pi model input. */
53
60
  export function uiUserMessageToPi(
54
61
  message: UIMessage & { readonly role: "user" },
62
+ privateContext?: string,
55
63
  ): UserMessage {
56
64
  return {
57
65
  role: "user",
58
- content: userContent(message.parts),
66
+ content: userContent(message.parts, privateContext),
59
67
  timestamp: timestampOf(message),
60
68
  };
61
69
  }
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  RuntimeBindings,
3
+ RuntimeGatewaySession,
3
4
  RuntimePlatformPort,
4
5
  } from "../../kernel/bindings";
5
6
  import type { RuntimeDegradation } from "../../kernel/degradation";
@@ -8,7 +9,7 @@ import type {
8
9
  } from "../../kernel/extensions";
9
10
  import type { RuntimeProfile } from "../../kernel/profile";
10
11
  import type { ExecutionLevel } from "../../lib/execution-level";
11
- import type { RuntimeSnapshot } from "../../plugins";
12
+ import type { RuntimeSnapshot } from "../../runtime-assembler";
12
13
  import type { PiRuntimeAssembly } from "../assembly";
13
14
  import {
14
15
  assemblePiSystemContext,
@@ -19,6 +20,7 @@ import {
19
20
  type PiLoadedExtension,
20
21
  } from "../assembly";
21
22
  import {
23
+ createPiGatewayToolCandidates,
22
24
  createPiMcpToolCandidates,
23
25
  listExtensionsPiToolCandidate,
24
26
  type PiMcpHost,
@@ -58,6 +60,7 @@ interface PiAssemblySnapshot {
58
60
  interface PreparePiAssemblyOptions {
59
61
  readonly snapshot: PiAssemblySnapshot;
60
62
  readonly mcpHost: PiMcpHost;
63
+ readonly gatewaySession?: RuntimeGatewaySession;
61
64
  // 为一个扩展生成带权限参数的 Host Fetcher。
62
65
  // loadPiExtension 在扩展需要 Host 能力时调用它。
63
66
  // Worker 掌握真实绑定,装配层只保存这个窄函数,不能自行扩大权限。
@@ -90,6 +93,8 @@ interface PreparedPiAssembly {
90
93
  export interface PreparePiRuntimeOptions {
91
94
  readonly snapshot: RuntimeSnapshot;
92
95
  readonly mcpHost: PiMcpHost;
96
+ readonly gatewaySession?: RuntimeGatewaySession;
97
+ readonly additionalDegradations?: readonly RuntimeDegradation[];
93
98
  /**
94
99
  * 为一个扩展创建受限的 Host 绑定。
95
100
  *
@@ -115,6 +120,7 @@ export interface PreparePiRuntimeOptions {
115
120
  export interface PreparedPiRuntime {
116
121
  readonly revisionDescriptor: string;
117
122
  readonly degradations: readonly RuntimeDegradation[];
123
+ readonly extensions: readonly { readonly name: string; readonly version: string }[];
118
124
  }
119
125
 
120
126
  /**
@@ -160,7 +166,7 @@ export interface PinnedPiRuntime {
160
166
  * @remarks
161
167
  * `PreparedPiTurnAdapter` 在重建 Turn 前读取这个 JSON 结构。
162
168
  *
163
- * 版本号和基础版本校验用于阻止恢复落到不兼容的 Runtime 状态。
169
+ * 基础版本作为准入时元数据保留,创建 Turn 时不与当前 Runtime 比较。
164
170
  */
165
171
  export interface PinnedPiRuntimeDescriptor {
166
172
  readonly version: 1;
@@ -200,6 +206,8 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
200
206
  "browser_markdown",
201
207
  "browser_scrape",
202
208
  "bind_resource",
209
+ "delete",
210
+ "edit",
203
211
  "find",
204
212
  "get_time",
205
213
  "grep",
@@ -211,6 +219,7 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
211
219
  "read_skill_resource",
212
220
  "sandbox_process_logs",
213
221
  "web_search",
222
+ "write",
214
223
  "unbind_resource",
215
224
  ]);
216
225
 
@@ -239,10 +248,12 @@ function describeTools(candidates: readonly PiToolCandidate[]) {
239
248
  name: candidate.tool.name,
240
249
  owner: candidate.owner,
241
250
  label: candidate.tool.label,
242
- description:
243
- candidate.tool.description ?? candidate.tool.label,
251
+ description: candidate.tool.description ?? candidate.tool.label,
244
252
  parameters: candidate.tool.parameters,
245
253
  requiredExecutionLevel: candidate.requiredExecutionLevel,
254
+ ...(candidate.alwaysRequiresApproval
255
+ ? { alwaysRequiresApproval: true }
256
+ : {}),
246
257
  ...(candidate.source ? { source: candidate.source } : {}),
247
258
  retry: piToolRetryPolicy(candidate),
248
259
  }))
@@ -251,7 +262,7 @@ function describeTools(candidates: readonly PiToolCandidate[]) {
251
262
 
252
263
  // 把本模块认定会影响版本的准备结果序列化出来。
253
264
  // preparePiRuntime 创建一次,Runtime 接受快照前再对结果求哈希。
254
- // 集合排序保证等价输入稳定;增删字段会改变版本哈希和恢复兼容性。
265
+ // 集合排序保证等价输入稳定;增删字段会改变版本哈希。
255
266
  function describeRuntime(
256
267
  snapshot: RuntimeSnapshot,
257
268
  candidates: readonly PiToolCandidate[],
@@ -298,6 +309,7 @@ class PreparedRuntime implements PreparedPiRuntime {
298
309
  constructor(
299
310
  readonly revisionDescriptor: string,
300
311
  readonly degradations: readonly RuntimeDegradation[],
312
+ readonly extensions: readonly { readonly name: string; readonly version: string }[],
301
313
  private readonly owner: object,
302
314
  private readonly state: PreparedPiRuntimeState,
303
315
  ) {}
@@ -334,18 +346,17 @@ export function readPreparedPiRuntime(
334
346
  }
335
347
 
336
348
  /**
337
- * 校验已存的固定描述是否仍与当前准备好的 Runtime 兼容。
349
+ * 读取已存的固定描述。
338
350
  *
339
351
  * @remarks
340
352
  * `PreparedPiTurnAdapter` 在新建或恢复 Pi Turn 前调用它。
341
353
  *
342
- * 只有固定阶段写入的提示词允许不同,其余准备字段及基础版本必须匹配。
354
+ * Prepared Runtime 提供当前执行能力,descriptor 仅保留 Submission 固定的 Turn 字段。
343
355
  */
344
356
  export function readPinnedPiRuntime(
345
357
  prepared: PreparedPiRuntime,
346
358
  owner: object,
347
359
  descriptorBody: string,
348
- baseRevision: string,
349
360
  ): {
350
361
  readonly state: PreparedPiRuntimeState;
351
362
  readonly descriptor: PinnedPiRuntimeDescriptor;
@@ -361,24 +372,11 @@ export function readPinnedPiRuntime(
361
372
  throw new Error("Pinned Runtime assembly descriptor is invalid");
362
373
  }
363
374
  const descriptor = value as PinnedPiRuntimeDescriptor;
364
- const expected = JSON.parse(prepared.revisionDescriptor) as Record<
365
- string,
366
- unknown
367
- >;
368
- const actual = descriptor as unknown as Record<string, unknown>;
369
- const incompatible = Object.keys(expected).some((key) =>
370
- key !== "systemPrompt" &&
371
- JSON.stringify(actual[key]) !== JSON.stringify(expected[key])
372
- );
373
375
  if (
374
376
  descriptor.version !== 1 ||
375
- descriptor.baseRevision !== baseRevision ||
376
- typeof descriptor.systemPrompt !== "string" ||
377
- incompatible
377
+ typeof descriptor.systemPrompt !== "string"
378
378
  ) {
379
- throw new Error(
380
- "Pinned Runtime revision is unavailable for Pi recovery",
381
- );
379
+ throw new Error("Pinned Runtime assembly descriptor is invalid");
382
380
  }
383
381
  return { state, descriptor };
384
382
  }
@@ -394,16 +392,8 @@ async function preparePiAssembly(
394
392
  options: PreparePiAssemblyOptions,
395
393
  ): Promise<PreparedPiAssembly> {
396
394
  const { snapshot } = options;
397
- const extensionNames = new Set(
398
- snapshot.pi.extensions.map((extension) =>
399
- extension.manifest.name.trim()
400
- ),
401
- );
402
395
  const extensions = await assemblePiExtensions({
403
396
  extensions: snapshot.pi.extensions,
404
- published: extensionNames,
405
- enabled: extensionNames,
406
- authorized: extensionNames,
407
397
  load: async (extension) => {
408
398
  const workspacePermission =
409
399
  extension.manifest.permissions?.workspace ?? "none";
@@ -425,6 +415,9 @@ async function preparePiAssembly(
425
415
  candidates: snapshot.pi.toolSurface.finalize([
426
416
  ...extensions.candidates,
427
417
  listExtensionsPiToolCandidate(extensions.loaded),
418
+ ...(options.gatewaySession
419
+ ? createPiGatewayToolCandidates(options.gatewaySession)
420
+ : []),
428
421
  ...createPiMcpToolCandidates(
429
422
  options.mcpHost,
430
423
  snapshot.profile.mcpServers,
@@ -455,8 +448,10 @@ export async function preparePiRuntime(
455
448
  describeRuntime(options.snapshot, candidates, assembly.loaded),
456
449
  Object.freeze([
457
450
  ...options.snapshot.degradations,
451
+ ...(options.additionalDegradations ?? []),
458
452
  ...assembly.degradations,
459
453
  ]),
454
+ Object.freeze(assembly.loaded.map(({ name, version }) => ({ name, version }))),
460
455
  owner,
461
456
  Object.freeze({
462
457
  snapshot: options.snapshot,