@springbrand/agent-runtime 0.1.3-alpha.4 → 0.1.3-alpha.6
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 +11 -3
- package/src/adapter/cloudflare/index.ts +55 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +89 -0
- package/src/adapter/cloudflare/sandbox/adapter.ts +1509 -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 +273 -0
- package/src/adapter/cloudflare/universal-agent/tools.ts +74 -0
- package/src/adapter/cloudflare/workspace/publisher.ts +31 -0
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +376 -0
- package/src/agent-tool-runtime.ts +152 -0
- package/src/index.ts +49 -7
- package/src/kernel/bindings.ts +6 -6
- package/src/kernel/recoverable-chat-agent.ts +12 -0
- package/src/kernel/runtime-load.ts +89 -0
- package/src/layers/orchestration/temporary-agent/core.ts +12 -1
- package/src/layers/orchestration/temporary-agent/runner.ts +1 -2
- package/src/lib/mcp.ts +7 -3
- package/src/pi/assembly/context.ts +3 -3
- package/src/pi/assembly/extensions.ts +11 -22
- package/src/pi/assembly/snapshot.ts +1 -1
- package/src/pi/message/contract.ts +7 -0
- package/src/pi/message/conversion.ts +9 -1
- package/src/pi/runtime-adapter/assembly.ts +4 -10
- package/src/pi/runtime-adapter/index.ts +6 -2
- package/src/pi/tool/base.ts +17 -2
- package/src/pi/tool/compiler.ts +0 -1
- package/src/pi/tool/core.ts +13 -3
- package/src/pi/tool/mcp.ts +3 -4
- package/src/pi/tool/schedule.ts +11 -1
- package/src/pi/tool/skill.ts +55 -41
- package/src/pi/tool/subagent.ts +14 -2
- package/src/pi/tool/web-fetch.ts +0 -1
- package/src/pi/tool/web-search/web-search.ts +0 -1
- package/src/pi/tool/workspace-sandbox.ts +15 -7
- package/src/runtime-agent-context.ts +112 -0
- package/src/runtime-agent.ts +442 -328
- package/src/runtime-assembler.ts +255 -103
- package/src/runtime-definition.ts +173 -0
- package/src/runtime.ts +185 -25
- package/src/tool-registry.ts +143 -0
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
import type { RuntimeLoadPhase, RuntimeLoadState } from "./state";
|
|
2
2
|
|
|
3
|
+
/** Hard ceiling for one Host-owned Runtime definition phase. */
|
|
4
|
+
export const RUNTIME_LOAD_TIMEOUT_MS = 180_000;
|
|
5
|
+
|
|
6
|
+
/** Hard ceiling for one optional external capability load. */
|
|
7
|
+
export const RUNTIME_CAPABILITY_LOAD_TIMEOUT_MS = 60_000;
|
|
8
|
+
|
|
9
|
+
const reportedFailures = new WeakSet<object>();
|
|
10
|
+
|
|
11
|
+
function errorText(error: unknown): string {
|
|
12
|
+
return error instanceof Error ? error.message : String(error);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Emit one stable structured record for a Runtime load failure. */
|
|
16
|
+
export function logRuntimeLoadFailure(
|
|
17
|
+
step: string,
|
|
18
|
+
error: unknown,
|
|
19
|
+
details: {
|
|
20
|
+
reason?: "error" | "timeout";
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
durationMs?: number;
|
|
23
|
+
} = {},
|
|
24
|
+
): void {
|
|
25
|
+
if (typeof error === "object" && error !== null) {
|
|
26
|
+
if (reportedFailures.has(error)) return;
|
|
27
|
+
reportedFailures.add(error);
|
|
28
|
+
}
|
|
29
|
+
console.error(
|
|
30
|
+
"[runtime-load:failed]",
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
step,
|
|
33
|
+
reason: details.reason ?? "error",
|
|
34
|
+
...(details.timeoutMs !== undefined
|
|
35
|
+
? { timeoutMs: details.timeoutMs }
|
|
36
|
+
: {}),
|
|
37
|
+
...(details.durationMs !== undefined
|
|
38
|
+
? { durationMs: details.durationMs }
|
|
39
|
+
: {}),
|
|
40
|
+
error: errorText(error),
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Stop waiting for one Runtime load boundary and report any failure once. */
|
|
46
|
+
export async function withRuntimeLoadTimeout<T>(
|
|
47
|
+
step: string,
|
|
48
|
+
load: () => T | PromiseLike<T>,
|
|
49
|
+
options: {
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
onLateResult?: (value: T) => void | Promise<void>;
|
|
52
|
+
} = {},
|
|
53
|
+
): Promise<T> {
|
|
54
|
+
const timeoutMs = options.timeoutMs ?? RUNTIME_CAPABILITY_LOAD_TIMEOUT_MS;
|
|
55
|
+
const startedAt = Date.now();
|
|
56
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
57
|
+
let timedOut = false;
|
|
58
|
+
const operation = Promise.resolve().then(load);
|
|
59
|
+
|
|
60
|
+
if (options.onLateResult) {
|
|
61
|
+
void operation.then(async (value) => {
|
|
62
|
+
if (timedOut) await options.onLateResult!(value);
|
|
63
|
+
}, () => undefined).catch((error) => {
|
|
64
|
+
logRuntimeLoadFailure(`${step}:late-cleanup`, error);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
return await Promise.race([
|
|
70
|
+
operation,
|
|
71
|
+
new Promise<never>((_, reject) => {
|
|
72
|
+
timeout = setTimeout(() => {
|
|
73
|
+
timedOut = true;
|
|
74
|
+
reject(new Error(
|
|
75
|
+
`Runtime load step "${step}" timed out after ${timeoutMs}ms`,
|
|
76
|
+
));
|
|
77
|
+
}, timeoutMs);
|
|
78
|
+
}),
|
|
79
|
+
]);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logRuntimeLoadFailure(step, error, {
|
|
82
|
+
reason: timedOut ? "timeout" : "error",
|
|
83
|
+
timeoutMs,
|
|
84
|
+
durationMs: Date.now() - startedAt,
|
|
85
|
+
});
|
|
86
|
+
throw error;
|
|
87
|
+
} finally {
|
|
88
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
3
92
|
/**
|
|
4
93
|
* 装载状态机与宿主之间的全部接触面。
|
|
5
94
|
*
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
import type { ExecutionLevel } from "../../../lib/execution-level";
|
|
2
|
+
|
|
3
|
+
export const TEMPORARY_AGENT_LAUNCH_KEY =
|
|
4
|
+
"universal-agent:temporary-agent-launch";
|
|
5
|
+
|
|
1
6
|
export interface TemporaryAgentRequest {
|
|
2
7
|
subagentName: string;
|
|
3
8
|
instructions: string;
|
|
4
9
|
task: string;
|
|
5
10
|
}
|
|
6
11
|
|
|
12
|
+
export interface TemporaryAgentLaunch<Config = unknown>
|
|
13
|
+
extends TemporaryAgentRequest {
|
|
14
|
+
runtimeKey: string;
|
|
15
|
+
config: Config;
|
|
16
|
+
executionLevel: ExecutionLevel;
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
export interface TemporaryAgentRunContext {
|
|
8
20
|
signal: AbortSignal;
|
|
9
21
|
requestId: string;
|
|
@@ -149,4 +161,3 @@ export class TemporaryAgentCoordinator {
|
|
|
149
161
|
});
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
|
-
import type { ExecutionLevel } from "../../../lib/execution-level";
|
|
@@ -10,7 +10,6 @@ const BLOCKED_TOOLS = new Set([
|
|
|
10
10
|
"resume_schedule",
|
|
11
11
|
"change_schedule_agent",
|
|
12
12
|
"cancel_schedule",
|
|
13
|
-
"execute",
|
|
14
13
|
"set_context",
|
|
15
14
|
"load_context",
|
|
16
15
|
"search_context",
|
|
@@ -25,7 +24,7 @@ const BLOCKED_TOOLS = new Set([
|
|
|
25
24
|
* @remarks
|
|
26
25
|
* 宿主在装配临时 Agent 的平台、Workspace、Sandbox 和其他 Tool 时调用;调用方可再加名字或前缀黑名单。
|
|
27
26
|
*
|
|
28
|
-
*
|
|
27
|
+
* 固定黑名单排除再次委派、调度和主 Session 上下文,以保持单层临时 Agent 和已确认的继承范围。
|
|
29
28
|
*
|
|
30
29
|
* Agent、Session、Workspace 和 Tool 的术语见 `src/index.ts`。
|
|
31
30
|
*/
|
package/src/lib/mcp.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RetryOptions } from "agents";
|
|
2
|
+
import { withRuntimeLoadTimeout } from "../kernel/runtime-load";
|
|
2
3
|
|
|
3
4
|
const MCP_RETRY: RetryOptions = {
|
|
4
5
|
maxAttempts: 3,
|
|
@@ -63,9 +64,12 @@ export async function connectConfiguredMcpServers(
|
|
|
63
64
|
const servers = [...pending.values()];
|
|
64
65
|
const settled = await Promise.allSettled(
|
|
65
66
|
servers.map((server) =>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
withRuntimeLoadTimeout(
|
|
68
|
+
`mcp:${server.name}:connect`,
|
|
69
|
+
() => host.addMcpServer(server.name, server.url, {
|
|
70
|
+
retry: MCP_RETRY,
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
69
73
|
),
|
|
70
74
|
);
|
|
71
75
|
const failed = settled.flatMap((result, index) =>
|
|
@@ -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}`,
|
|
@@ -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,
|
|
@@ -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
|
}
|
|
@@ -248,10 +248,12 @@ function describeTools(candidates: readonly PiToolCandidate[]) {
|
|
|
248
248
|
name: candidate.tool.name,
|
|
249
249
|
owner: candidate.owner,
|
|
250
250
|
label: candidate.tool.label,
|
|
251
|
-
description:
|
|
252
|
-
candidate.tool.description ?? candidate.tool.label,
|
|
251
|
+
description: candidate.tool.description ?? candidate.tool.label,
|
|
253
252
|
parameters: candidate.tool.parameters,
|
|
254
253
|
requiredExecutionLevel: candidate.requiredExecutionLevel,
|
|
254
|
+
...(candidate.alwaysRequiresApproval
|
|
255
|
+
? { alwaysRequiresApproval: true }
|
|
256
|
+
: {}),
|
|
255
257
|
...(candidate.source ? { source: candidate.source } : {}),
|
|
256
258
|
retry: piToolRetryPolicy(candidate),
|
|
257
259
|
}))
|
|
@@ -390,16 +392,8 @@ async function preparePiAssembly(
|
|
|
390
392
|
options: PreparePiAssemblyOptions,
|
|
391
393
|
): Promise<PreparedPiAssembly> {
|
|
392
394
|
const { snapshot } = options;
|
|
393
|
-
const extensionNames = new Set(
|
|
394
|
-
snapshot.pi.extensions.map((extension) =>
|
|
395
|
-
extension.manifest.name.trim()
|
|
396
|
-
),
|
|
397
|
-
);
|
|
398
395
|
const extensions = await assemblePiExtensions({
|
|
399
396
|
extensions: snapshot.pi.extensions,
|
|
400
|
-
published: extensionNames,
|
|
401
|
-
enabled: extensionNames,
|
|
402
|
-
authorized: extensionNames,
|
|
403
397
|
load: async (extension) => {
|
|
404
398
|
const workspacePermission =
|
|
405
399
|
extension.manifest.permissions?.workspace ?? "none";
|
|
@@ -107,8 +107,11 @@ export class PiRuntimeAdapter {
|
|
|
107
107
|
* 实现理由:转换只生成 Pi UserMessage 的 content 和 timestamp,原始浏览器视图由 transcript sidecar 另行保存。
|
|
108
108
|
* 不要把 UI id、metadata 或原始 part 结构并入 canonical message。
|
|
109
109
|
*/
|
|
110
|
-
normalizeUserInput(
|
|
111
|
-
|
|
110
|
+
normalizeUserInput(
|
|
111
|
+
input: UIMessage & { role: "user" },
|
|
112
|
+
privateContext?: string,
|
|
113
|
+
) {
|
|
114
|
+
return uiUserMessageToPi(input, privateContext);
|
|
112
115
|
}
|
|
113
116
|
|
|
114
117
|
resolveApiKey(
|
|
@@ -209,6 +212,7 @@ export class PiRuntimeAdapter {
|
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
export type {
|
|
215
|
+
RequestedCapability,
|
|
212
216
|
UIChatRequestBody,
|
|
213
217
|
} from "../message";
|
|
214
218
|
export type { PiToolApproval } from "../turn";
|
package/src/pi/tool/base.ts
CHANGED
|
@@ -8,6 +8,10 @@ import type { RuntimeMemoryPort } from "../../kernel/bindings";
|
|
|
8
8
|
import type { RuntimeMemoryProfile } from "../../kernel/profile";
|
|
9
9
|
import { serializeOutput } from "../../lib/artifacts";
|
|
10
10
|
import type { PiToolCandidate } from "./compiler";
|
|
11
|
+
import {
|
|
12
|
+
toolRegistryFromPiCandidates,
|
|
13
|
+
type ToolRegistry,
|
|
14
|
+
} from "../../tool-registry";
|
|
11
15
|
import { webSearchPiToolCandidate } from "./web-search";
|
|
12
16
|
import type { WebSearch } from "./web-search/api";
|
|
13
17
|
|
|
@@ -140,7 +144,6 @@ function result<T>(details: T): AgentToolResult<T> {
|
|
|
140
144
|
function candidate<T extends TSchema>(tool: AgentTool<T>): PiToolCandidate {
|
|
141
145
|
return {
|
|
142
146
|
owner: "runtime-base",
|
|
143
|
-
authorized: true,
|
|
144
147
|
requiredExecutionLevel: "safe",
|
|
145
148
|
source: "action",
|
|
146
149
|
tool,
|
|
@@ -265,9 +268,21 @@ export function memoryPiToolCandidate(
|
|
|
265
268
|
};
|
|
266
269
|
return {
|
|
267
270
|
owner: "runtime-memory",
|
|
268
|
-
authorized: true,
|
|
269
271
|
requiredExecutionLevel: "safe",
|
|
270
272
|
source: "action",
|
|
271
273
|
tool,
|
|
272
274
|
};
|
|
273
275
|
}
|
|
276
|
+
|
|
277
|
+
/** 从 Memory Port 生成 `set_context` Tool。 */
|
|
278
|
+
export function createMemoryTools(
|
|
279
|
+
memory: RuntimeMemoryPort,
|
|
280
|
+
profile: Pick<
|
|
281
|
+
RuntimeMemoryProfile,
|
|
282
|
+
"memoryTokens" | "preferencesTokens"
|
|
283
|
+
>,
|
|
284
|
+
): ToolRegistry {
|
|
285
|
+
return toolRegistryFromPiCandidates([
|
|
286
|
+
memoryPiToolCandidate(memory, profile),
|
|
287
|
+
]);
|
|
288
|
+
}
|
package/src/pi/tool/compiler.ts
CHANGED
|
@@ -36,7 +36,6 @@ export interface PiToolInteractionSpec {
|
|
|
36
36
|
/** 描述一个尚未进入最终 Tool Surface 和结算包装的 Pi 工具。 */
|
|
37
37
|
export interface PiToolCandidate {
|
|
38
38
|
readonly owner: string;
|
|
39
|
-
readonly authorized: boolean;
|
|
40
39
|
readonly tool: AgentTool<any, any>;
|
|
41
40
|
/** Conservative maximum used in the stable Runtime descriptor. */
|
|
42
41
|
readonly requiredExecutionLevel: ExecutionLevel;
|
package/src/pi/tool/core.ts
CHANGED
|
@@ -12,6 +12,10 @@ import { serializeOutput } from "../../lib/artifacts";
|
|
|
12
12
|
import type { PiLoadedExtension } from "../assembly/extensions";
|
|
13
13
|
import { aiToolToPi } from "./ai-adapter";
|
|
14
14
|
import type { PiToolCandidate } from "./compiler";
|
|
15
|
+
import {
|
|
16
|
+
toolRegistryFromPiCandidates,
|
|
17
|
+
type ToolRegistry,
|
|
18
|
+
} from "../../tool-registry";
|
|
15
19
|
|
|
16
20
|
// 本文件沿用 `../../index.ts` 入口定义的 Extension、Port 和 Tool Candidate 术语。
|
|
17
21
|
|
|
@@ -47,7 +51,6 @@ export function browserQuickActionPiToolCandidates(
|
|
|
47
51
|
return Object.entries(createQuickActionTools({ browser })).map(
|
|
48
52
|
([name, tool]) => ({
|
|
49
53
|
owner: "core:browser",
|
|
50
|
-
authorized: true,
|
|
51
54
|
requiredExecutionLevel: "low",
|
|
52
55
|
tool: aiToolToPi(name, tool, {
|
|
53
56
|
label: BROWSER_TOOL_LABELS[name] ?? name,
|
|
@@ -90,7 +93,6 @@ export function listExtensionsPiToolCandidate(
|
|
|
90
93
|
};
|
|
91
94
|
return {
|
|
92
95
|
owner: "core:extensions",
|
|
93
|
-
authorized: true,
|
|
94
96
|
requiredExecutionLevel: "low",
|
|
95
97
|
tool,
|
|
96
98
|
};
|
|
@@ -170,7 +172,6 @@ export function codeExecutionPiToolCandidate(
|
|
|
170
172
|
};
|
|
171
173
|
return {
|
|
172
174
|
owner: "core:codemode",
|
|
173
|
-
authorized: true,
|
|
174
175
|
requiredExecutionLevel: "high",
|
|
175
176
|
source: "codemode",
|
|
176
177
|
summary: "Run JavaScript with network and configured connector access",
|
|
@@ -178,4 +179,13 @@ export function codeExecutionPiToolCandidate(
|
|
|
178
179
|
};
|
|
179
180
|
}
|
|
180
181
|
|
|
182
|
+
/** 从 Codemode Runtime Port 生成 `execute` Tool。 */
|
|
183
|
+
export function createCodeExecutionTool(
|
|
184
|
+
runtime: RuntimeCodeExecutionPort,
|
|
185
|
+
): ToolRegistry {
|
|
186
|
+
return toolRegistryFromPiCandidates([
|
|
187
|
+
codeExecutionPiToolCandidate(runtime),
|
|
188
|
+
]);
|
|
189
|
+
}
|
|
190
|
+
|
|
181
191
|
// #endregion
|
package/src/pi/tool/mcp.ts
CHANGED
|
@@ -263,7 +263,6 @@ export function createPiMcpToolCandidate(
|
|
|
263
263
|
};
|
|
264
264
|
return {
|
|
265
265
|
owner: policy.owner,
|
|
266
|
-
authorized: true,
|
|
267
266
|
tool,
|
|
268
267
|
summary: label,
|
|
269
268
|
requiredExecutionLevel: policy.requiredExecutionLevel,
|
|
@@ -278,15 +277,15 @@ export function createPiMcpToolCandidate(
|
|
|
278
277
|
}
|
|
279
278
|
|
|
280
279
|
/**
|
|
281
|
-
*
|
|
280
|
+
* 把当前已配置且 ready 的 MCP 工具转换成 Pi 候选工具。
|
|
282
281
|
*
|
|
283
282
|
* Tool Surface 在 Runtime 准备时调用它。调用方应传入
|
|
284
283
|
* Agent Host,以及同一 Runtime 快照中的 MCP 配置;未就绪、未配置或重复 URL
|
|
285
284
|
* 的连接不会进入模型工具目录。
|
|
286
285
|
*
|
|
287
286
|
* “候选工具”是进入 Pi 编译和统一治理前的内部描述。连接、凭据和恢复由 Host
|
|
288
|
-
* 与 Agents SDK
|
|
289
|
-
* 远端 Server 提供的 annotations
|
|
287
|
+
* 与 Agents SDK 管理;本函数只做动态连接状态过滤、Schema 转接和公开结果投影。
|
|
288
|
+
* 远端 Server 提供的 annotations 只是提示,不是本地审批边界,所以所有动态
|
|
290
289
|
* MCP 工具继续标为高风险,不能据此绕过审批。
|
|
291
290
|
*
|
|
292
291
|
* `Host`、`Pi`、`Runtime Snapshot` 与“候选工具”见 `../../index.ts`。
|
package/src/pi/tool/schedule.ts
CHANGED
|
@@ -10,6 +10,10 @@ import type {
|
|
|
10
10
|
import type { ScheduleSpec } from "../../kernel/receipts";
|
|
11
11
|
import { serializeOutput } from "../../lib/artifacts";
|
|
12
12
|
import type { PiToolCandidate } from "./compiler";
|
|
13
|
+
import {
|
|
14
|
+
toolRegistryFromPiCandidates,
|
|
15
|
+
type ToolRegistry,
|
|
16
|
+
} from "../../tool-registry";
|
|
13
17
|
|
|
14
18
|
const scheduleTriggerParameters = Type.Union([
|
|
15
19
|
Type.Object({
|
|
@@ -95,7 +99,6 @@ function candidate<T extends TSchema>(
|
|
|
95
99
|
): PiToolCandidate {
|
|
96
100
|
return {
|
|
97
101
|
owner: options.owner ?? "runtime-base",
|
|
98
|
-
authorized: true,
|
|
99
102
|
requiredExecutionLevel: options.requiredExecutionLevel ?? "safe",
|
|
100
103
|
source: "action",
|
|
101
104
|
tool,
|
|
@@ -240,3 +243,10 @@ export function schedulePiToolCandidates(
|
|
|
240
243
|
),
|
|
241
244
|
];
|
|
242
245
|
}
|
|
246
|
+
|
|
247
|
+
/** 从 {@link RuntimeSchedulePort} 生成定时任务 Tool 集。 */
|
|
248
|
+
export function createScheduleTools(
|
|
249
|
+
schedule: RuntimeSchedulePort,
|
|
250
|
+
): ToolRegistry {
|
|
251
|
+
return toolRegistryFromPiCandidates(schedulePiToolCandidates(schedule));
|
|
252
|
+
}
|
package/src/pi/tool/skill.ts
CHANGED
|
@@ -13,9 +13,10 @@ import type {
|
|
|
13
13
|
import { aiToolToPi } from "./ai-adapter";
|
|
14
14
|
import type { PiToolCandidate } from "./compiler";
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/** A configured Skill source and its script capabilities. */
|
|
17
17
|
export interface PiSkillBinding {
|
|
18
18
|
readonly name: string;
|
|
19
|
+
readonly description: string;
|
|
19
20
|
readonly source: SkillSource;
|
|
20
21
|
readonly script?: RuntimeSkillScriptPolicy;
|
|
21
22
|
}
|
|
@@ -46,42 +47,23 @@ function scriptTools(
|
|
|
46
47
|
})) as ToolSet;
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
function authorizedSource(binding: PiSkillBinding): SkillSource {
|
|
52
|
-
const { name, source } = binding;
|
|
50
|
+
function catalogSkillSource(binding: PiSkillBinding): SkillSource {
|
|
51
|
+
const source = binding.source;
|
|
53
52
|
return {
|
|
54
|
-
id:
|
|
53
|
+
id: source.id,
|
|
55
54
|
get fingerprint() {
|
|
56
|
-
return
|
|
57
|
-
},
|
|
58
|
-
async list() {
|
|
59
|
-
return (await source.list()).filter((skill) => skill.name === name);
|
|
60
|
-
},
|
|
61
|
-
async load(requested) {
|
|
62
|
-
if (requested !== name) return null;
|
|
63
|
-
try {
|
|
64
|
-
const skill = await source.load(requested);
|
|
65
|
-
return skill?.name === name ? skill : null;
|
|
66
|
-
} catch (cause) {
|
|
67
|
-
throw new Error(`Skill unavailable: ${name}`, { cause });
|
|
68
|
-
}
|
|
55
|
+
return source.fingerprint;
|
|
69
56
|
},
|
|
57
|
+
// Runtime configuration already resolved the catalogue; Skill content stays lazy.
|
|
58
|
+
list: async () => [{
|
|
59
|
+
name: binding.name,
|
|
60
|
+
description: binding.description,
|
|
61
|
+
sourceId: source.id,
|
|
62
|
+
}],
|
|
63
|
+
load: (name) => source.load(name),
|
|
70
64
|
...(source.readResource
|
|
71
|
-
? {
|
|
72
|
-
|
|
73
|
-
if (requested !== name) return null;
|
|
74
|
-
try {
|
|
75
|
-
const resource = await source.readResource!(requested, path);
|
|
76
|
-
return resource?.path === path ? resource : null;
|
|
77
|
-
} catch (cause) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`Skill resource unavailable: ${name}/${path}`,
|
|
80
|
-
{ cause },
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
}
|
|
65
|
+
? { readResource: (name: string, path: string) =>
|
|
66
|
+
source.readResource!(name, path) }
|
|
85
67
|
: {}),
|
|
86
68
|
...(source.refresh ? { refresh: () => source.refresh!() } : {}),
|
|
87
69
|
};
|
|
@@ -123,6 +105,9 @@ const SKILL_TOOL_LABELS: Readonly<Record<string, string>> = {
|
|
|
123
105
|
run_skill_script: "Run Skill script",
|
|
124
106
|
};
|
|
125
107
|
|
|
108
|
+
const SKILL_ENTRY_READ_GUIDANCE =
|
|
109
|
+
"SKILL.md contains the Skill instructions; use activate_skill instead.";
|
|
110
|
+
|
|
126
111
|
/** Create Pi candidates from the official Agents SDK SkillRegistry tools. */
|
|
127
112
|
export async function skillPiToolCandidates(
|
|
128
113
|
bindings: readonly PiSkillBinding[],
|
|
@@ -131,17 +116,46 @@ export async function skillPiToolCandidates(
|
|
|
131
116
|
if (bindings.length === 0) return [];
|
|
132
117
|
|
|
133
118
|
const registry = new SkillRegistry(
|
|
134
|
-
bindings.map(
|
|
119
|
+
bindings.map(catalogSkillSource),
|
|
135
120
|
scriptRunner(bindings, options),
|
|
136
121
|
);
|
|
137
122
|
await registry.load();
|
|
138
123
|
|
|
139
|
-
return Object.entries(registry.tools()).map(([name, tool]) =>
|
|
140
|
-
|
|
141
|
-
authorized: true,
|
|
142
|
-
requiredExecutionLevel: name === "run_skill_script" ? "high" : "safe",
|
|
143
|
-
tool: aiToolToPi(name, tool, {
|
|
124
|
+
return Object.entries(registry.tools()).map(([name, tool]) => {
|
|
125
|
+
const adapted = aiToolToPi(name, tool, {
|
|
144
126
|
label: SKILL_TOOL_LABELS[name] ?? name,
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
...(name === "read_skill_resource"
|
|
128
|
+
? {
|
|
129
|
+
description:
|
|
130
|
+
"Read a bundled resource listed by activate_skill. Do not pass SKILL.md; activate_skill returns the Skill instructions.",
|
|
131
|
+
}
|
|
132
|
+
: {}),
|
|
133
|
+
});
|
|
134
|
+
if (name === "read_skill_resource") {
|
|
135
|
+
const execute = adapted.execute;
|
|
136
|
+
adapted.execute = (toolCallId, input, signal) => {
|
|
137
|
+
const target = input as { name?: unknown; path?: unknown };
|
|
138
|
+
const parts = typeof target.path === "string"
|
|
139
|
+
? target.path.split("/")
|
|
140
|
+
: [];
|
|
141
|
+
if (
|
|
142
|
+
target.path === "SKILL.md" ||
|
|
143
|
+
(target.name === undefined &&
|
|
144
|
+
parts.length === 2 &&
|
|
145
|
+
parts[1] === "SKILL.md")
|
|
146
|
+
) {
|
|
147
|
+
return Promise.resolve({
|
|
148
|
+
content: [{ type: "text", text: SKILL_ENTRY_READ_GUIDANCE }],
|
|
149
|
+
details: SKILL_ENTRY_READ_GUIDANCE,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return execute(toolCallId, input, signal);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
owner: "runtime-skill",
|
|
157
|
+
requiredExecutionLevel: name === "run_skill_script" ? "high" : "safe",
|
|
158
|
+
tool: adapted,
|
|
159
|
+
};
|
|
160
|
+
});
|
|
147
161
|
}
|