@springbrand/agent-runtime 0.1.3-alpha.5 → 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 +9 -3
- package/src/adapter/cloudflare/index.ts +0 -5
- package/src/adapter/cloudflare/resources/runtime-resources.ts +14 -11
- package/src/adapter/cloudflare/subagent/tools.ts +2 -4
- 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/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/runtime-adapter/assembly.ts +0 -8
- package/src/pi/tool/base.ts +0 -2
- package/src/pi/tool/compiler.ts +0 -1
- package/src/pi/tool/core.ts +0 -3
- package/src/pi/tool/mcp.ts +3 -4
- package/src/pi/tool/schedule.ts +0 -1
- package/src/pi/tool/skill.ts +55 -41
- package/src/pi/tool/subagent.ts +0 -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 +0 -7
- package/src/runtime-agent.ts +28 -28
- package/src/runtime-assembler.ts +10 -8
- package/src/runtime.ts +46 -13
- package/src/adapter/cloudflare/universal-agent/definition.ts +0 -71
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/agent-runtime",
|
|
3
|
-
"version": "0.1.3-alpha.
|
|
3
|
+
"version": "0.1.3-alpha.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -22,9 +22,15 @@
|
|
|
22
22
|
"@cloudflare/think": "0.15.0",
|
|
23
23
|
"@earendil-works/pi-agent-core": "0.83.0",
|
|
24
24
|
"@earendil-works/pi-ai": "0.83.0",
|
|
25
|
-
"agents": "^0.19.0",
|
|
26
|
-
"ai": "^7.0.0",
|
|
27
25
|
"lodash-es": "^4.18.1",
|
|
28
26
|
"zod": "^4.4.3"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"agents": "^0.19.0",
|
|
30
|
+
"ai": "^7.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"agents": "0.19.0",
|
|
34
|
+
"ai": "7.0.37"
|
|
29
35
|
}
|
|
30
36
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { r2, type SkillSource } from "agents/skills";
|
|
2
2
|
import type { RuntimeDegradation } from "../../../kernel/degradation";
|
|
3
3
|
import type { RuntimeExtensionConfig } from "../../../kernel/extensions";
|
|
4
|
+
import { withRuntimeLoadTimeout } from "../../../kernel/runtime-load";
|
|
4
5
|
import type { RuntimeExtensionContribution } from "../../../runtime-definition";
|
|
5
6
|
|
|
6
7
|
export interface CloudflareRuntimeExtensionSource {
|
|
@@ -38,17 +39,19 @@ export async function readVerifiedR2Text(
|
|
|
38
39
|
sourceRef: string,
|
|
39
40
|
sourceHash: string,
|
|
40
41
|
): Promise<string> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
return withRuntimeLoadTimeout("extension-source:r2", async () => {
|
|
43
|
+
const object = await bucket.get(sourceRef);
|
|
44
|
+
if (!object) throw new Error(`Runtime content not found: ${sourceRef}`);
|
|
45
|
+
const source = await object.text();
|
|
46
|
+
const digest = await crypto.subtle.digest(
|
|
47
|
+
"SHA-256",
|
|
48
|
+
new TextEncoder().encode(source),
|
|
49
|
+
);
|
|
50
|
+
if (hex(digest) !== sourceHash) {
|
|
51
|
+
throw new Error(`Runtime content hash mismatch: ${sourceRef}`);
|
|
52
|
+
}
|
|
53
|
+
return source;
|
|
54
|
+
});
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
export async function prepareRuntimeExtensions(
|
|
@@ -104,7 +104,6 @@ function executeCandidate(options: {
|
|
|
104
104
|
};
|
|
105
105
|
return {
|
|
106
106
|
owner: "subagent-execute",
|
|
107
|
-
authorized: true,
|
|
108
107
|
requiredExecutionLevel: "safe",
|
|
109
108
|
tool,
|
|
110
109
|
};
|
|
@@ -181,7 +180,6 @@ function fetchCandidate(options: {
|
|
|
181
180
|
};
|
|
182
181
|
return {
|
|
183
182
|
owner: "subagent-fetch",
|
|
184
|
-
authorized: true,
|
|
185
183
|
requiredExecutionLevel: "safe",
|
|
186
184
|
tool,
|
|
187
185
|
};
|
|
@@ -242,11 +240,11 @@ export function createCloudflareSubAgentTools(options: {
|
|
|
242
240
|
]
|
|
243
241
|
: [...workspace, fetchCandidate(options)];
|
|
244
242
|
|
|
245
|
-
// 子 Agent 是独立的嵌套 Tool surface
|
|
243
|
+
// 子 Agent 是独立的嵌套 Tool surface:先移除会递归委派的候选,
|
|
246
244
|
// compiler 只负责执行治理和结算。
|
|
247
245
|
const deny = new Set(DENIED_TOOLS);
|
|
248
246
|
const mounted = candidates.filter(
|
|
249
|
-
(candidate) =>
|
|
247
|
+
(candidate) => !deny.has(candidate.tool.name),
|
|
250
248
|
);
|
|
251
249
|
assertSafeExecutionLevel(mounted);
|
|
252
250
|
|
package/src/kernel/bindings.ts
CHANGED
|
@@ -893,15 +893,15 @@ export interface RuntimePlatformPort {
|
|
|
893
893
|
}
|
|
894
894
|
|
|
895
895
|
/**
|
|
896
|
-
*
|
|
896
|
+
* 汇总本次装配已配置的 Skill 来源。
|
|
897
897
|
*
|
|
898
898
|
* @remarks
|
|
899
899
|
* Builder 在生成候选 Snapshot 时创建,Pi 组装在生成 Skill 工具时读取。
|
|
900
900
|
*
|
|
901
|
-
* catalog
|
|
901
|
+
* catalog、来源与脚本策略一起保留,避免装配期重新访问来源。
|
|
902
902
|
*/
|
|
903
903
|
export interface RuntimeSkillBindings {
|
|
904
|
-
/**
|
|
904
|
+
/** Configured external sources and their isolated script policy. */
|
|
905
905
|
sources: readonly RuntimeSkillSourceBinding[];
|
|
906
906
|
}
|
|
907
907
|
|
|
@@ -911,7 +911,7 @@ export interface RuntimeSkillBindings {
|
|
|
911
911
|
* @remarks
|
|
912
912
|
* Skill Plugin 在准备来源时设置,Pi Skill 工具在决定是否注入脚本执行能力时读取。
|
|
913
913
|
*
|
|
914
|
-
*
|
|
914
|
+
* 这是 Config 已解析的执行策略,执行期不应自动放宽。
|
|
915
915
|
*/
|
|
916
916
|
export interface RuntimeSkillScriptPolicy {
|
|
917
917
|
network: "none" | "full";
|
|
@@ -920,12 +920,12 @@ export interface RuntimeSkillScriptPolicy {
|
|
|
920
920
|
}
|
|
921
921
|
|
|
922
922
|
/**
|
|
923
|
-
*
|
|
923
|
+
* 把一个已配置 Skill 来源与其脚本策略放在同一快照中。
|
|
924
924
|
*
|
|
925
925
|
* @remarks
|
|
926
926
|
* Builder 为每个 Skill 贡献创建,Pi 组装和 Skill 工具按名称查找并读取。
|
|
927
927
|
*
|
|
928
|
-
*
|
|
928
|
+
* 名称、内容来源与脚本策略不分开存放,脚本 runner 才能按 Skill 名找到对应策略。
|
|
929
929
|
*/
|
|
930
930
|
export interface RuntimeSkillSourceBinding {
|
|
931
931
|
name: string;
|
|
@@ -353,7 +353,13 @@ export abstract class RecoverableChatAgent<
|
|
|
353
353
|
* 清理延后执行,为断线重连的客户端保留终态流片段。
|
|
354
354
|
*/
|
|
355
355
|
protected completeRecoverableStream(streamId: string): void {
|
|
356
|
+
const completedRequestId = this.resumableStream.activeRequestId;
|
|
356
357
|
this.resumableStream.complete(streamId);
|
|
358
|
+
this.pendingResumeConnections.clear();
|
|
359
|
+
if (completedRequestId === this.continuation.activeRequestId) {
|
|
360
|
+
this.continuation.activeRequestId = null;
|
|
361
|
+
this.continuation.activeConnectionId = null;
|
|
362
|
+
}
|
|
357
363
|
void this.ensureStreamCleanupScheduled();
|
|
358
364
|
}
|
|
359
365
|
|
|
@@ -366,7 +372,13 @@ export abstract class RecoverableChatAgent<
|
|
|
366
372
|
* 流错误标志与业务失败分开,因为续传协议和提交生命周期的责任不同。
|
|
367
373
|
*/
|
|
368
374
|
protected failRecoverableStream(streamId: string): void {
|
|
375
|
+
const erroredRequestId = this.resumableStream.activeRequestId;
|
|
369
376
|
this.resumableStream.markError(streamId);
|
|
377
|
+
this.pendingResumeConnections.clear();
|
|
378
|
+
if (erroredRequestId === this.continuation.activeRequestId) {
|
|
379
|
+
this.continuation.activeRequestId = null;
|
|
380
|
+
this.continuation.activeConnectionId = null;
|
|
381
|
+
}
|
|
370
382
|
void this.ensureStreamCleanupScheduled();
|
|
371
383
|
}
|
|
372
384
|
|
|
@@ -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
|
*
|
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,
|
|
@@ -392,16 +392,8 @@ async function preparePiAssembly(
|
|
|
392
392
|
options: PreparePiAssemblyOptions,
|
|
393
393
|
): Promise<PreparedPiAssembly> {
|
|
394
394
|
const { snapshot } = options;
|
|
395
|
-
const extensionNames = new Set(
|
|
396
|
-
snapshot.pi.extensions.map((extension) =>
|
|
397
|
-
extension.manifest.name.trim()
|
|
398
|
-
),
|
|
399
|
-
);
|
|
400
395
|
const extensions = await assemblePiExtensions({
|
|
401
396
|
extensions: snapshot.pi.extensions,
|
|
402
|
-
published: extensionNames,
|
|
403
|
-
enabled: extensionNames,
|
|
404
|
-
authorized: extensionNames,
|
|
405
397
|
load: async (extension) => {
|
|
406
398
|
const workspacePermission =
|
|
407
399
|
extension.manifest.permissions?.workspace ?? "none";
|
package/src/pi/tool/base.ts
CHANGED
|
@@ -144,7 +144,6 @@ function result<T>(details: T): AgentToolResult<T> {
|
|
|
144
144
|
function candidate<T extends TSchema>(tool: AgentTool<T>): PiToolCandidate {
|
|
145
145
|
return {
|
|
146
146
|
owner: "runtime-base",
|
|
147
|
-
authorized: true,
|
|
148
147
|
requiredExecutionLevel: "safe",
|
|
149
148
|
source: "action",
|
|
150
149
|
tool,
|
|
@@ -269,7 +268,6 @@ export function memoryPiToolCandidate(
|
|
|
269
268
|
};
|
|
270
269
|
return {
|
|
271
270
|
owner: "runtime-memory",
|
|
272
|
-
authorized: true,
|
|
273
271
|
requiredExecutionLevel: "safe",
|
|
274
272
|
source: "action",
|
|
275
273
|
tool,
|
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
|
@@ -51,7 +51,6 @@ export function browserQuickActionPiToolCandidates(
|
|
|
51
51
|
return Object.entries(createQuickActionTools({ browser })).map(
|
|
52
52
|
([name, tool]) => ({
|
|
53
53
|
owner: "core:browser",
|
|
54
|
-
authorized: true,
|
|
55
54
|
requiredExecutionLevel: "low",
|
|
56
55
|
tool: aiToolToPi(name, tool, {
|
|
57
56
|
label: BROWSER_TOOL_LABELS[name] ?? name,
|
|
@@ -94,7 +93,6 @@ export function listExtensionsPiToolCandidate(
|
|
|
94
93
|
};
|
|
95
94
|
return {
|
|
96
95
|
owner: "core:extensions",
|
|
97
|
-
authorized: true,
|
|
98
96
|
requiredExecutionLevel: "low",
|
|
99
97
|
tool,
|
|
100
98
|
};
|
|
@@ -174,7 +172,6 @@ export function codeExecutionPiToolCandidate(
|
|
|
174
172
|
};
|
|
175
173
|
return {
|
|
176
174
|
owner: "core:codemode",
|
|
177
|
-
authorized: true,
|
|
178
175
|
requiredExecutionLevel: "high",
|
|
179
176
|
source: "codemode",
|
|
180
177
|
summary: "Run JavaScript with network and configured connector access",
|
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
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
|
}
|
package/src/pi/tool/subagent.ts
CHANGED
|
@@ -91,7 +91,6 @@ export function subagentPiToolCandidates(
|
|
|
91
91
|
};
|
|
92
92
|
return {
|
|
93
93
|
owner: `subagent:${type.name}`,
|
|
94
|
-
authorized: true,
|
|
95
94
|
requiredExecutionLevel: "safe",
|
|
96
95
|
tool,
|
|
97
96
|
};
|
|
@@ -142,7 +141,6 @@ export function subagentPiToolCandidates(
|
|
|
142
141
|
};
|
|
143
142
|
candidates.push({
|
|
144
143
|
owner: "subagent:background",
|
|
145
|
-
authorized: true,
|
|
146
144
|
requiredExecutionLevel: "low",
|
|
147
145
|
summary: "Dispatch a background sub-agent",
|
|
148
146
|
source: "action",
|
package/src/pi/tool/web-fetch.ts
CHANGED
|
@@ -288,13 +288,11 @@ export function workspacePiToolCandidates(
|
|
|
288
288
|
return [
|
|
289
289
|
...[read, write, edit, list, find, grep, remove].map((tool) => ({
|
|
290
290
|
owner: "workspace",
|
|
291
|
-
authorized: true,
|
|
292
291
|
requiredExecutionLevel: "safe" as const,
|
|
293
292
|
tool,
|
|
294
293
|
})),
|
|
295
294
|
{
|
|
296
295
|
owner: "workspace",
|
|
297
|
-
authorized: true,
|
|
298
296
|
requiredExecutionLevel: "high" as const,
|
|
299
297
|
summary: "Run a Bash script over Workspace files",
|
|
300
298
|
source: "action" as const,
|
|
@@ -474,31 +472,26 @@ export function sandboxPiToolCandidates(
|
|
|
474
472
|
return [
|
|
475
473
|
{
|
|
476
474
|
owner: "sandbox",
|
|
477
|
-
authorized: true,
|
|
478
475
|
requiredExecutionLevel: "high",
|
|
479
476
|
tool: exec,
|
|
480
477
|
},
|
|
481
478
|
{
|
|
482
479
|
owner: "sandbox",
|
|
483
|
-
authorized: true,
|
|
484
480
|
requiredExecutionLevel: "high",
|
|
485
481
|
tool: startProcess,
|
|
486
482
|
},
|
|
487
483
|
{
|
|
488
484
|
owner: "sandbox",
|
|
489
|
-
authorized: true,
|
|
490
485
|
requiredExecutionLevel: "safe",
|
|
491
486
|
tool: processLogs,
|
|
492
487
|
},
|
|
493
488
|
{
|
|
494
489
|
owner: "sandbox",
|
|
495
|
-
authorized: true,
|
|
496
490
|
requiredExecutionLevel: "high",
|
|
497
491
|
tool: stopProcess,
|
|
498
492
|
},
|
|
499
493
|
{
|
|
500
494
|
owner: "sandbox",
|
|
501
|
-
authorized: true,
|
|
502
495
|
requiredExecutionLevel: "high",
|
|
503
496
|
tool: publishFiles,
|
|
504
497
|
},
|
package/src/runtime-agent.ts
CHANGED
|
@@ -44,6 +44,11 @@ import {
|
|
|
44
44
|
} from "./tool-registry";
|
|
45
45
|
import type { RuntimeAssemblyView } from "./kernel/runtime-assembly-view";
|
|
46
46
|
import type { RuntimeConfigUpdateResult } from "./kernel/runtime-config";
|
|
47
|
+
import {
|
|
48
|
+
logRuntimeLoadFailure,
|
|
49
|
+
RUNTIME_LOAD_TIMEOUT_MS,
|
|
50
|
+
withRuntimeLoadTimeout,
|
|
51
|
+
} from "./kernel/runtime-load";
|
|
47
52
|
import { AgentToolRuntimeKernel } from "./agent-tool-runtime";
|
|
48
53
|
import type {
|
|
49
54
|
RuntimeAgentConfigContext,
|
|
@@ -388,20 +393,6 @@ function parseTemporaryAgentLaunch<Config>(
|
|
|
388
393
|
*
|
|
389
394
|
* 生成类把首次加载、并发去重、按 key 重载和失败重试收口到一个入口。
|
|
390
395
|
*/
|
|
391
|
-
export function defineRuntimeAgent<
|
|
392
|
-
Env extends Cloudflare.Env,
|
|
393
|
-
Config,
|
|
394
|
-
Command,
|
|
395
|
-
Change,
|
|
396
|
-
>(
|
|
397
|
-
definition: MutableRuntimeAgentDefinition<Env, Config, Command, Change>,
|
|
398
|
-
): RuntimeAgentClass<Env, Command, Change>;
|
|
399
|
-
export function defineRuntimeAgent<
|
|
400
|
-
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
401
|
-
Config = unknown,
|
|
402
|
-
>(
|
|
403
|
-
definition: ReadonlyRuntimeAgentDefinition<Env, Config>,
|
|
404
|
-
): RuntimeAgentClass<Env>;
|
|
405
396
|
export function defineRuntimeAgent<
|
|
406
397
|
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
407
398
|
Config = unknown,
|
|
@@ -587,14 +578,18 @@ export function defineRuntimeAgent<
|
|
|
587
578
|
const pending = (async () => {
|
|
588
579
|
this.runtimeLoad.begin();
|
|
589
580
|
const loadContext = this.createDefinitionContext();
|
|
590
|
-
const loaded = await (
|
|
591
|
-
definition.load
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
581
|
+
const loaded = await withRuntimeLoadTimeout(
|
|
582
|
+
"definition.load",
|
|
583
|
+
() => (
|
|
584
|
+
definition.load as (
|
|
585
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
586
|
+
requestedRuntimeKey?: string,
|
|
587
|
+
) => Promise<LoadedRuntime<Config>>
|
|
588
|
+
)(
|
|
589
|
+
loadContext,
|
|
590
|
+
requestedRuntimeKey,
|
|
591
|
+
),
|
|
592
|
+
{ timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
|
|
598
593
|
);
|
|
599
594
|
const resolvedRuntimeKey = loaded.runtimeKey.trim();
|
|
600
595
|
if (!resolvedRuntimeKey) {
|
|
@@ -603,12 +598,16 @@ export function defineRuntimeAgent<
|
|
|
603
598
|
const context = this.createDefinitionContext({
|
|
604
599
|
value: loaded.config,
|
|
605
600
|
});
|
|
606
|
-
let tools = normalizeToolAssembly(await (
|
|
607
|
-
definition.tools
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
601
|
+
let tools = normalizeToolAssembly(await withRuntimeLoadTimeout(
|
|
602
|
+
"definition.tools",
|
|
603
|
+
() => (
|
|
604
|
+
definition.tools as (
|
|
605
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
606
|
+
config: Config,
|
|
607
|
+
) => ToolRegistry | ToolAssemblyResult | Promise<ToolRegistry | ToolAssemblyResult>
|
|
608
|
+
)(context, loaded.config),
|
|
609
|
+
{ timeoutMs: RUNTIME_LOAD_TIMEOUT_MS },
|
|
610
|
+
));
|
|
612
611
|
let hooks = resolveDefinitionHooks(context);
|
|
613
612
|
if (this.role === "temporary") {
|
|
614
613
|
this.temporaryDispose = tools.dispose;
|
|
@@ -631,6 +630,7 @@ export function defineRuntimeAgent<
|
|
|
631
630
|
// Host 的会话列表把装配后的第一次投影当作「facet 已就绪」的信号。
|
|
632
631
|
await this.broadcastApprovals();
|
|
633
632
|
})().catch((error) => {
|
|
633
|
+
logRuntimeLoadFailure("runtime", error);
|
|
634
634
|
this.runtimeLoad.fail();
|
|
635
635
|
throw error;
|
|
636
636
|
});
|
package/src/runtime-assembler.ts
CHANGED
|
@@ -45,6 +45,7 @@ import type {
|
|
|
45
45
|
RuntimeToolSurfacePolicy,
|
|
46
46
|
} from "./runtime-definition";
|
|
47
47
|
import type { ToolRegistry } from "./tool-registry";
|
|
48
|
+
import { withRuntimeLoadTimeout } from "./kernel/runtime-load";
|
|
48
49
|
import {
|
|
49
50
|
basePiToolCandidates,
|
|
50
51
|
memoryPiToolCandidate,
|
|
@@ -168,7 +169,7 @@ interface ToolSurface {
|
|
|
168
169
|
readonly degradations: readonly RuntimeDegradation[];
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
// 唯一 Tool Surface seam
|
|
172
|
+
// 唯一 Tool Surface seam:只读取已配置 Port、Resource 和 Agent policy,
|
|
172
173
|
// 统一生成可见 Tool,并在一处做名称、执行档位与冲突校验。
|
|
173
174
|
async function createToolSurface(
|
|
174
175
|
input: ToolSurfaceInput,
|
|
@@ -177,7 +178,6 @@ async function createToolSurface(
|
|
|
177
178
|
const allowsTool = input.policy?.allowsTool;
|
|
178
179
|
const allowsExtension = input.policy?.allowsExtension;
|
|
179
180
|
const visible = (candidate: PiToolCandidate) =>
|
|
180
|
-
candidate.authorized &&
|
|
181
181
|
!deny.has(candidate.tool.name) &&
|
|
182
182
|
allowsTool?.(candidate.tool.name) !== false;
|
|
183
183
|
const browserCandidates = input.platform.browser
|
|
@@ -207,12 +207,12 @@ async function createToolSurface(
|
|
|
207
207
|
|
|
208
208
|
return {
|
|
209
209
|
surface: Object.freeze({
|
|
210
|
-
finalize(
|
|
210
|
+
finalize(candidates: readonly PiToolCandidate[]) {
|
|
211
211
|
const tools = new Map<string, PiToolCandidate>();
|
|
212
212
|
|
|
213
213
|
for (const candidate of [
|
|
214
214
|
...staticCandidates,
|
|
215
|
-
...
|
|
215
|
+
...candidates,
|
|
216
216
|
]) {
|
|
217
217
|
const name = requiredName(candidate.tool.name, "Pi Tool");
|
|
218
218
|
if (!visible(candidate)) continue;
|
|
@@ -576,7 +576,6 @@ export function toolRegistryPiToolCandidates(
|
|
|
576
576
|
const normalized = requiredName(name, "Platform Tool");
|
|
577
577
|
candidates.push(Object.freeze({
|
|
578
578
|
owner: "platform",
|
|
579
|
-
authorized: true,
|
|
580
579
|
requiredExecutionLevel: spec.requiredExecutionLevel,
|
|
581
580
|
...(spec.alwaysRequiresApproval
|
|
582
581
|
? { alwaysRequiresApproval: true }
|
|
@@ -675,9 +674,12 @@ export async function assembleRuntimeSnapshot<
|
|
|
675
674
|
const provider = resources.provider;
|
|
676
675
|
let profile = settings.profile;
|
|
677
676
|
if (input.hooks?.profileOverrides) {
|
|
678
|
-
const overrides = await
|
|
679
|
-
|
|
680
|
-
input.
|
|
677
|
+
const overrides = await withRuntimeLoadTimeout(
|
|
678
|
+
"profile-overrides",
|
|
679
|
+
() => input.hooks!.profileOverrides!(
|
|
680
|
+
input.ctx,
|
|
681
|
+
input.config,
|
|
682
|
+
),
|
|
681
683
|
);
|
|
682
684
|
if (overrides.systemPrompt !== undefined) {
|
|
683
685
|
profile = { ...profile, systemPrompt: overrides.systemPrompt };
|
package/src/runtime.ts
CHANGED
|
@@ -45,7 +45,10 @@ import type {
|
|
|
45
45
|
RuntimeState,
|
|
46
46
|
RuntimeTurnState,
|
|
47
47
|
} from "./kernel/state";
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
RuntimeLoadTracker,
|
|
50
|
+
withRuntimeLoadTimeout,
|
|
51
|
+
} from "./kernel/runtime-load";
|
|
49
52
|
import {
|
|
50
53
|
prepareRuntimeCandidate,
|
|
51
54
|
type RuntimeAssemblyInput,
|
|
@@ -175,6 +178,16 @@ function json(value: unknown): string {
|
|
|
175
178
|
return JSON.stringify(value);
|
|
176
179
|
}
|
|
177
180
|
|
|
181
|
+
async function closeRuntimeGatewaySession(
|
|
182
|
+
session: RuntimeGatewaySession | undefined,
|
|
183
|
+
step: string,
|
|
184
|
+
): Promise<void> {
|
|
185
|
+
if (!session) return;
|
|
186
|
+
await withRuntimeLoadTimeout(step, () => session.close()).catch(
|
|
187
|
+
() => undefined,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
178
191
|
function assistantUsageEvent(
|
|
179
192
|
eventId: string,
|
|
180
193
|
submissionId: string,
|
|
@@ -530,8 +543,15 @@ export abstract class AgentRuntimeKernel<
|
|
|
530
543
|
let gatewaySession: RuntimeGatewaySession | undefined;
|
|
531
544
|
if (candidateSnapshot.bindings.gateway) {
|
|
532
545
|
try {
|
|
533
|
-
gatewaySession = await
|
|
534
|
-
|
|
546
|
+
gatewaySession = await withRuntimeLoadTimeout(
|
|
547
|
+
"gateway.open",
|
|
548
|
+
() => candidateSnapshot.bindings.gateway!.open(
|
|
549
|
+
`${this.ctx.id.toString()}:${crypto.randomUUID()}`,
|
|
550
|
+
),
|
|
551
|
+
{
|
|
552
|
+
onLateResult: (session) =>
|
|
553
|
+
closeRuntimeGatewaySession(session, "gateway.close.late"),
|
|
554
|
+
},
|
|
535
555
|
);
|
|
536
556
|
} catch {
|
|
537
557
|
gatewayDegradations.push({
|
|
@@ -557,7 +577,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
557
577
|
),
|
|
558
578
|
});
|
|
559
579
|
} catch (error) {
|
|
560
|
-
await gatewaySession
|
|
580
|
+
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.failed");
|
|
561
581
|
throw error;
|
|
562
582
|
}
|
|
563
583
|
const revision = await this.hash(prepared.revisionDescriptor);
|
|
@@ -569,18 +589,23 @@ export abstract class AgentRuntimeKernel<
|
|
|
569
589
|
this.submissions.isBusy();
|
|
570
590
|
const parked = contested && this.db.everyUnfinishedSubmissionParked();
|
|
571
591
|
if (contested && !parked) {
|
|
572
|
-
await gatewaySession
|
|
592
|
+
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.contested");
|
|
573
593
|
throw new Error(
|
|
574
594
|
"Cannot reload Runtime while a revision-pinned Pi Turn is active",
|
|
575
595
|
);
|
|
576
596
|
}
|
|
577
597
|
const repin = parked
|
|
578
|
-
? await
|
|
598
|
+
? await withRuntimeLoadTimeout(
|
|
599
|
+
"parked-submission.repin",
|
|
600
|
+
() => this.prepareParkedSubmissionRepin(prepared, revision),
|
|
601
|
+
)
|
|
579
602
|
: undefined;
|
|
580
603
|
let activated = false;
|
|
581
604
|
const previousGatewaySession = this.runtimeGatewaySession;
|
|
582
605
|
try {
|
|
583
|
-
for (const guard of candidate.commitGuards)
|
|
606
|
+
for (const [index, guard] of candidate.commitGuards.entries()) {
|
|
607
|
+
await withRuntimeLoadTimeout(`commit-guard:${index}`, guard);
|
|
608
|
+
}
|
|
584
609
|
if (candidateSnapshot.bindings.platform.telemetryConsole) {
|
|
585
610
|
installConsoleSink();
|
|
586
611
|
}
|
|
@@ -594,13 +619,19 @@ export abstract class AgentRuntimeKernel<
|
|
|
594
619
|
} catch (error) {
|
|
595
620
|
repin?.abort();
|
|
596
621
|
if (activated && previous) this.pi.activate(previous);
|
|
597
|
-
await gatewaySession
|
|
622
|
+
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.aborted");
|
|
598
623
|
throw error;
|
|
599
624
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
625
|
+
if (repin) {
|
|
626
|
+
await withRuntimeLoadTimeout(
|
|
627
|
+
"parked-submission.interrupt",
|
|
628
|
+
() => repin.interrupt(),
|
|
629
|
+
).catch(() => undefined);
|
|
630
|
+
}
|
|
631
|
+
await closeRuntimeGatewaySession(
|
|
632
|
+
previousGatewaySession,
|
|
633
|
+
"gateway.close.previous",
|
|
634
|
+
);
|
|
604
635
|
|
|
605
636
|
const degradations = prepared.degradations;
|
|
606
637
|
if (degradations.length > 0) {
|
|
@@ -2099,7 +2130,9 @@ export abstract class AgentRuntimeKernel<
|
|
|
2099
2130
|
continuation: recovery,
|
|
2100
2131
|
assistantOrdinal,
|
|
2101
2132
|
onRecord: (record) =>
|
|
2102
|
-
this.
|
|
2133
|
+
this.migratedSubmissions.has(submissionId)
|
|
2134
|
+
? undefined
|
|
2135
|
+
: this.persistAndBroadcastRecord(turn, record),
|
|
2103
2136
|
onTerminal: (intent) => {
|
|
2104
2137
|
terminalIntent = intent;
|
|
2105
2138
|
this.appendTerminalIntent(
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defineRuntimeAgent,
|
|
3
|
-
type MutableRuntimeAgentDefinition,
|
|
4
|
-
type ReadonlyRuntimeAgentDefinition,
|
|
5
|
-
type RuntimeAgentClass,
|
|
6
|
-
type RuntimeAgentInstance,
|
|
7
|
-
} from "../../../runtime-agent";
|
|
8
|
-
|
|
9
|
-
export type CloudflareUniversalAgentInstance<
|
|
10
|
-
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
11
|
-
Command = never,
|
|
12
|
-
Change = never,
|
|
13
|
-
> = RuntimeAgentInstance<Env, Command, Change> & {
|
|
14
|
-
revokeUserConnections(): Promise<void>;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export interface CloudflareUniversalAgentClass<
|
|
18
|
-
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
19
|
-
Command = never,
|
|
20
|
-
Change = never,
|
|
21
|
-
> {
|
|
22
|
-
new (
|
|
23
|
-
ctx: DurableObjectState,
|
|
24
|
-
env: Env,
|
|
25
|
-
): CloudflareUniversalAgentInstance<Env, Command, Change>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function defineCloudflareUniversalAgent<
|
|
29
|
-
Env extends Cloudflare.Env,
|
|
30
|
-
Config,
|
|
31
|
-
Command,
|
|
32
|
-
Change,
|
|
33
|
-
>(
|
|
34
|
-
definition: MutableRuntimeAgentDefinition<Env, Config, Command, Change>,
|
|
35
|
-
): CloudflareUniversalAgentClass<Env, Command, Change>;
|
|
36
|
-
export function defineCloudflareUniversalAgent<
|
|
37
|
-
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
38
|
-
Config = unknown,
|
|
39
|
-
>(
|
|
40
|
-
definition: ReadonlyRuntimeAgentDefinition<Env, Config>,
|
|
41
|
-
): CloudflareUniversalAgentClass<Env>;
|
|
42
|
-
export function defineCloudflareUniversalAgent<
|
|
43
|
-
Env extends Cloudflare.Env,
|
|
44
|
-
Config,
|
|
45
|
-
Command,
|
|
46
|
-
Change,
|
|
47
|
-
>(
|
|
48
|
-
definition:
|
|
49
|
-
| MutableRuntimeAgentDefinition<Env, Config, Command, Change>
|
|
50
|
-
| ReadonlyRuntimeAgentDefinition<Env, Config>,
|
|
51
|
-
): CloudflareUniversalAgentClass<Env, Command, Change> {
|
|
52
|
-
const Base = defineRuntimeAgent(
|
|
53
|
-
definition as MutableRuntimeAgentDefinition<
|
|
54
|
-
Env,
|
|
55
|
-
Config,
|
|
56
|
-
Command,
|
|
57
|
-
Change
|
|
58
|
-
>,
|
|
59
|
-
) as RuntimeAgentClass<Env, Command, Change>;
|
|
60
|
-
const Generated = {
|
|
61
|
-
[definition.name]: class extends Base {
|
|
62
|
-
async revokeUserConnections(): Promise<void> {
|
|
63
|
-
await this.stopTurn(undefined, "Session ended");
|
|
64
|
-
for (const connection of this.getConnections()) {
|
|
65
|
-
connection.close(1008, "Session ended");
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
}[definition.name];
|
|
70
|
-
return Generated as CloudflareUniversalAgentClass<Env, Command, Change>;
|
|
71
|
-
}
|