@springbrand/agent-runtime 0.1.3-alpha.0 → 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.
- package/package.json +12 -3
- package/src/adapter/cloudflare/index.ts +56 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +89 -0
- package/src/adapter/cloudflare/sandbox/adapter.ts +1513 -0
- package/src/adapter/cloudflare/sandbox/id.ts +23 -0
- package/src/adapter/cloudflare/sandbox/policy.ts +15 -0
- package/src/adapter/cloudflare/subagent/definition.ts +574 -0
- package/src/adapter/cloudflare/subagent/runner.ts +175 -0
- package/src/adapter/cloudflare/subagent/tools.ts +254 -0
- package/src/adapter/cloudflare/universal-agent/hooks.ts +35 -0
- package/src/adapter/cloudflare/universal-agent/preparation.ts +277 -0
- package/src/adapter/cloudflare/universal-agent/tools.ts +80 -0
- package/src/adapter/cloudflare/workspace/git-fs.ts +178 -0
- package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
- package/src/adapter/cloudflare/workspace/version-control.ts +374 -0
- package/src/agent-tool-runtime.ts +152 -0
- package/src/db/agent-tool.repo.ts +27 -0
- package/src/db/index.ts +33 -0
- package/src/db/interaction.repo.ts +185 -0
- package/src/db/schema.ts +25 -1
- package/src/db/submission.repo.ts +63 -1
- package/src/index.ts +61 -27
- package/src/kernel/approval-lifecycle.ts +41 -6
- package/src/kernel/bindings.ts +99 -12
- package/src/kernel/extensions.ts +1 -1
- package/src/kernel/interaction-lifecycle.ts +395 -0
- package/src/kernel/profile.ts +3 -4
- package/src/kernel/public-contracts.ts +2 -0
- package/src/kernel/recoverable-chat-agent.ts +104 -6
- package/src/kernel/runtime-assembly-view.ts +37 -0
- package/src/kernel/runtime-assembly.ts +41 -0
- package/src/kernel/runtime-config.ts +4 -0
- package/src/kernel/runtime-load.ts +191 -0
- package/src/kernel/state.ts +12 -1
- package/src/kernel/submission-lifecycle.ts +33 -2
- package/src/layers/orchestration/temporary-agent/core.ts +12 -1
- package/src/layers/orchestration/temporary-agent/runner.ts +1 -67
- package/src/lib/mcp.ts +7 -3
- package/src/lib/prompt.ts +1 -1
- package/src/lib/telemetry-dev.ts +7 -4
- package/src/pi/assembly/context.ts +4 -6
- package/src/pi/assembly/extensions.ts +11 -22
- package/src/pi/assembly/snapshot.ts +17 -9
- package/src/pi/message/contract.ts +7 -0
- package/src/pi/message/conversion.ts +9 -1
- package/src/pi/runtime-adapter/assembly.ts +42 -97
- package/src/pi/runtime-adapter/execution.ts +216 -21
- package/src/pi/runtime-adapter/index.ts +24 -8
- package/src/pi/runtime-adapter/models.ts +382 -35
- package/src/pi/runtime-adapter/recovery.ts +188 -1
- package/src/pi/runtime-adapter/transcript.ts +61 -3
- package/src/pi/tool/ai-adapter.ts +58 -1
- package/src/pi/tool/base.ts +190 -12
- package/src/pi/tool/compiler.ts +39 -33
- package/src/pi/tool/core-host.ts +28 -30
- package/src/pi/tool/core.ts +37 -124
- package/src/pi/tool/gateway.ts +54 -0
- package/src/pi/tool/index.ts +2 -0
- package/src/pi/tool/mcp.ts +98 -70
- package/src/pi/tool/schedule.ts +86 -20
- package/src/pi/tool/skill.ts +126 -420
- package/src/pi/tool/subagent.ts +14 -2
- package/src/pi/tool/web-fetch.ts +281 -0
- package/src/pi/tool/web-search/api.ts +34 -18
- package/src/pi/tool/web-search/web-search.ts +0 -1
- package/src/pi/tool/workspace-revision.ts +64 -0
- package/src/pi/tool/workspace-sandbox.ts +105 -263
- package/src/pi/turn/index.ts +20 -0
- package/src/pi/turn/interaction.ts +181 -0
- package/src/pi/turn/tool-recovery.ts +244 -1
- package/src/runtime-agent-context.ts +112 -0
- package/src/runtime-agent.ts +568 -321
- package/src/runtime-assembler.ts +797 -0
- package/src/runtime-definition.ts +175 -0
- package/src/runtime.ts +840 -208
- package/src/tool-registry.ts +143 -0
- package/src/workspace-versioning.ts +46 -0
- package/src/plugins.ts +0 -1033
package/src/lib/prompt.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const BEHAVIOR =
|
|
|
46
46
|
export const PLANNING =
|
|
47
47
|
"Planning: For work with 3 or more distinct steps, or any non-trivial / multi-file change, call " +
|
|
48
48
|
"update_plan with the complete plan (it fully replaces the previous one) so the user sees live " +
|
|
49
|
-
"progress. Mark a step in_progress before starting it and
|
|
49
|
+
"progress. Mark a step in_progress before starting it and done the moment it's done; keep only " +
|
|
50
50
|
"one step in_progress at a time and don't batch completions. Do not make a plan for a single trivial " +
|
|
51
51
|
"step or a purely conversational reply — just do it.";
|
|
52
52
|
|
package/src/lib/telemetry-dev.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* 观测降级:把 observability 事件打到 console,让 `wrangler dev` / `wrangler tail` 里能实时看到埋点,
|
|
3
|
+
* 并且(生产上开了 `observability.logs` 时)进 Workers Logs 供事后查询。
|
|
4
|
+
* 不装它,事件就 publish 到零订阅 channel = 静默 no-op。
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* 由 onStart 在 env `TELEMETRY_CONSOLE==="1"` 时装载 —— `.dev.vars` 和 `wrangler.jsonc` 的 vars 里都开着。
|
|
7
|
+
* 注意:上游注释宣称"生产上所有 channel 事件自动转发 Tail Worker",那条路径需要 `tail_consumers`,
|
|
8
|
+
* 本仓没有配,所以生产**不能**指望它;这个 console sink 就是生产的唯一消费面(2026-08-06 实测:
|
|
9
|
+
* 未开启前 Observability API 查 universal-agent 24h 事件 count=0)。
|
|
7
10
|
*
|
|
8
11
|
* 用 `agents/observability` 的类型化 `subscribe`(按 channel key 订阅),避免引 `node:diagnostics_channel`
|
|
9
12
|
* 与 `@types/node`(tsconfig 只带 workers-types)。
|
|
@@ -116,7 +116,7 @@ export async function compactPiContext(input: Readonly<{
|
|
|
116
116
|
const settings = DEFAULT_COMPACTION_SETTINGS;
|
|
117
117
|
if (
|
|
118
118
|
!settings.enabled ||
|
|
119
|
-
estimateContextTokens(currentMessages).tokens
|
|
119
|
+
estimateContextTokens(currentMessages).tokens <
|
|
120
120
|
input.compactAfterTokens
|
|
121
121
|
) {
|
|
122
122
|
return {
|
|
@@ -367,7 +367,7 @@ async function readMemory(
|
|
|
367
367
|
return { blocks, degradations };
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
//
|
|
370
|
+
// 作用:把已配置 Skill 的持久化 catalog 整理成提示词。
|
|
371
371
|
// 调用:系统上下文装配在需要告诉模型当前可用 Skill 时调用。
|
|
372
372
|
// 原因:catalog 已在发布和绑定时固定,装配期不得为此访问远端内容来源。
|
|
373
373
|
function renderSkillCatalog(
|
|
@@ -379,8 +379,8 @@ function renderSkillCatalog(
|
|
|
379
379
|
if (bindings.length === 0) return null;
|
|
380
380
|
|
|
381
381
|
return [
|
|
382
|
-
"
|
|
383
|
-
"Use an
|
|
382
|
+
"AVAILABLE SKILLS",
|
|
383
|
+
"Use an available Skill when its description matches. Read its instructions or files only through the Skill tools.",
|
|
384
384
|
"",
|
|
385
385
|
...bindings.map(
|
|
386
386
|
({ name, description }) => `- ${name}: ${description}`,
|
|
@@ -479,7 +479,6 @@ export async function assemblePiSystemContext(input: Readonly<{
|
|
|
479
479
|
) => Promise<string | null>;
|
|
480
480
|
}>): Promise<Readonly<{
|
|
481
481
|
systemPrompt: string;
|
|
482
|
-
compactAfterTokens: number;
|
|
483
482
|
degradations: readonly RuntimeDegradation[];
|
|
484
483
|
}>> {
|
|
485
484
|
const [memory, extensions] = await Promise.all([
|
|
@@ -499,7 +498,6 @@ export async function assemblePiSystemContext(input: Readonly<{
|
|
|
499
498
|
|
|
500
499
|
return Object.freeze({
|
|
501
500
|
systemPrompt: parts.join("\n\n"),
|
|
502
|
-
compactAfterTokens: input.memory.compactAfterTokens,
|
|
503
501
|
degradations: Object.freeze([
|
|
504
502
|
...memory.degradations,
|
|
505
503
|
...extensions.degradations,
|
|
@@ -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
|
|
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
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
*
|
|
511
|
+
* 把当前已配置的 Extension 装配成 Runtime 能力。
|
|
511
512
|
*
|
|
512
513
|
* @remarks
|
|
513
|
-
* Runtime 准备阶段和资源 smoke test
|
|
514
|
+
* Runtime 准备阶段和资源 smoke test 调用;调用方传入已选 Extension 及已确定加载策略的 `load`。
|
|
514
515
|
*
|
|
515
|
-
*
|
|
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,
|
|
@@ -16,13 +17,22 @@ import { assembleSystemPrompt } from "../../lib/prompt";
|
|
|
16
17
|
import { resolvePiModel } from "../runtime-adapter/models";
|
|
17
18
|
import type { PiToolCandidate } from "../tool/compiler";
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 收集完动态 Extension/MCP 候选后,一次性生成最终 Tool Surface。
|
|
22
|
+
*/
|
|
23
|
+
export interface PiToolSurface {
|
|
24
|
+
finalize(
|
|
25
|
+
candidates: readonly PiToolCandidate[],
|
|
26
|
+
): readonly PiToolCandidate[];
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
/** Immutable inputs consumed directly by Pi Agent Core. */
|
|
20
30
|
export interface PiRuntimeAssembly {
|
|
21
31
|
readonly model: Model<Api>;
|
|
22
32
|
readonly thinkingLevel: ThinkingLevel;
|
|
23
33
|
readonly systemPrompt: string;
|
|
24
34
|
readonly messages: readonly AgentMessage[];
|
|
25
|
-
readonly
|
|
35
|
+
readonly toolSurface: PiToolSurface;
|
|
26
36
|
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
27
37
|
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
28
38
|
}
|
|
@@ -34,7 +44,7 @@ interface PiRuntimeAssemblyInput {
|
|
|
34
44
|
>;
|
|
35
45
|
readonly provider: RuntimeProviderPort;
|
|
36
46
|
readonly messages?: readonly AgentMessage[];
|
|
37
|
-
readonly
|
|
47
|
+
readonly toolSurface: PiToolSurface;
|
|
38
48
|
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
39
49
|
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
40
50
|
}
|
|
@@ -70,11 +80,13 @@ function freezeModel(model: Model<Api>): Model<Api> {
|
|
|
70
80
|
|
|
71
81
|
// 作用:把 Runtime 的思考强度名称转成 Pi Agent Core 接受的名称。
|
|
72
82
|
// 调用:创建 Runtime 装配快照时对 profile 中的 `thinking` 调用。
|
|
73
|
-
//
|
|
83
|
+
// 原因:`none` 映射成 `off`;xhigh/max 用 clampReasoning 夹到 API 真正支持的最高档,
|
|
84
|
+
// 避免超出范围的等级在 provider 侧静默失败。
|
|
74
85
|
function toPiThinkingLevel(
|
|
75
86
|
thinking: ThinkingEffort,
|
|
76
87
|
): ThinkingLevel {
|
|
77
|
-
|
|
88
|
+
if (thinking === "none") return "off";
|
|
89
|
+
return clampReasoning(thinking) ?? "off";
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
// 作用:复制并冻结一个 Extension 配置的 manifest 和权限集合。
|
|
@@ -172,15 +184,11 @@ export function createPiRuntimeAssembly(
|
|
|
172
184
|
input: PiRuntimeAssemblyInput,
|
|
173
185
|
): PiRuntimeAssembly {
|
|
174
186
|
const messages = [...(input.messages ?? [])];
|
|
175
|
-
const toolCandidates = input.toolCandidates.map(
|
|
176
|
-
(candidate) => Object.freeze({ ...candidate }),
|
|
177
|
-
);
|
|
178
187
|
const mcpServers = input.mcpServers.map(
|
|
179
188
|
(server) => Object.freeze({ ...server }),
|
|
180
189
|
);
|
|
181
190
|
const extensions = input.extensions.map(freezeExtension);
|
|
182
191
|
Object.freeze(messages);
|
|
183
|
-
Object.freeze(toolCandidates);
|
|
184
192
|
Object.freeze(mcpServers);
|
|
185
193
|
Object.freeze(extensions);
|
|
186
194
|
|
|
@@ -193,7 +201,7 @@ export function createPiRuntimeAssembly(
|
|
|
193
201
|
input.profile.systemPrompt,
|
|
194
202
|
),
|
|
195
203
|
messages,
|
|
196
|
-
|
|
204
|
+
toolSurface: input.toolSurface,
|
|
197
205
|
mcpServers,
|
|
198
206
|
extensions,
|
|
199
207
|
});
|
|
@@ -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 "../../
|
|
12
|
+
import type { RuntimeSnapshot } from "../../runtime-assembler";
|
|
12
13
|
import type { PiRuntimeAssembly } from "../assembly";
|
|
13
14
|
import {
|
|
14
15
|
assemblePiSystemContext,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
type PiLoadedExtension,
|
|
20
21
|
} from "../assembly";
|
|
21
22
|
import {
|
|
22
|
-
|
|
23
|
+
createPiGatewayToolCandidates,
|
|
23
24
|
createPiMcpToolCandidates,
|
|
24
25
|
listExtensionsPiToolCandidate,
|
|
25
26
|
type PiMcpHost,
|
|
@@ -41,7 +42,7 @@ import {
|
|
|
41
42
|
interface PiAssemblySnapshot {
|
|
42
43
|
readonly pi: Pick<
|
|
43
44
|
PiRuntimeAssembly,
|
|
44
|
-
"extensions" | "
|
|
45
|
+
"extensions" | "toolSurface"
|
|
45
46
|
>;
|
|
46
47
|
readonly profile: Pick<
|
|
47
48
|
RuntimeProfile,
|
|
@@ -59,6 +60,7 @@ interface PiAssemblySnapshot {
|
|
|
59
60
|
interface PreparePiAssemblyOptions {
|
|
60
61
|
readonly snapshot: PiAssemblySnapshot;
|
|
61
62
|
readonly mcpHost: PiMcpHost;
|
|
63
|
+
readonly gatewaySession?: RuntimeGatewaySession;
|
|
62
64
|
// 为一个扩展生成带权限参数的 Host Fetcher。
|
|
63
65
|
// loadPiExtension 在扩展需要 Host 能力时调用它。
|
|
64
66
|
// Worker 掌握真实绑定,装配层只保存这个窄函数,不能自行扩大权限。
|
|
@@ -75,10 +77,7 @@ interface PreparedPiAssembly {
|
|
|
75
77
|
readonly loaded: readonly PiLoadedExtension[];
|
|
76
78
|
readonly context: readonly PiExtensionContextContribution[];
|
|
77
79
|
readonly degradations: PiExtensionAssembly["degradations"];
|
|
78
|
-
|
|
79
|
-
// preparePiAssembly 用它预检,preparePiRuntime 再读取并冻结最终列表。
|
|
80
|
-
// MCP 就绪状态由 Host 提供,因此这里不能只拼接初始化时的静态数组。
|
|
81
|
-
toolCandidates(): PiToolCandidate[];
|
|
80
|
+
readonly candidates: readonly PiToolCandidate[];
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
/**
|
|
@@ -94,6 +93,8 @@ interface PreparedPiAssembly {
|
|
|
94
93
|
export interface PreparePiRuntimeOptions {
|
|
95
94
|
readonly snapshot: RuntimeSnapshot;
|
|
96
95
|
readonly mcpHost: PiMcpHost;
|
|
96
|
+
readonly gatewaySession?: RuntimeGatewaySession;
|
|
97
|
+
readonly additionalDegradations?: readonly RuntimeDegradation[];
|
|
97
98
|
/**
|
|
98
99
|
* 为一个扩展创建受限的 Host 绑定。
|
|
99
100
|
*
|
|
@@ -119,6 +120,7 @@ export interface PreparePiRuntimeOptions {
|
|
|
119
120
|
export interface PreparedPiRuntime {
|
|
120
121
|
readonly revisionDescriptor: string;
|
|
121
122
|
readonly degradations: readonly RuntimeDegradation[];
|
|
123
|
+
readonly extensions: readonly { readonly name: string; readonly version: string }[];
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
/**
|
|
@@ -164,7 +166,7 @@ export interface PinnedPiRuntime {
|
|
|
164
166
|
* @remarks
|
|
165
167
|
* `PreparedPiTurnAdapter` 在重建 Turn 前读取这个 JSON 结构。
|
|
166
168
|
*
|
|
167
|
-
*
|
|
169
|
+
* 基础版本作为准入时元数据保留,创建 Turn 时不与当前 Runtime 比较。
|
|
168
170
|
*/
|
|
169
171
|
export interface PinnedPiRuntimeDescriptor {
|
|
170
172
|
readonly version: 1;
|
|
@@ -204,6 +206,8 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
|
|
|
204
206
|
"browser_markdown",
|
|
205
207
|
"browser_scrape",
|
|
206
208
|
"bind_resource",
|
|
209
|
+
"delete",
|
|
210
|
+
"edit",
|
|
207
211
|
"find",
|
|
208
212
|
"get_time",
|
|
209
213
|
"grep",
|
|
@@ -215,6 +219,7 @@ const IDEMPOTENT_TOOL_NAMES = new Set([
|
|
|
215
219
|
"read_skill_resource",
|
|
216
220
|
"sandbox_process_logs",
|
|
217
221
|
"web_search",
|
|
222
|
+
"write",
|
|
218
223
|
"unbind_resource",
|
|
219
224
|
]);
|
|
220
225
|
|
|
@@ -243,10 +248,12 @@ function describeTools(candidates: readonly PiToolCandidate[]) {
|
|
|
243
248
|
name: candidate.tool.name,
|
|
244
249
|
owner: candidate.owner,
|
|
245
250
|
label: candidate.tool.label,
|
|
246
|
-
description:
|
|
247
|
-
candidate.tool.description ?? candidate.tool.label,
|
|
251
|
+
description: candidate.tool.description ?? candidate.tool.label,
|
|
248
252
|
parameters: candidate.tool.parameters,
|
|
249
253
|
requiredExecutionLevel: candidate.requiredExecutionLevel,
|
|
254
|
+
...(candidate.alwaysRequiresApproval
|
|
255
|
+
? { alwaysRequiresApproval: true }
|
|
256
|
+
: {}),
|
|
250
257
|
...(candidate.source ? { source: candidate.source } : {}),
|
|
251
258
|
retry: piToolRetryPolicy(candidate),
|
|
252
259
|
}))
|
|
@@ -255,7 +262,7 @@ function describeTools(candidates: readonly PiToolCandidate[]) {
|
|
|
255
262
|
|
|
256
263
|
// 把本模块认定会影响版本的准备结果序列化出来。
|
|
257
264
|
// preparePiRuntime 创建一次,Runtime 接受快照前再对结果求哈希。
|
|
258
|
-
//
|
|
265
|
+
// 集合排序保证等价输入稳定;增删字段会改变版本哈希。
|
|
259
266
|
function describeRuntime(
|
|
260
267
|
snapshot: RuntimeSnapshot,
|
|
261
268
|
candidates: readonly PiToolCandidate[],
|
|
@@ -302,6 +309,7 @@ class PreparedRuntime implements PreparedPiRuntime {
|
|
|
302
309
|
constructor(
|
|
303
310
|
readonly revisionDescriptor: string,
|
|
304
311
|
readonly degradations: readonly RuntimeDegradation[],
|
|
312
|
+
readonly extensions: readonly { readonly name: string; readonly version: string }[],
|
|
305
313
|
private readonly owner: object,
|
|
306
314
|
private readonly state: PreparedPiRuntimeState,
|
|
307
315
|
) {}
|
|
@@ -338,18 +346,17 @@ export function readPreparedPiRuntime(
|
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
/**
|
|
341
|
-
*
|
|
349
|
+
* 读取已存的固定描述。
|
|
342
350
|
*
|
|
343
351
|
* @remarks
|
|
344
352
|
* `PreparedPiTurnAdapter` 在新建或恢复 Pi Turn 前调用它。
|
|
345
353
|
*
|
|
346
|
-
*
|
|
354
|
+
* Prepared Runtime 提供当前执行能力,descriptor 仅保留 Submission 固定的 Turn 字段。
|
|
347
355
|
*/
|
|
348
356
|
export function readPinnedPiRuntime(
|
|
349
357
|
prepared: PreparedPiRuntime,
|
|
350
358
|
owner: object,
|
|
351
359
|
descriptorBody: string,
|
|
352
|
-
baseRevision: string,
|
|
353
360
|
): {
|
|
354
361
|
readonly state: PreparedPiRuntimeState;
|
|
355
362
|
readonly descriptor: PinnedPiRuntimeDescriptor;
|
|
@@ -365,24 +372,11 @@ export function readPinnedPiRuntime(
|
|
|
365
372
|
throw new Error("Pinned Runtime assembly descriptor is invalid");
|
|
366
373
|
}
|
|
367
374
|
const descriptor = value as PinnedPiRuntimeDescriptor;
|
|
368
|
-
const expected = JSON.parse(prepared.revisionDescriptor) as Record<
|
|
369
|
-
string,
|
|
370
|
-
unknown
|
|
371
|
-
>;
|
|
372
|
-
const actual = descriptor as unknown as Record<string, unknown>;
|
|
373
|
-
const incompatible = Object.keys(expected).some((key) =>
|
|
374
|
-
key !== "systemPrompt" &&
|
|
375
|
-
JSON.stringify(actual[key]) !== JSON.stringify(expected[key])
|
|
376
|
-
);
|
|
377
375
|
if (
|
|
378
376
|
descriptor.version !== 1 ||
|
|
379
|
-
descriptor.
|
|
380
|
-
typeof descriptor.systemPrompt !== "string" ||
|
|
381
|
-
incompatible
|
|
377
|
+
typeof descriptor.systemPrompt !== "string"
|
|
382
378
|
) {
|
|
383
|
-
throw new Error(
|
|
384
|
-
"Pinned Runtime revision is unavailable for Pi recovery",
|
|
385
|
-
);
|
|
379
|
+
throw new Error("Pinned Runtime assembly descriptor is invalid");
|
|
386
380
|
}
|
|
387
381
|
return { state, descriptor };
|
|
388
382
|
}
|
|
@@ -391,70 +385,15 @@ export function readPinnedPiRuntime(
|
|
|
391
385
|
|
|
392
386
|
// #region 扩展与工具装配
|
|
393
387
|
|
|
394
|
-
|
|
395
|
-
// 把准备快照、已加载扩展和 MCP Host 放在一个装配对象里。
|
|
396
|
-
// preparePiAssembly 在扩展加载完成后构造它。
|
|
397
|
-
// 这里保留源对象而不复制列表,让每个访问器都读取同一份结果。
|
|
398
|
-
constructor(
|
|
399
|
-
private readonly snapshot: PiAssemblySnapshot,
|
|
400
|
-
private readonly extensions: PiExtensionAssembly,
|
|
401
|
-
private readonly mcpHost: PiMcpHost,
|
|
402
|
-
) {}
|
|
403
|
-
|
|
404
|
-
// 返回成功加载的扩展。
|
|
405
|
-
// preparePiRuntime 在生成版本描述时读取这个列表。
|
|
406
|
-
// 直接代理扩展装配,避免维护可能漂移的第二份列表。
|
|
407
|
-
get loaded(): readonly PiLoadedExtension[] {
|
|
408
|
-
return this.extensions.loaded;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// 返回已加载扩展声明的上下文贡献。
|
|
412
|
-
// pinPiRuntime 在提交准入时把它们交给系统上下文装配器。
|
|
413
|
-
// 上下文值在固定阶段才读取,所以不能提前写死在准备阶段的提示词中。
|
|
414
|
-
get context(): readonly PiExtensionContextContribution[] {
|
|
415
|
-
return this.extensions.context;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// 返回准备阶段被降级处理、而不是继续抛出的扩展失败。
|
|
419
|
-
// preparePiRuntime 把它们与快照降级合并,用于报告 Runtime 加载结果。
|
|
420
|
-
// 直接代理可保留扩展装配器生成的原始降级记录。
|
|
421
|
-
get degradations(): PiExtensionAssembly["degradations"] {
|
|
422
|
-
return this.extensions.degradations;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// 汇总内置、扩展列表、扩展和当前已就绪的 MCP 工具。
|
|
426
|
-
// 准备流程先调用它做校验,再冻结一份列表供后续 Turn 使用。
|
|
427
|
-
// 每次重新生成是因为 MCP 候选来自 Host 当前的连接状态。
|
|
428
|
-
toolCandidates(): PiToolCandidate[] {
|
|
429
|
-
return [
|
|
430
|
-
...this.snapshot.pi.toolCandidates,
|
|
431
|
-
...this.extensions.candidates,
|
|
432
|
-
listExtensionsPiToolCandidate(this.extensions.loaded),
|
|
433
|
-
...createPiMcpToolCandidates(
|
|
434
|
-
this.mcpHost,
|
|
435
|
-
this.snapshot.profile.mcpServers,
|
|
436
|
-
),
|
|
437
|
-
];
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// 加载已启用扩展,并确认汇总后的工具可以正常编译。
|
|
388
|
+
// 加载已启用扩展,并把 Extension/MCP 候选交给唯一 Tool Surface 最终化。
|
|
442
389
|
// preparePiRuntime 为每份待配置的 Runtime 快照调用它一次。
|
|
443
390
|
// 工作区能力在共享加载边界校验,单个扩展加载失败则保留为降级结果。
|
|
444
391
|
async function preparePiAssembly(
|
|
445
392
|
options: PreparePiAssemblyOptions,
|
|
446
393
|
): Promise<PreparedPiAssembly> {
|
|
447
394
|
const { snapshot } = options;
|
|
448
|
-
const extensionNames = new Set(
|
|
449
|
-
snapshot.pi.extensions.map((extension) =>
|
|
450
|
-
extension.manifest.name.trim()
|
|
451
|
-
),
|
|
452
|
-
);
|
|
453
395
|
const extensions = await assemblePiExtensions({
|
|
454
396
|
extensions: snapshot.pi.extensions,
|
|
455
|
-
published: extensionNames,
|
|
456
|
-
enabled: extensionNames,
|
|
457
|
-
authorized: extensionNames,
|
|
458
397
|
load: async (extension) => {
|
|
459
398
|
const workspacePermission =
|
|
460
399
|
extension.manifest.permissions?.workspace ?? "none";
|
|
@@ -469,18 +408,22 @@ async function preparePiAssembly(
|
|
|
469
408
|
});
|
|
470
409
|
},
|
|
471
410
|
});
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
extensions,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
411
|
+
return Object.freeze({
|
|
412
|
+
loaded: extensions.loaded,
|
|
413
|
+
context: extensions.context,
|
|
414
|
+
degradations: extensions.degradations,
|
|
415
|
+
candidates: snapshot.pi.toolSurface.finalize([
|
|
416
|
+
...extensions.candidates,
|
|
417
|
+
listExtensionsPiToolCandidate(extensions.loaded),
|
|
418
|
+
...(options.gatewaySession
|
|
419
|
+
? createPiGatewayToolCandidates(options.gatewaySession)
|
|
420
|
+
: []),
|
|
421
|
+
...createPiMcpToolCandidates(
|
|
422
|
+
options.mcpHost,
|
|
423
|
+
snapshot.profile.mcpServers,
|
|
424
|
+
),
|
|
425
|
+
]),
|
|
482
426
|
});
|
|
483
|
-
return prepared;
|
|
484
427
|
}
|
|
485
428
|
|
|
486
429
|
// #endregion
|
|
@@ -500,13 +443,15 @@ export async function preparePiRuntime(
|
|
|
500
443
|
owner: object,
|
|
501
444
|
): Promise<PreparedPiRuntime> {
|
|
502
445
|
const assembly = await preparePiAssembly(options);
|
|
503
|
-
const candidates = assembly.
|
|
446
|
+
const candidates = assembly.candidates;
|
|
504
447
|
return Object.freeze(new PreparedRuntime(
|
|
505
448
|
describeRuntime(options.snapshot, candidates, assembly.loaded),
|
|
506
449
|
Object.freeze([
|
|
507
450
|
...options.snapshot.degradations,
|
|
451
|
+
...(options.additionalDegradations ?? []),
|
|
508
452
|
...assembly.degradations,
|
|
509
453
|
]),
|
|
454
|
+
Object.freeze(assembly.loaded.map(({ name, version }) => ({ name, version }))),
|
|
510
455
|
owner,
|
|
511
456
|
Object.freeze({
|
|
512
457
|
snapshot: options.snapshot,
|