@springbrand/agent-runtime 0.1.3-alpha.4 → 0.1.3-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/src/adapter/cloudflare/index.ts +60 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +86 -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 +256 -0
- package/src/adapter/cloudflare/universal-agent/definition.ts +71 -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/layers/orchestration/temporary-agent/core.ts +12 -1
- package/src/layers/orchestration/temporary-agent/runner.ts +1 -2
- package/src/pi/message/contract.ts +7 -0
- package/src/pi/message/conversion.ts +9 -1
- package/src/pi/runtime-adapter/assembly.ts +4 -2
- package/src/pi/runtime-adapter/index.ts +6 -2
- package/src/pi/tool/base.ts +17 -0
- package/src/pi/tool/core.ts +13 -0
- package/src/pi/tool/schedule.ts +11 -0
- package/src/pi/tool/subagent.ts +14 -0
- package/src/pi/tool/workspace-sandbox.ts +15 -0
- package/src/runtime-agent-context.ts +112 -0
- package/src/runtime-agent.ts +429 -315
- package/src/runtime-assembler.ts +249 -99
- package/src/runtime-definition.ts +173 -0
- package/src/runtime.ts +139 -12
- package/src/tool-registry.ts +143 -0
package/src/runtime-agent.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
callable,
|
|
3
3
|
type Agent,
|
|
4
|
-
type
|
|
4
|
+
type AgentToolChildAdapter,
|
|
5
5
|
type ChatCapableAgentClass,
|
|
6
|
-
type RunAgentToolOptions,
|
|
7
6
|
} from "agents";
|
|
8
7
|
import type { UIMessage } from "ai";
|
|
9
8
|
import type { PiCanonicalTranscriptSnapshot } from "./pi/runtime-adapter";
|
|
@@ -14,19 +13,44 @@ import type {
|
|
|
14
13
|
MessageDispatchReceipt,
|
|
15
14
|
SubmissionReceipt,
|
|
16
15
|
} from "./kernel/receipts";
|
|
17
|
-
import type { ExecutionLevel } from "./lib/execution-level";
|
|
18
16
|
import type { RuntimeState } from "./kernel/state";
|
|
19
17
|
import type {
|
|
20
18
|
TemporaryAgentApprovalDecision,
|
|
21
19
|
TemporaryAgentApprovalRequest,
|
|
22
|
-
|
|
23
|
-
TemporaryAgentRequest,
|
|
24
|
-
TemporaryAgentRunContext,
|
|
20
|
+
TemporaryAgentLaunch,
|
|
25
21
|
} from "./layers/orchestration/temporary-agent/core";
|
|
26
|
-
import
|
|
22
|
+
import { TEMPORARY_AGENT_LAUNCH_KEY } from "./layers/orchestration/temporary-agent/core";
|
|
23
|
+
import {
|
|
24
|
+
temporaryAgentExtensionIsSafe,
|
|
25
|
+
temporaryAgentToolAllowed,
|
|
26
|
+
} from "./layers/orchestration/temporary-agent/runner";
|
|
27
|
+
import {
|
|
28
|
+
EXECUTION_LEVELS,
|
|
29
|
+
requiresExecutionApproval,
|
|
30
|
+
type ExecutionLevel,
|
|
31
|
+
} from "./lib/execution-level";
|
|
32
|
+
import {
|
|
33
|
+
assembleRuntimeSnapshot,
|
|
34
|
+
type RuntimeCandidate,
|
|
35
|
+
} from "./runtime-assembler";
|
|
36
|
+
import type {
|
|
37
|
+
ResolvedResources,
|
|
38
|
+
RuntimeAgentHooks,
|
|
39
|
+
} from "./runtime-definition";
|
|
40
|
+
import {
|
|
41
|
+
normalizeToolAssembly,
|
|
42
|
+
type ToolAssemblyResult,
|
|
43
|
+
type ToolRegistry,
|
|
44
|
+
} from "./tool-registry";
|
|
27
45
|
import type { RuntimeAssemblyView } from "./kernel/runtime-assembly-view";
|
|
28
46
|
import type { RuntimeConfigUpdateResult } from "./kernel/runtime-config";
|
|
29
|
-
import {
|
|
47
|
+
import { AgentToolRuntimeKernel } from "./agent-tool-runtime";
|
|
48
|
+
import type {
|
|
49
|
+
RuntimeAgentConfigContext,
|
|
50
|
+
RuntimeAgentPlanningContext,
|
|
51
|
+
RuntimeAgentRole,
|
|
52
|
+
RuntimeAgentToolResult,
|
|
53
|
+
} from "./runtime-agent-context";
|
|
30
54
|
|
|
31
55
|
/**
|
|
32
56
|
* 本文件把应用提供的 Config Definition 与 Planner 接到 Cloudflare Agent 生命周期。
|
|
@@ -37,165 +61,21 @@ import { AgentRuntimeKernel } from "./runtime";
|
|
|
37
61
|
|
|
38
62
|
// #region Agent 身份与公开契约
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"do",
|
|
53
|
-
"else",
|
|
54
|
-
"enum",
|
|
55
|
-
"eval",
|
|
56
|
-
"export",
|
|
57
|
-
"extends",
|
|
58
|
-
"false",
|
|
59
|
-
"finally",
|
|
60
|
-
"for",
|
|
61
|
-
"function",
|
|
62
|
-
"if",
|
|
63
|
-
"implements",
|
|
64
|
-
"import",
|
|
65
|
-
"in",
|
|
66
|
-
"instanceof",
|
|
67
|
-
"interface",
|
|
68
|
-
"let",
|
|
69
|
-
"new",
|
|
70
|
-
"null",
|
|
71
|
-
"package",
|
|
72
|
-
"private",
|
|
73
|
-
"protected",
|
|
74
|
-
"public",
|
|
75
|
-
"return",
|
|
76
|
-
"static",
|
|
77
|
-
"super",
|
|
78
|
-
"switch",
|
|
79
|
-
"this",
|
|
80
|
-
"throw",
|
|
81
|
-
"true",
|
|
82
|
-
"try",
|
|
83
|
-
"typeof",
|
|
84
|
-
"var",
|
|
85
|
-
"void",
|
|
86
|
-
"while",
|
|
87
|
-
"with",
|
|
88
|
-
"yield",
|
|
89
|
-
]);
|
|
90
|
-
const JAVASCRIPT_IDENTIFIER =
|
|
91
|
-
/^[$_\p{ID_Start}][$\u200C\u200D\p{ID_Continue}]*$/u;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 描述 Agent 寻址路径中的一级父子节点。
|
|
95
|
-
*
|
|
96
|
-
* @remarks
|
|
97
|
-
* Cloudflare Agents SDK 用 `parentPath` 暴露根节点到直接父节点的路径。
|
|
98
|
-
*
|
|
99
|
-
* 调用方通常只读取,不应自行构造后写回 SDK。
|
|
100
|
-
*/
|
|
101
|
-
export interface RuntimeAgentPathStep {
|
|
102
|
-
className: string;
|
|
103
|
-
name: string;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 表示一次 Agent Tool 执行返回给 Runtime 的稳定字段。
|
|
108
|
-
*
|
|
109
|
-
* @remarks
|
|
110
|
-
* Subagent 端口在调用 `RuntimeAgentConfigContext.runAgentTool` 后读取它。
|
|
111
|
-
*
|
|
112
|
-
* 这里只保留 Runtime 需要的结果,避免把 SDK 内部运行对象暴露给应用。
|
|
113
|
-
*/
|
|
114
|
-
export interface RuntimeAgentToolResult {
|
|
115
|
-
status: string;
|
|
116
|
-
runId: string;
|
|
117
|
-
output?: unknown;
|
|
118
|
-
summary?: string;
|
|
119
|
-
error?: string;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* 这是应用创建 Config 时唯一可以接触的 Agent 实例能力。
|
|
124
|
-
*
|
|
125
|
-
* 身份通过实时 getter 暴露。
|
|
126
|
-
*
|
|
127
|
-
* 待确认:原注释把 getter 的必要性归因于 facet 元数据在 Durable Object
|
|
128
|
-
* 构造后恢复,但当前 SDK 会在用户 `onStart` 前完成恢复。
|
|
129
|
-
*/
|
|
130
|
-
export interface RuntimeAgentConfigContext<
|
|
131
|
-
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
132
|
-
> {
|
|
133
|
-
/** 读取当前 Durable Object 的状态句柄。 */
|
|
134
|
-
readonly ctx: DurableObjectState;
|
|
135
|
-
/** 读取当前 Worker 的环境绑定。 */
|
|
136
|
-
readonly env: Env;
|
|
137
|
-
/** 读取当前 Agent 实例名称。 */
|
|
138
|
-
readonly name: string;
|
|
139
|
-
/** 读取从根节点到直接父节点的 Agent 路径。 */
|
|
140
|
-
readonly parentPath: readonly RuntimeAgentPathStep[];
|
|
141
|
-
/**
|
|
142
|
-
* 通过当前 Agent 调用一个受 SDK 管理的子 Agent Tool。
|
|
143
|
-
*
|
|
144
|
-
* @remarks
|
|
145
|
-
* subagent 端口在父 Agent 需要保留子运行状态、事件和取消边界时调用。
|
|
146
|
-
*
|
|
147
|
-
* 调用必须经过当前 Agent 实例,才能使用 Cloudflare Agents SDK 的受管子运行登记。
|
|
148
|
-
*/
|
|
149
|
-
runAgentTool(
|
|
150
|
-
agentClass: ChatCapableAgentClass,
|
|
151
|
-
options: RunAgentToolOptions<unknown>,
|
|
152
|
-
): Promise<RuntimeAgentToolResult>;
|
|
153
|
-
/**
|
|
154
|
-
* 清理符合年龄和状态条件的 SDK 子运行记录。
|
|
155
|
-
*
|
|
156
|
-
* @remarks
|
|
157
|
-
* subagent 清理逻辑在需要回收已保留运行时调用。
|
|
158
|
-
*
|
|
159
|
-
* 必须显式提供年龄和状态筛选,避免无边界地删除仍需查询的子运行。
|
|
160
|
-
*/
|
|
161
|
-
clearAgentToolRuns(options: {
|
|
162
|
-
olderThan: number;
|
|
163
|
-
status: AgentToolRunStatus[];
|
|
164
|
-
}): Promise<void>;
|
|
165
|
-
/** 读取当前已装配 User Agent 的执行档位。 */
|
|
166
|
-
executionLevel(): ExecutionLevel;
|
|
167
|
-
/**
|
|
168
|
-
* 在当前 Session Runtime 中执行一个仅对本次调用存活的临时 Agent。
|
|
169
|
-
*
|
|
170
|
-
* @remarks
|
|
171
|
-
* temporary-agent 端口在 Tool 需要一次性委派时调用。
|
|
172
|
-
*
|
|
173
|
-
* 它复用当前 Session 的 Runtime 和取消信号,但不创建持久 facet。
|
|
174
|
-
*/
|
|
175
|
-
runTemporaryAgent(
|
|
176
|
-
request: TemporaryAgentRequest,
|
|
177
|
-
context: TemporaryAgentRunContext,
|
|
178
|
-
execute: TemporaryAgentExecutor,
|
|
179
|
-
): Promise<string>;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export interface RuntimeAgentPlanningContext<
|
|
183
|
-
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
184
|
-
Command = never,
|
|
185
|
-
Change = never,
|
|
186
|
-
> extends RuntimeAgentConfigContext<Env> {
|
|
187
|
-
/** 仅可变 Definition 提供;Host Tool 用它复用生成类的更新与重载编排。 */
|
|
188
|
-
readonly updateConfig?: (
|
|
189
|
-
command: Command,
|
|
190
|
-
) => Promise<RuntimeConfigUpdateResult<Change>>;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Config 读取的显式结果;runtimeKey 是生成类唯一的缓存键。
|
|
195
|
-
*/
|
|
196
|
-
export interface ResolvedRuntimeConfig<Config> {
|
|
64
|
+
export type {
|
|
65
|
+
RuntimeAgentConfigContext,
|
|
66
|
+
RuntimeAgentContext,
|
|
67
|
+
RuntimeAgentPathStep,
|
|
68
|
+
RuntimeAgentPlanningContext,
|
|
69
|
+
RuntimeAgentRole,
|
|
70
|
+
RuntimeAgentToolResult,
|
|
71
|
+
RuntimeAssemblyContext,
|
|
72
|
+
} from "./runtime-agent-context";
|
|
73
|
+
|
|
74
|
+
/** Host 一次性准备的完整 Runtime 加载结果。 */
|
|
75
|
+
export interface LoadedRuntime<Config> {
|
|
197
76
|
readonly runtimeKey: string;
|
|
198
77
|
readonly config: Config;
|
|
78
|
+
readonly resources: ResolvedResources;
|
|
199
79
|
}
|
|
200
80
|
|
|
201
81
|
export interface RuntimeConfigUpdate<Change> {
|
|
@@ -203,60 +83,36 @@ export interface RuntimeConfigUpdate<Change> {
|
|
|
203
83
|
readonly change: Change;
|
|
204
84
|
}
|
|
205
85
|
|
|
206
|
-
|
|
86
|
+
interface RuntimeAgentDefinitionBase<
|
|
207
87
|
Env extends Cloudflare.Env,
|
|
208
88
|
Config,
|
|
209
89
|
Command,
|
|
210
90
|
Change,
|
|
211
91
|
> {
|
|
212
|
-
|
|
213
|
-
|
|
92
|
+
/** 必须等于 Worker 导出名(Wrangler `class_name`)。 */
|
|
93
|
+
readonly name: string;
|
|
94
|
+
|
|
95
|
+
readonly load: (
|
|
96
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
214
97
|
requestedRuntimeKey?: string,
|
|
215
|
-
)
|
|
216
|
-
update?(
|
|
217
|
-
context: RuntimeAgentConfigContext<Env>,
|
|
218
|
-
command: Command,
|
|
219
|
-
): Promise<RuntimeConfigUpdate<Change>>;
|
|
220
|
-
}
|
|
98
|
+
) => Promise<LoadedRuntime<Config>>;
|
|
221
99
|
|
|
222
|
-
|
|
223
|
-
* 告诉 Runtime 如何读取有效配置,并把它规划为一次扁平装配输入。
|
|
224
|
-
*
|
|
225
|
-
* @remarks
|
|
226
|
-
* Worker 入口把它传给 `defineRuntimeAgent`。
|
|
227
|
-
*
|
|
228
|
-
* Config 只负责业务寻址,Planner 把已读取配置投影为装配输入。
|
|
229
|
-
*/
|
|
230
|
-
interface RuntimeAgentDefinitionBase<
|
|
231
|
-
Env extends Cloudflare.Env,
|
|
232
|
-
Config,
|
|
233
|
-
Command,
|
|
234
|
-
Change,
|
|
235
|
-
> {
|
|
236
|
-
/**
|
|
237
|
-
* 必须等于 Worker 导出名。
|
|
238
|
-
*
|
|
239
|
-
* 顶层 Agent 还必须与 Wrangler `class_name` 一致。
|
|
240
|
-
*
|
|
241
|
-
* 纯子 facet 不需要自己的顶层绑定。
|
|
242
|
-
*
|
|
243
|
-
* Agents SDK 使用构造器名称解析 facet 回调。
|
|
244
|
-
*/
|
|
245
|
-
className: string;
|
|
246
|
-
planRuntime(
|
|
100
|
+
readonly tools: (
|
|
247
101
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
248
102
|
config: Config,
|
|
249
|
-
)
|
|
103
|
+
) => ToolRegistry | ToolAssemblyResult | Promise<ToolRegistry | ToolAssemblyResult>;
|
|
104
|
+
|
|
105
|
+
readonly hooks?:
|
|
106
|
+
| RuntimeAgentHooks<Env, Config, Command, Change>
|
|
107
|
+
| ((context: RuntimeAgentPlanningContext<Env, Command, Change>) =>
|
|
108
|
+
RuntimeAgentHooks<Env, Config, Command, Change>);
|
|
250
109
|
}
|
|
251
110
|
|
|
252
111
|
export interface ReadonlyRuntimeAgentDefinition<
|
|
253
112
|
Env extends Cloudflare.Env = Cloudflare.Env,
|
|
254
113
|
Config = unknown,
|
|
255
114
|
> extends RuntimeAgentDefinitionBase<Env, Config, never, never> {
|
|
256
|
-
readonly
|
|
257
|
-
RuntimeAgentConfigDefinition<Env, Config, never, never>,
|
|
258
|
-
"update"
|
|
259
|
-
> & { readonly update?: never };
|
|
115
|
+
readonly update?: never;
|
|
260
116
|
}
|
|
261
117
|
|
|
262
118
|
export interface MutableRuntimeAgentDefinition<
|
|
@@ -265,14 +121,10 @@ export interface MutableRuntimeAgentDefinition<
|
|
|
265
121
|
Command = unknown,
|
|
266
122
|
Change = unknown,
|
|
267
123
|
> extends RuntimeAgentDefinitionBase<Env, Config, Command, Change> {
|
|
268
|
-
readonly
|
|
269
|
-
Env
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
Change
|
|
273
|
-
> & Required<
|
|
274
|
-
Pick<RuntimeAgentConfigDefinition<Env, Config, Command, Change>, "update">
|
|
275
|
-
>;
|
|
124
|
+
readonly update: (
|
|
125
|
+
context: RuntimeAgentConfigContext<Env>,
|
|
126
|
+
command: Command,
|
|
127
|
+
) => Promise<RuntimeConfigUpdate<Change>>;
|
|
276
128
|
}
|
|
277
129
|
|
|
278
130
|
export type RuntimeAgentDefinition<
|
|
@@ -447,14 +299,11 @@ export interface RuntimeAgentControls {
|
|
|
447
299
|
): Promise<{ ok: boolean }>;
|
|
448
300
|
}
|
|
449
301
|
|
|
450
|
-
export
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
command: Command,
|
|
456
|
-
): Promise<RuntimeConfigUpdateResult<Change>>;
|
|
457
|
-
};
|
|
302
|
+
export interface RuntimeAgentConfigControls<Command, Change> {
|
|
303
|
+
updateConfig(
|
|
304
|
+
command: Command,
|
|
305
|
+
): Promise<RuntimeConfigUpdateResult<Change>>;
|
|
306
|
+
}
|
|
458
307
|
|
|
459
308
|
/**
|
|
460
309
|
* 表示 `defineRuntimeAgent` 生成类的实例。
|
|
@@ -467,7 +316,7 @@ export type RuntimeAgentInstance<
|
|
|
467
316
|
Command = never,
|
|
468
317
|
Change = never,
|
|
469
318
|
> = Agent<Env, RuntimeState> & RuntimeAgentControls &
|
|
470
|
-
RuntimeAgentConfigControls<Command, Change>;
|
|
319
|
+
RuntimeAgentConfigControls<Command, Change> & AgentToolChildAdapter<unknown, string>;
|
|
471
320
|
|
|
472
321
|
/**
|
|
473
322
|
* 表示 `defineRuntimeAgent` 返回的 Durable Object 类构造器。
|
|
@@ -490,18 +339,45 @@ export interface RuntimeAgentClass<
|
|
|
490
339
|
|
|
491
340
|
// #region 生成类的构造与生命周期
|
|
492
341
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
342
|
+
const TERMINAL_AGENT_TOOL_STATUSES = [
|
|
343
|
+
"completed",
|
|
344
|
+
"error",
|
|
345
|
+
"aborted",
|
|
346
|
+
"interrupted",
|
|
347
|
+
] as const;
|
|
348
|
+
const AGENT_TOOL_RETENTION_MS = 24 * 60 * 60 * 1_000;
|
|
349
|
+
|
|
350
|
+
function parseTemporaryAgentLaunch<Config>(
|
|
351
|
+
input: unknown,
|
|
352
|
+
): TemporaryAgentLaunch<Config> {
|
|
353
|
+
const record = input != null && typeof input === "object"
|
|
354
|
+
? input as Record<string, unknown>
|
|
355
|
+
: {};
|
|
356
|
+
const read = (name: string, max: number) => {
|
|
357
|
+
const value = record[name];
|
|
358
|
+
if (typeof value !== "string" || !value.trim() || value.length > max) {
|
|
359
|
+
throw new Error(`invalid temporary agent ${name}`);
|
|
360
|
+
}
|
|
361
|
+
return value.trim();
|
|
362
|
+
};
|
|
363
|
+
const executionLevel = record.executionLevel;
|
|
497
364
|
if (
|
|
498
|
-
|
|
499
|
-
|
|
365
|
+
typeof executionLevel !== "string" ||
|
|
366
|
+
!EXECUTION_LEVELS.includes(executionLevel as ExecutionLevel)
|
|
500
367
|
) {
|
|
501
|
-
throw new Error(
|
|
502
|
-
`Invalid Runtime Agent className "${className}": expected a non-reserved JavaScript identifier`,
|
|
503
|
-
);
|
|
368
|
+
throw new Error("invalid temporary agent executionLevel");
|
|
504
369
|
}
|
|
370
|
+
if (!("config" in record)) {
|
|
371
|
+
throw new Error("invalid temporary agent config");
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
subagentName: read("subagentName", 128),
|
|
375
|
+
instructions: read("instructions", 65_536),
|
|
376
|
+
task: read("task", 65_536),
|
|
377
|
+
runtimeKey: read("runtimeKey", 512),
|
|
378
|
+
config: record.config as Config,
|
|
379
|
+
executionLevel: executionLevel as ExecutionLevel,
|
|
380
|
+
};
|
|
505
381
|
}
|
|
506
382
|
|
|
507
383
|
/**
|
|
@@ -536,17 +412,32 @@ export function defineRuntimeAgent<
|
|
|
536
412
|
| MutableRuntimeAgentDefinition<Env, Config, Command, Change>
|
|
537
413
|
| ReadonlyRuntimeAgentDefinition<Env, Config>,
|
|
538
414
|
): RuntimeAgentClass<Env, Command, Change> {
|
|
539
|
-
|
|
540
|
-
const planRuntime = definition.planRuntime as (
|
|
415
|
+
const resolveDefinitionHooks = (
|
|
541
416
|
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
542
|
-
|
|
543
|
-
|
|
417
|
+
): RuntimeAgentHooks<Env, Config, Command, Change> | undefined => {
|
|
418
|
+
const hooks = definition.hooks;
|
|
419
|
+
if (!hooks) return undefined;
|
|
420
|
+
return typeof hooks === "function"
|
|
421
|
+
? (hooks as (
|
|
422
|
+
ctx: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
423
|
+
) => RuntimeAgentHooks<Env, Config, Command, Change>)(context)
|
|
424
|
+
: hooks as RuntimeAgentHooks<Env, Config, Command, Change>;
|
|
425
|
+
};
|
|
544
426
|
|
|
545
427
|
const GeneratedRuntimeAgent = {
|
|
546
|
-
[definition.
|
|
428
|
+
[definition.name]: class extends AgentToolRuntimeKernel<Env> {
|
|
547
429
|
private hasLoadedRuntime = false;
|
|
548
430
|
private loadedRuntimeKey?: string;
|
|
549
431
|
private loading?: Promise<void>;
|
|
432
|
+
private temporaryLaunch?: TemporaryAgentLaunch<Config>;
|
|
433
|
+
private temporaryDispose?: () => Promise<void>;
|
|
434
|
+
private temporaryCleanup?: Promise<void>;
|
|
435
|
+
|
|
436
|
+
protected get role(): RuntimeAgentRole {
|
|
437
|
+
return this.parentPath.at(-1)?.className === definition.name
|
|
438
|
+
? "temporary"
|
|
439
|
+
: "primary";
|
|
440
|
+
}
|
|
550
441
|
|
|
551
442
|
// 作用:在 Kernel 真正消费 RuntimeSnapshot 前确保配置已完整提交。
|
|
552
443
|
// 调用:Submission 准入、恢复、审批续跑与 Runtime Workspace 路径调用。
|
|
@@ -568,8 +459,13 @@ export function defineRuntimeAgent<
|
|
|
568
459
|
async updateConfig(
|
|
569
460
|
command: Command,
|
|
570
461
|
): Promise<RuntimeConfigUpdateResult<Change>> {
|
|
571
|
-
|
|
572
|
-
|
|
462
|
+
if (this.role === "temporary") {
|
|
463
|
+
throw new Error("Temporary agents cannot update Runtime Config");
|
|
464
|
+
}
|
|
465
|
+
const update = definition.update;
|
|
466
|
+
if (!update) {
|
|
467
|
+
return { change: undefined as Change, runtime: "unchanged" };
|
|
468
|
+
}
|
|
573
469
|
const result = await update(this.createDefinitionContext(), command);
|
|
574
470
|
if (!result.changed) {
|
|
575
471
|
return { change: result.change, runtime: "unchanged" };
|
|
@@ -629,90 +525,259 @@ export function defineRuntimeAgent<
|
|
|
629
525
|
return super.stopTurn(requestId, reason);
|
|
630
526
|
}
|
|
631
527
|
|
|
528
|
+
protected override async prepareAgentToolRun(
|
|
529
|
+
input: unknown,
|
|
530
|
+
): Promise<string> {
|
|
531
|
+
if (this.role !== "temporary") {
|
|
532
|
+
throw new Error("Agent Tool runs require a temporary child facet");
|
|
533
|
+
}
|
|
534
|
+
const launch = parseTemporaryAgentLaunch<Config>(input);
|
|
535
|
+
this.temporaryLaunch = launch;
|
|
536
|
+
await this.ctx.storage.put(TEMPORARY_AGENT_LAUNCH_KEY, launch);
|
|
537
|
+
await this.reloadRuntime(launch.runtimeKey, { force: true });
|
|
538
|
+
return launch.task;
|
|
539
|
+
}
|
|
540
|
+
|
|
632
541
|
// 作用:把首次加载和按 key 重载合并成一个串行入口。
|
|
633
542
|
// 调用:Runtime readiness、Session 配置和重载都会调用。
|
|
634
543
|
// 原因:共享同一个 Promise 可合并并发首加载,显式切 key 则等待前序完成后再判断。
|
|
635
|
-
|
|
544
|
+
protected ensureConfig(
|
|
636
545
|
requestedRuntimeKey?: string,
|
|
637
546
|
force = false,
|
|
638
547
|
fromQueue = false,
|
|
639
548
|
): Promise<void> {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
549
|
+
if (this.loading && !fromQueue) {
|
|
550
|
+
if (!requestedRuntimeKey && !force) return this.loading;
|
|
551
|
+
|
|
552
|
+
// 显式 key 或强制刷新不能被正在进行的其他加载吞掉,必须等它结束后重新核对。
|
|
553
|
+
const queued = this.loading
|
|
554
|
+
.catch(() => undefined)
|
|
555
|
+
.then(() => {
|
|
556
|
+
if (
|
|
557
|
+
!force &&
|
|
558
|
+
this.hasLoadedRuntime &&
|
|
559
|
+
this.loadedRuntimeKey === requestedRuntimeKey
|
|
560
|
+
) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
return this.ensureConfig(
|
|
564
|
+
requestedRuntimeKey,
|
|
565
|
+
force,
|
|
566
|
+
true,
|
|
567
|
+
);
|
|
568
|
+
});
|
|
569
|
+
let tracked: Promise<void>;
|
|
570
|
+
tracked = queued.finally(() => {
|
|
571
|
+
if (this.loading === tracked) this.loading = undefined;
|
|
572
|
+
});
|
|
573
|
+
this.loading = tracked;
|
|
574
|
+
return tracked;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (
|
|
578
|
+
!force &&
|
|
579
|
+
this.hasLoadedRuntime &&
|
|
580
|
+
(!requestedRuntimeKey ||
|
|
581
|
+
requestedRuntimeKey === this.loadedRuntimeKey)
|
|
582
|
+
) {
|
|
583
|
+
return Promise.resolve();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// 先创建局部 Promise,再赋给 `loading`,失败时也能在 finally 中放开重试。
|
|
587
|
+
const pending = (async () => {
|
|
588
|
+
this.runtimeLoad.begin();
|
|
589
|
+
const loadContext = this.createDefinitionContext();
|
|
590
|
+
const loaded = await (
|
|
591
|
+
definition.load as (
|
|
592
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
593
|
+
requestedRuntimeKey?: string,
|
|
594
|
+
) => Promise<LoadedRuntime<Config>>
|
|
595
|
+
)(
|
|
596
|
+
loadContext,
|
|
597
|
+
requestedRuntimeKey,
|
|
598
|
+
);
|
|
599
|
+
const resolvedRuntimeKey = loaded.runtimeKey.trim();
|
|
600
|
+
if (!resolvedRuntimeKey) {
|
|
601
|
+
throw new Error("Resolved Runtime key must not be empty");
|
|
602
|
+
}
|
|
603
|
+
const context = this.createDefinitionContext({
|
|
604
|
+
value: loaded.config,
|
|
605
|
+
});
|
|
606
|
+
let tools = normalizeToolAssembly(await (
|
|
607
|
+
definition.tools as (
|
|
608
|
+
context: RuntimeAgentPlanningContext<Env, Command, Change>,
|
|
609
|
+
config: Config,
|
|
610
|
+
) => ToolRegistry | ToolAssemblyResult | Promise<ToolRegistry | ToolAssemblyResult>
|
|
611
|
+
)(context, loaded.config));
|
|
612
|
+
let hooks = resolveDefinitionHooks(context);
|
|
613
|
+
if (this.role === "temporary") {
|
|
614
|
+
this.temporaryDispose = tools.dispose;
|
|
615
|
+
tools = this.applyTemporaryToolPolicy(tools);
|
|
616
|
+
hooks = await this.createTemporaryHooks();
|
|
617
|
+
}
|
|
618
|
+
const candidate = await assembleRuntimeSnapshot({
|
|
619
|
+
ctx: context,
|
|
620
|
+
config: loaded.config,
|
|
621
|
+
tools,
|
|
622
|
+
resources: loaded.resources,
|
|
623
|
+
hooks,
|
|
659
624
|
});
|
|
625
|
+
|
|
626
|
+
await this.initCandidate(candidate);
|
|
627
|
+
this.hasLoadedRuntime = true;
|
|
628
|
+
this.loadedRuntimeKey = resolvedRuntimeKey;
|
|
629
|
+
this.runtimeLoad.complete();
|
|
630
|
+
// 提交之前 turnEvents 绑定还不存在,此前每一次广播都到不了 Host。
|
|
631
|
+
// Host 的会话列表把装配后的第一次投影当作「facet 已就绪」的信号。
|
|
632
|
+
await this.broadcastApprovals();
|
|
633
|
+
})().catch((error) => {
|
|
634
|
+
this.runtimeLoad.fail();
|
|
635
|
+
throw error;
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
if (fromQueue) return pending;
|
|
639
|
+
|
|
640
|
+
// 无论成功失败都清掉进行中标记,后续调用才可以重试或切 key。
|
|
660
641
|
let tracked: Promise<void>;
|
|
661
|
-
tracked =
|
|
642
|
+
tracked = pending.finally(() => {
|
|
662
643
|
if (this.loading === tracked) this.loading = undefined;
|
|
663
644
|
});
|
|
664
645
|
this.loading = tracked;
|
|
665
646
|
return tracked;
|
|
666
647
|
}
|
|
667
648
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
649
|
+
private applyTemporaryToolPolicy(
|
|
650
|
+
assembly: ToolAssemblyResult,
|
|
651
|
+
): ToolAssemblyResult {
|
|
652
|
+
const policy = assembly.surfacePolicy;
|
|
653
|
+
return {
|
|
654
|
+
...assembly,
|
|
655
|
+
bindings: assembly.bindings?.workspace
|
|
656
|
+
? { workspace: assembly.bindings.workspace }
|
|
657
|
+
: {},
|
|
658
|
+
memoryProfile: {
|
|
659
|
+
enabled: false,
|
|
660
|
+
memoryTokens: 2_000,
|
|
661
|
+
preferencesTokens: 500,
|
|
662
|
+
},
|
|
663
|
+
enabledSubagents: [],
|
|
664
|
+
surfacePolicy: {
|
|
665
|
+
allowsTool: (name) =>
|
|
666
|
+
temporaryAgentToolAllowed(name) &&
|
|
667
|
+
policy?.allowsTool?.(name) !== false,
|
|
668
|
+
allowsExtension: (extension) =>
|
|
669
|
+
temporaryAgentExtensionIsSafe(extension) &&
|
|
670
|
+
policy?.allowsExtension?.(extension) !== false,
|
|
671
|
+
},
|
|
672
|
+
};
|
|
673
|
+
}
|
|
676
674
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
requestedRuntimeKey,
|
|
675
|
+
private async createTemporaryHooks(): Promise<
|
|
676
|
+
RuntimeAgentHooks<Env, Config, Command, Change>
|
|
677
|
+
> {
|
|
678
|
+
const launch = this.temporaryLaunch ??
|
|
679
|
+
await this.ctx.storage.get<TemporaryAgentLaunch<Config>>(
|
|
680
|
+
TEMPORARY_AGENT_LAUNCH_KEY,
|
|
684
681
|
);
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
throw error;
|
|
702
|
-
});
|
|
682
|
+
if (!launch) throw new Error("Temporary Agent is not initialized");
|
|
683
|
+
this.temporaryLaunch = launch;
|
|
684
|
+
return {
|
|
685
|
+
profileOverrides: () => ({
|
|
686
|
+
systemPrompt: launch.instructions,
|
|
687
|
+
executionLevel: "high",
|
|
688
|
+
}),
|
|
689
|
+
gateTool: async (request) => {
|
|
690
|
+
if (!requiresExecutionApproval(
|
|
691
|
+
launch.executionLevel,
|
|
692
|
+
request.requiredExecutionLevel,
|
|
693
|
+
)) return;
|
|
694
|
+
await this.requestParentTemporaryApproval(launch, request);
|
|
695
|
+
},
|
|
696
|
+
};
|
|
697
|
+
}
|
|
703
698
|
|
|
704
|
-
|
|
699
|
+
private async requestParentTemporaryApproval(
|
|
700
|
+
launch: TemporaryAgentLaunch<Config>,
|
|
701
|
+
request: {
|
|
702
|
+
toolCallId: string;
|
|
703
|
+
toolName: string;
|
|
704
|
+
input: unknown;
|
|
705
|
+
requiredExecutionLevel: ExecutionLevel;
|
|
706
|
+
signal: AbortSignal;
|
|
707
|
+
},
|
|
708
|
+
): Promise<void> {
|
|
709
|
+
if (request.signal.aborted) {
|
|
710
|
+
throw new Error("temporary agent was cancelled", {
|
|
711
|
+
cause: request.signal.reason,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
const parent = await this.parentAgent(
|
|
715
|
+
this.constructor as ChatCapableAgentClass,
|
|
716
|
+
) as unknown as Pick<
|
|
717
|
+
RuntimeAgentControls,
|
|
718
|
+
"requestTemporaryAgentApproval" | "cancelTemporaryAgentApproval"
|
|
719
|
+
>;
|
|
720
|
+
const executionId =
|
|
721
|
+
`temporary-agent:${this.name}:${request.toolCallId}`;
|
|
722
|
+
let onAbort = () => {};
|
|
723
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
724
|
+
onAbort = () => {
|
|
725
|
+
void parent.cancelTemporaryAgentApproval(
|
|
726
|
+
executionId,
|
|
727
|
+
"temporary agent was cancelled",
|
|
728
|
+
);
|
|
729
|
+
reject(new Error("temporary agent was cancelled", {
|
|
730
|
+
cause: request.signal.reason,
|
|
731
|
+
}));
|
|
732
|
+
};
|
|
733
|
+
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
734
|
+
});
|
|
735
|
+
try {
|
|
736
|
+
const decision = await Promise.race([
|
|
737
|
+
parent.requestTemporaryAgentApproval({
|
|
738
|
+
executionId,
|
|
739
|
+
subagentName: launch.subagentName,
|
|
740
|
+
requestId: `temporary-agent:${this.name}`,
|
|
741
|
+
toolCallId: request.toolCallId,
|
|
742
|
+
toolName: request.toolName,
|
|
743
|
+
executionLevel: launch.executionLevel,
|
|
744
|
+
requiredExecutionLevel: request.requiredExecutionLevel,
|
|
745
|
+
input: request.input,
|
|
746
|
+
}),
|
|
747
|
+
aborted,
|
|
748
|
+
]);
|
|
749
|
+
if (!decision.approved) {
|
|
750
|
+
throw new Error(
|
|
751
|
+
`temporary agent "${launch.subagentName}" Tool ` +
|
|
752
|
+
`"${request.toolName}" was rejected: ` +
|
|
753
|
+
`${decision.reason?.trim() || "rejected by user"}`,
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
} finally {
|
|
757
|
+
request.signal.removeEventListener("abort", onAbort);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
705
760
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (this.
|
|
761
|
+
protected override cleanupAgentToolRun(): Promise<void> {
|
|
762
|
+
if (this.temporaryCleanup) return this.temporaryCleanup;
|
|
763
|
+
this.temporaryCleanup = (async () => {
|
|
764
|
+
if (!this.temporaryDispose) {
|
|
765
|
+
const launch = this.temporaryLaunch ??
|
|
766
|
+
await this.ctx.storage.get<TemporaryAgentLaunch<Config>>(
|
|
767
|
+
TEMPORARY_AGENT_LAUNCH_KEY,
|
|
768
|
+
);
|
|
769
|
+
if (launch) await this.ensureConfig(launch.runtimeKey);
|
|
770
|
+
}
|
|
771
|
+
await this.temporaryDispose?.();
|
|
772
|
+
})().catch((error) => {
|
|
773
|
+
console.warn("Temporary Agent resource cleanup failed", error);
|
|
710
774
|
});
|
|
711
|
-
this.
|
|
712
|
-
return tracked;
|
|
775
|
+
return this.temporaryCleanup;
|
|
713
776
|
}
|
|
714
777
|
|
|
715
|
-
|
|
778
|
+
protected createDefinitionContext(
|
|
779
|
+
runtimeConfig?: { value: Config },
|
|
780
|
+
): RuntimeAgentPlanningContext<
|
|
716
781
|
Env,
|
|
717
782
|
Command,
|
|
718
783
|
Change
|
|
@@ -727,7 +792,10 @@ export function defineRuntimeAgent<
|
|
|
727
792
|
get parentPath() {
|
|
728
793
|
return agent.parentPath;
|
|
729
794
|
},
|
|
730
|
-
|
|
795
|
+
get role() {
|
|
796
|
+
return agent.role;
|
|
797
|
+
},
|
|
798
|
+
...(agent.role === "primary"
|
|
731
799
|
? {
|
|
732
800
|
updateConfig: (command: Command) =>
|
|
733
801
|
agent.updateConfig(command),
|
|
@@ -741,16 +809,64 @@ export function defineRuntimeAgent<
|
|
|
741
809
|
clearAgentToolRuns: (options) =>
|
|
742
810
|
agent.clearAgentToolRuns(options),
|
|
743
811
|
executionLevel: () => agent.executionLevel(),
|
|
744
|
-
runTemporaryAgent: (request, runContext
|
|
745
|
-
agent.
|
|
812
|
+
runTemporaryAgent: (request, runContext) => {
|
|
813
|
+
if (agent.role !== "primary" || !runtimeConfig) {
|
|
814
|
+
throw new Error("Temporary Agent cannot delegate again");
|
|
815
|
+
}
|
|
816
|
+
return agent.runTemporaryAgent(
|
|
817
|
+
request,
|
|
818
|
+
runContext,
|
|
819
|
+
async (normalized) => {
|
|
820
|
+
await agent.clearAgentToolRuns({
|
|
821
|
+
olderThan: Date.now() - AGENT_TOOL_RETENTION_MS,
|
|
822
|
+
status: [...TERMINAL_AGENT_TOOL_STATUSES],
|
|
823
|
+
});
|
|
824
|
+
const result = await agent.runAgentTool(
|
|
825
|
+
agent.constructor as ChatCapableAgentClass,
|
|
826
|
+
{
|
|
827
|
+
input: {
|
|
828
|
+
...normalized,
|
|
829
|
+
runtimeKey: agent.loadedRuntimeKey,
|
|
830
|
+
config: runtimeConfig.value,
|
|
831
|
+
executionLevel: agent.executionLevel(),
|
|
832
|
+
},
|
|
833
|
+
runId: `temporary-agent:${runContext.toolCallId}`,
|
|
834
|
+
parentToolCallId: runContext.toolCallId,
|
|
835
|
+
signal: runContext.signal,
|
|
836
|
+
inputPreview: normalized.task,
|
|
837
|
+
display: {
|
|
838
|
+
name: normalized.subagentName,
|
|
839
|
+
displayName: normalized.subagentName,
|
|
840
|
+
kind: "temporary",
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
);
|
|
844
|
+
if (result.status !== "completed") {
|
|
845
|
+
throw new Error(
|
|
846
|
+
result.error ??
|
|
847
|
+
`temporary agent ended with status ${result.status}`,
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
if (
|
|
851
|
+
typeof result.summary !== "string" ||
|
|
852
|
+
!result.summary.trim()
|
|
853
|
+
) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
"temporary agent completed without final text",
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
return result.summary;
|
|
859
|
+
},
|
|
860
|
+
);
|
|
861
|
+
},
|
|
746
862
|
};
|
|
747
863
|
}
|
|
748
864
|
},
|
|
749
|
-
}[definition.
|
|
865
|
+
}[definition.name];
|
|
750
866
|
|
|
751
|
-
if (GeneratedRuntimeAgent.name !== definition.
|
|
867
|
+
if (GeneratedRuntimeAgent.name !== definition.name) {
|
|
752
868
|
throw new Error(
|
|
753
|
-
`Runtime Agent class name mismatch: expected "${definition.
|
|
869
|
+
`Runtime Agent class name mismatch: expected "${definition.name}", got "${GeneratedRuntimeAgent.name}"`,
|
|
754
870
|
);
|
|
755
871
|
}
|
|
756
872
|
|
|
@@ -758,6 +874,8 @@ export function defineRuntimeAgent<
|
|
|
758
874
|
// Apply the same decorator function after class-name inference instead.
|
|
759
875
|
const callableContext = {} as ClassMethodDecoratorContext;
|
|
760
876
|
callable()(GeneratedRuntimeAgent.prototype.reloadRuntime, callableContext);
|
|
877
|
+
callable()(GeneratedRuntimeAgent.prototype.updateConfig, callableContext);
|
|
878
|
+
callable()(GeneratedRuntimeAgent.prototype.getRuntimeAssembly, callableContext);
|
|
761
879
|
callable()(GeneratedRuntimeAgent.prototype.dispatchMessage, callableContext);
|
|
762
880
|
callable()(
|
|
763
881
|
GeneratedRuntimeAgent.prototype.steerQueuedSubmission,
|
|
@@ -767,21 +885,17 @@ export function defineRuntimeAgent<
|
|
|
767
885
|
GeneratedRuntimeAgent.prototype.cancelSubmissionById,
|
|
768
886
|
callableContext,
|
|
769
887
|
);
|
|
770
|
-
callable()(GeneratedRuntimeAgent.prototype.stopTurn, callableContext);
|
|
771
888
|
callable()(
|
|
772
889
|
GeneratedRuntimeAgent.prototype.respondToolInteraction,
|
|
773
890
|
callableContext,
|
|
774
891
|
);
|
|
775
|
-
callable()(GeneratedRuntimeAgent.prototype.
|
|
776
|
-
if (definition.config.update) {
|
|
777
|
-
callable()(GeneratedRuntimeAgent.prototype.updateConfig, callableContext);
|
|
778
|
-
} else {
|
|
779
|
-
delete (GeneratedRuntimeAgent.prototype as {
|
|
780
|
-
updateConfig?: unknown;
|
|
781
|
-
}).updateConfig;
|
|
782
|
-
}
|
|
892
|
+
callable()(GeneratedRuntimeAgent.prototype.stopTurn, callableContext);
|
|
783
893
|
|
|
784
|
-
return GeneratedRuntimeAgent as RuntimeAgentClass<
|
|
894
|
+
return GeneratedRuntimeAgent as unknown as RuntimeAgentClass<
|
|
895
|
+
Env,
|
|
896
|
+
Command,
|
|
897
|
+
Change
|
|
898
|
+
>;
|
|
785
899
|
}
|
|
786
900
|
|
|
787
901
|
// #endregion
|