@springbrand/agent-runtime 0.1.3-alpha.0 → 0.1.3-alpha.2
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 +1 -1
- package/src/db/schema.ts +10 -1
- package/src/db/submission.repo.ts +34 -1
- package/src/index.ts +9 -11
- package/src/kernel/bindings.ts +27 -6
- package/src/kernel/extensions.ts +1 -1
- package/src/kernel/profile.ts +3 -4
- package/src/kernel/recoverable-chat-agent.ts +82 -4
- package/src/kernel/state.ts +4 -0
- package/src/kernel/submission-lifecycle.ts +3 -2
- package/src/layers/orchestration/temporary-agent/runner.ts +0 -65
- package/src/lib/prompt.ts +1 -1
- package/src/pi/assembly/context.ts +1 -3
- package/src/pi/assembly/snapshot.ts +17 -9
- package/src/pi/runtime-adapter/assembly.ts +25 -86
- package/src/pi/runtime-adapter/execution.ts +107 -12
- package/src/pi/runtime-adapter/index.ts +8 -3
- package/src/pi/runtime-adapter/models.ts +221 -29
- package/src/pi/runtime-adapter/transcript.ts +61 -3
- package/src/pi/tool/ai-adapter.ts +58 -1
- package/src/pi/tool/base.ts +112 -2
- package/src/pi/tool/compiler.ts +5 -32
- package/src/pi/tool/core-host.ts +28 -30
- package/src/pi/tool/core.ts +25 -122
- package/src/pi/tool/mcp.ts +2 -2
- package/src/pi/tool/schedule.ts +46 -2
- package/src/pi/tool/skill.ts +112 -420
- package/src/pi/tool/web-fetch.ts +282 -0
- package/src/pi/tool/web-search/api.ts +34 -18
- package/src/pi/tool/workspace-sandbox.ts +92 -258
- package/src/plugins.ts +358 -531
- package/src/runtime.ts +195 -44
package/package.json
CHANGED
package/src/db/schema.ts
CHANGED
|
@@ -21,7 +21,9 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
|
|
|
21
21
|
assembly_revision TEXT NOT NULL,
|
|
22
22
|
assembly_descriptor TEXT NOT NULL,
|
|
23
23
|
assistant_message_id TEXT NOT NULL,
|
|
24
|
-
abort_reason TEXT
|
|
24
|
+
abort_reason TEXT,
|
|
25
|
+
recovery_error_count INTEGER NOT NULL DEFAULT 0,
|
|
26
|
+
recovery_reason TEXT
|
|
25
27
|
)`;
|
|
26
28
|
const submissionColumns = new Set(
|
|
27
29
|
sql<{ name: string }>`PRAGMA table_info(pi_submissions)`
|
|
@@ -39,6 +41,13 @@ export function initializeSchema(sql: SqlTaggedTemplate): void {
|
|
|
39
41
|
if (!submissionColumns.has("regenerate_message_id")) {
|
|
40
42
|
sql`ALTER TABLE pi_submissions ADD COLUMN regenerate_message_id TEXT`;
|
|
41
43
|
}
|
|
44
|
+
if (!submissionColumns.has("recovery_error_count")) {
|
|
45
|
+
sql`ALTER TABLE pi_submissions
|
|
46
|
+
ADD COLUMN recovery_error_count INTEGER NOT NULL DEFAULT 0`;
|
|
47
|
+
}
|
|
48
|
+
if (!submissionColumns.has("recovery_reason")) {
|
|
49
|
+
sql`ALTER TABLE pi_submissions ADD COLUMN recovery_reason TEXT`;
|
|
50
|
+
}
|
|
42
51
|
sql`CREATE UNIQUE INDEX IF NOT EXISTS pi_submissions_one_running
|
|
43
52
|
ON pi_submissions(status)
|
|
44
53
|
WHERE status = 'running'`;
|
|
@@ -9,6 +9,9 @@ export type SubmissionStatus =
|
|
|
9
9
|
| "aborted"
|
|
10
10
|
| "skipped"
|
|
11
11
|
| "error";
|
|
12
|
+
export type SubmissionRecoveryReason =
|
|
13
|
+
| "no_meaningful_model_progress"
|
|
14
|
+
| "transient_model_error";
|
|
12
15
|
|
|
13
16
|
const TERMINAL_SUBMISSION_STATUSES: ReadonlySet<SubmissionStatus> = new Set([
|
|
14
17
|
"completed",
|
|
@@ -43,6 +46,8 @@ export interface StoredSubmission {
|
|
|
43
46
|
queuedUiMessageJson: string | null;
|
|
44
47
|
userMessageId: string | null;
|
|
45
48
|
regenerateMessageId: string | null;
|
|
49
|
+
recoveryErrorCount: number;
|
|
50
|
+
recoveryReason: SubmissionRecoveryReason | null;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
export interface NewSubmission {
|
|
@@ -76,6 +81,8 @@ type SubmissionRow = {
|
|
|
76
81
|
queued_ui_message_json: string | null;
|
|
77
82
|
user_message_id: string | null;
|
|
78
83
|
regenerate_message_id: string | null;
|
|
84
|
+
recovery_error_count: number;
|
|
85
|
+
recovery_reason: string | null;
|
|
79
86
|
};
|
|
80
87
|
|
|
81
88
|
// #endregion
|
|
@@ -101,6 +108,8 @@ function mapRow(row: SubmissionRow): StoredSubmission {
|
|
|
101
108
|
queuedUiMessageJson: row.queued_ui_message_json,
|
|
102
109
|
userMessageId: row.user_message_id,
|
|
103
110
|
regenerateMessageId: row.regenerate_message_id,
|
|
111
|
+
recoveryErrorCount: row.recovery_error_count,
|
|
112
|
+
recoveryReason: row.recovery_reason as SubmissionRecoveryReason | null,
|
|
104
113
|
};
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -121,7 +130,7 @@ export class SubmissionRepository {
|
|
|
121
130
|
error, created_at, completed_at, assembly_revision,
|
|
122
131
|
assembly_descriptor, assistant_message_id, abort_reason,
|
|
123
132
|
queued_input_json, queued_ui_message_json, user_message_id,
|
|
124
|
-
regenerate_message_id
|
|
133
|
+
regenerate_message_id, recovery_error_count, recovery_reason
|
|
125
134
|
FROM pi_submissions
|
|
126
135
|
WHERE submission_id = ${id}
|
|
127
136
|
`[0];
|
|
@@ -309,6 +318,30 @@ export class SubmissionRepository {
|
|
|
309
318
|
`;
|
|
310
319
|
}
|
|
311
320
|
|
|
321
|
+
// chatRecovery 遇到 partial 会重置 attempt;Submission 计数保证同一 Turn 的可恢复模型错误仍绝对封顶。
|
|
322
|
+
incrementRecoveryErrorCount(
|
|
323
|
+
id: string,
|
|
324
|
+
reason: SubmissionRecoveryReason,
|
|
325
|
+
): number {
|
|
326
|
+
return this.sql<{ recovery_error_count: number }>`
|
|
327
|
+
UPDATE pi_submissions
|
|
328
|
+
SET recovery_error_count = recovery_error_count + 1,
|
|
329
|
+
recovery_reason = ${reason}
|
|
330
|
+
WHERE submission_id = ${id}
|
|
331
|
+
AND status IN ('pending', 'running')
|
|
332
|
+
RETURNING recovery_error_count
|
|
333
|
+
`[0]?.recovery_error_count ?? 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
clearRecoveryReason(id: string): void {
|
|
337
|
+
this.sql`
|
|
338
|
+
UPDATE pi_submissions
|
|
339
|
+
SET recovery_reason = NULL
|
|
340
|
+
WHERE submission_id = ${id}
|
|
341
|
+
AND status IN ('pending', 'running')
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
|
|
312
345
|
// 返回所有标准会话消息到 Submission 的关联映射。
|
|
313
346
|
// Transcript.storedMessages 读取 Pi Session 分支时调用,它用 Map 为每条消息补回 submissionId。
|
|
314
347
|
// 关联表由 Pi Session 存储拥有,本方法只读不写;不能在 RuntimeDatabase.clearAll 中单独删它而破坏 Transcript 的所有权。
|
package/src/index.ts
CHANGED
|
@@ -23,11 +23,9 @@
|
|
|
23
23
|
* - `canonical transcript` 是 Pi 持久化和恢复 Turn 时使用的权威消息历史。
|
|
24
24
|
* - `admission` 是提示进入 Turn 前的接纳、去重和状态登记流程。
|
|
25
25
|
* - `AgentConfig` 是应用交给 Runtime 的一次完整装配说明。
|
|
26
|
-
* - `AgentPlugin`
|
|
27
|
-
* - `PluginKind`
|
|
28
|
-
* - `
|
|
29
|
-
* - `prepare` 执行 `loader`,但不会改动正在运行的 Runtime。
|
|
30
|
-
* - `RuntimeContributionContext` 是 Plugin 写入候选配置的受限接口。
|
|
26
|
+
* - `AgentPlugin` 是一种能力的声明式准备单元。
|
|
27
|
+
* - `PluginKind` 同时收窄 Plugin 可以准备的字段,并作为内部稳定合并顺序。
|
|
28
|
+
* - `prepare` 读取业务数据或外部绑定,但不会改动正在运行的 Runtime。
|
|
31
29
|
* - `Port` 是 Runtime 调用外部能力时依赖的最小接口。
|
|
32
30
|
* - `RuntimeBindings` 是本次装配选中的 Port 和可执行对象集合。
|
|
33
31
|
* - `RuntimeBuilder` 是包内实现,用来校验贡献并生成候选结果。
|
|
@@ -40,8 +38,7 @@
|
|
|
40
38
|
*
|
|
41
39
|
* Snapshot 不允许替换已选集合,但不承诺把每个 Port 的内部对象深冻结。
|
|
42
40
|
*
|
|
43
|
-
* 正常调用顺序是 `
|
|
44
|
-
* 待确认:当前实现先校验 Plugin kind,再在 `prepare` 中调用 `loader`;这里的阶段简写是否仍应作为对外文档保留,需要与实际命名统一。
|
|
41
|
+
* 正常调用顺序是 `prepare → merge → validate → freeze → guard → commit`。
|
|
45
42
|
*
|
|
46
43
|
* 应用只创建 `AgentConfig` 和 Plugin。
|
|
47
44
|
*
|
|
@@ -62,11 +59,13 @@ export type {
|
|
|
62
59
|
AgentPlugin,
|
|
63
60
|
AgentPluginSpec,
|
|
64
61
|
PluginKind,
|
|
65
|
-
|
|
62
|
+
PluginPreparation,
|
|
66
63
|
PreparedPlugin,
|
|
67
|
-
RuntimeContributionContext,
|
|
68
64
|
RuntimeDegradation,
|
|
65
|
+
RuntimeExtensionContribution,
|
|
69
66
|
RuntimeProfileContribution,
|
|
67
|
+
RuntimeSkillContribution,
|
|
68
|
+
RuntimeToolSurfacePolicy,
|
|
70
69
|
} from "./plugins";
|
|
71
70
|
export type { ModelOption } from "./lib/model-catalog";
|
|
72
71
|
export {
|
|
@@ -105,7 +104,7 @@ export {
|
|
|
105
104
|
browserQuickActionPiToolCandidates,
|
|
106
105
|
} from "./pi/tool";
|
|
107
106
|
export {
|
|
108
|
-
|
|
107
|
+
createWorkspaceCodeExecutionPort,
|
|
109
108
|
} from "./pi/tool";
|
|
110
109
|
export {
|
|
111
110
|
assemblePiExtensions,
|
|
@@ -119,7 +118,6 @@ export type {
|
|
|
119
118
|
TemporaryAgentRunContext,
|
|
120
119
|
} from "./layers/orchestration/temporary-agent/core";
|
|
121
120
|
export {
|
|
122
|
-
bridgeTemporaryAgentToolApprovals,
|
|
123
121
|
temporaryAgentExtensionIsSafe,
|
|
124
122
|
temporaryAgentToolAllowed,
|
|
125
123
|
} from "./layers/orchestration/temporary-agent/runner";
|
package/src/kernel/bindings.ts
CHANGED
|
@@ -186,6 +186,17 @@ export interface WorkspacePort {
|
|
|
186
186
|
glob(pattern: string): Promise<WorkspaceFileInfo[]>;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* 向 Runtime 提供已组装的 Workspace Code Mode 执行能力。
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* Host Adapter 负责绑定 Durable Object、Worker Loader、网络出口和
|
|
194
|
+
* Workspace;Tool Surface 只把这个已授权 Port 转成 `execute` Tool。
|
|
195
|
+
*/
|
|
196
|
+
export interface RuntimeCodeExecutionPort {
|
|
197
|
+
execute(input: { code: string }): Promise<unknown>;
|
|
198
|
+
}
|
|
199
|
+
|
|
189
200
|
/**
|
|
190
201
|
* 汇总一个已限定作用域的 Workspace 用量。
|
|
191
202
|
*
|
|
@@ -283,9 +294,7 @@ export interface RuntimeMemoryPort {
|
|
|
283
294
|
* 按标签覆盖一块热记忆。
|
|
284
295
|
*
|
|
285
296
|
* @remarks
|
|
286
|
-
* Host
|
|
287
|
-
*
|
|
288
|
-
* TODO(待确认): 确认后续写入方是否仍需通过这个公开 Port 提交热记忆。
|
|
297
|
+
* `set_context` 在校验标签和 token 预算后调用,Host 负责按当前 Agent 与会话持久化。
|
|
289
298
|
*/
|
|
290
299
|
set(label: string, content: string): Promise<void>;
|
|
291
300
|
}
|
|
@@ -540,6 +549,14 @@ export interface RuntimeScheduleUpdate {
|
|
|
540
549
|
tz?: string;
|
|
541
550
|
}
|
|
542
551
|
|
|
552
|
+
/** 定时任务可切换到的已授权 User Agent 摘要。 */
|
|
553
|
+
export interface RuntimeScheduleAgentSummary {
|
|
554
|
+
id: string;
|
|
555
|
+
name: string;
|
|
556
|
+
description: string | null;
|
|
557
|
+
isDefault: boolean;
|
|
558
|
+
}
|
|
559
|
+
|
|
543
560
|
/**
|
|
544
561
|
* 让 Runtime 通过 Host 管理当前用户的定时任务。
|
|
545
562
|
*
|
|
@@ -573,6 +590,10 @@ export interface RuntimeSchedulePort {
|
|
|
573
590
|
): Promise<{ ok: boolean }>;
|
|
574
591
|
pause(id: string): Promise<{ ok: boolean }>;
|
|
575
592
|
resume(id: string): Promise<{ ok: boolean }>;
|
|
593
|
+
/** 列出当前用户可用于执行定时任务的 User Agent。 */
|
|
594
|
+
listAgents(): Promise<RuntimeScheduleAgentSummary[]>;
|
|
595
|
+
/** 更换一条定时任务的执行 Agent。 */
|
|
596
|
+
changeAgent(id: string, userAgentId: string): Promise<{ ok: boolean }>;
|
|
576
597
|
/**
|
|
577
598
|
* 按 ID 取消一条定时任务。
|
|
578
599
|
*
|
|
@@ -786,7 +807,7 @@ export interface RuntimeModelEndpoint {
|
|
|
786
807
|
* 向 Runtime 提供本次装配可用的模型端点。
|
|
787
808
|
*
|
|
788
809
|
* @remarks
|
|
789
|
-
* Provider Plugin
|
|
810
|
+
* Provider Plugin 准备它,Builder 在提交前校验,Pi Adapter 在激活与每次解析模型时读取。
|
|
790
811
|
*
|
|
791
812
|
* Runtime 只依赖已解析结果,不知道环境变量或业务模型配置的来源。
|
|
792
813
|
*/
|
|
@@ -800,7 +821,7 @@ export interface RuntimeProviderPort {
|
|
|
800
821
|
* 向 Runtime 提供 Cloudflare 执行平台上的可授权能力。
|
|
801
822
|
*
|
|
802
823
|
* @remarks
|
|
803
|
-
* Platform Plugin
|
|
824
|
+
* Platform Plugin 准备它,Workspace Codemode、Browser 工具、遥测和工具门卫按需使用。
|
|
804
825
|
*
|
|
805
826
|
* Worker Loader 与网络出口由 Host 选择,使 Dynamic Worker 只获得已授权绑定;术语见 `../index.ts`。
|
|
806
827
|
*/
|
|
@@ -853,7 +874,7 @@ export interface RuntimeSkillBindings {
|
|
|
853
874
|
* 限定一个 Skill 脚本可使用的网络、Workspace 和工具。
|
|
854
875
|
*
|
|
855
876
|
* @remarks
|
|
856
|
-
* Skill Plugin
|
|
877
|
+
* Skill Plugin 在准备来源时设置,Pi Skill 工具在决定是否注入脚本执行能力时读取。
|
|
857
878
|
*
|
|
858
879
|
* 这是已授权结果,执行期不应为某个 Skill 自动放宽。
|
|
859
880
|
*/
|
package/src/kernel/extensions.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface RuntimeExtensionContextDefinition {
|
|
|
43
43
|
* 把 Extension 的 manifest 与待加载源码放在同一份配置里。
|
|
44
44
|
*
|
|
45
45
|
* @remarks
|
|
46
|
-
* extension Plugin
|
|
46
|
+
* extension Plugin 在准备结果中提供它,Pi Extension 适配器在候选 Runtime 装配时加载。
|
|
47
47
|
*
|
|
48
48
|
* manifest 与源码一起传递,可避免授权信息与实际执行代码在不同查找步骤中错位。
|
|
49
49
|
*
|
package/src/kernel/profile.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type ThinkingEffort =
|
|
|
22
22
|
* 保存一次装配选定的内存开关与上下文预算。
|
|
23
23
|
*
|
|
24
24
|
* @remarks
|
|
25
|
-
* memory Plugin
|
|
25
|
+
* memory Plugin 在准备结果中提供它,Runtime 在构建上下文时读取。
|
|
26
26
|
*
|
|
27
27
|
* 预算与开关放在同一个 Profile 中,确保一次 Snapshot 只使用一套内存配置。
|
|
28
28
|
*
|
|
@@ -32,14 +32,13 @@ export interface RuntimeMemoryProfile {
|
|
|
32
32
|
enabled: boolean;
|
|
33
33
|
memoryTokens: number;
|
|
34
34
|
preferencesTokens: number;
|
|
35
|
-
compactAfterTokens: number;
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
38
|
* 指明本次装配允许连接的一个 MCP 服务。
|
|
40
39
|
*
|
|
41
40
|
* @remarks
|
|
42
|
-
* connector Plugin
|
|
41
|
+
* connector Plugin 在准备结果中加入它,Pi 工具装配只选择 Host 已就绪且 URL 匹配的连接。
|
|
43
42
|
*
|
|
44
43
|
* 这里只保留名称和 URL;连接生命周期、凭据与重试仍由 Host 管理。
|
|
45
44
|
*
|
|
@@ -71,7 +70,7 @@ export interface RuntimeDenyPolicy {
|
|
|
71
70
|
* 保存一次装配中真正会影响执行的参数。
|
|
72
71
|
*
|
|
73
72
|
* @remarks
|
|
74
|
-
* `RuntimeBuilder` 在所有 Plugin
|
|
73
|
+
* `RuntimeBuilder` 在所有 Plugin 声明合并完成后生成它,Kernel 只通过已提交的 Snapshot 读取。
|
|
75
74
|
*
|
|
76
75
|
* 它不包含业务身份、归属键或存储地址,因为这些选择属于 Host,不应被冻结成执行参数。
|
|
77
76
|
*
|
|
@@ -73,15 +73,15 @@ export type RuntimeRecoveryDecision<Data> =
|
|
|
73
73
|
| { readonly kind: "ignore"; readonly reason: string };
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
*
|
|
76
|
+
* 记录一次已执行恢复的结果,或表明该恢复已接力给下一次持久调度。
|
|
77
77
|
*
|
|
78
78
|
* @remarks
|
|
79
|
-
* `_chatRecoveryRetry` 在调用端口的 `retry`
|
|
79
|
+
* `_chatRecoveryRetry` 在调用端口的 `retry` 后读取该结果;终态关闭事故,`scheduled` 保持事故活跃。
|
|
80
80
|
*
|
|
81
81
|
* 只有失败时才需要 `error`;结果不承载流内容,避免在调度数据与流缓冲区之间复制状态。
|
|
82
82
|
*/
|
|
83
83
|
export interface RuntimeRecoveryResult {
|
|
84
|
-
readonly status: "completed" | "failed";
|
|
84
|
+
readonly status: "completed" | "scheduled" | "failed";
|
|
85
85
|
readonly error?: string;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -103,7 +103,7 @@ export interface RuntimeRecoveryPort<Data> {
|
|
|
103
103
|
/**
|
|
104
104
|
* 使用已分类的业务数据重放一次对话。
|
|
105
105
|
*
|
|
106
|
-
* @remarks
|
|
106
|
+
* @remarks 持久调度回调会调用它;实现应返回权威终态,或明确说明下一次恢复已经持久调度。
|
|
107
107
|
*/
|
|
108
108
|
retry(data: Data): Promise<RuntimeRecoveryResult>;
|
|
109
109
|
/**
|
|
@@ -544,6 +544,7 @@ export abstract class RecoverableChatAgent<
|
|
|
544
544
|
this.activeRecoveryRootRequestId = data.requestId;
|
|
545
545
|
try {
|
|
546
546
|
const result = await this.recoveryPort().retry(data);
|
|
547
|
+
if (result.status === "scheduled") return;
|
|
547
548
|
await this.engine().updateIncident(
|
|
548
549
|
data.incidentId,
|
|
549
550
|
result.status === "completed" ? "completed" : "failed",
|
|
@@ -563,6 +564,83 @@ export abstract class RecoverableChatAgent<
|
|
|
563
564
|
}
|
|
564
565
|
}
|
|
565
566
|
|
|
567
|
+
/**
|
|
568
|
+
* 把活跃进程内检测到的模型流停滞交给同一套持久恢复预算。
|
|
569
|
+
*
|
|
570
|
+
* @remarks
|
|
571
|
+
* 子类在确认当前 Turn 可以从 durable checkpoint 重放后调用;`beforeSchedule`
|
|
572
|
+
* 必须先结束旧流并恢复权威消息快照,随后引擎才广播 recovering 状态。
|
|
573
|
+
*/
|
|
574
|
+
protected async scheduleChatRecoveryRetry(
|
|
575
|
+
data: RecoveryData,
|
|
576
|
+
beforeSchedule: () => void | Promise<void>,
|
|
577
|
+
): Promise<"disabled" | "scheduled" | "exhausted"> {
|
|
578
|
+
if (!resolveChatRecoveryConfig(this.chatRecovery).enabled) {
|
|
579
|
+
return "disabled";
|
|
580
|
+
}
|
|
581
|
+
const recoveryRootRequestId =
|
|
582
|
+
this.activeRecoveryRootRequestId ?? data.requestId;
|
|
583
|
+
const { incident, exhausted } = await this.engine().beginIncident({
|
|
584
|
+
requestId: data.requestId,
|
|
585
|
+
recoveryRootRequestId,
|
|
586
|
+
recoveryKind: "retry",
|
|
587
|
+
});
|
|
588
|
+
await beforeSchedule();
|
|
589
|
+
if (exhausted) {
|
|
590
|
+
await this.engine().exhaustRecoveryGiveUp({
|
|
591
|
+
callback: "_chatRecoveryRetry",
|
|
592
|
+
data: {
|
|
593
|
+
incidentId: incident.incidentId,
|
|
594
|
+
originalRequestId: recoveryRootRequestId,
|
|
595
|
+
},
|
|
596
|
+
reason: incident.reason ?? "max_attempts_exceeded",
|
|
597
|
+
});
|
|
598
|
+
return "exhausted";
|
|
599
|
+
}
|
|
600
|
+
await this.engine().scheduleRecovery({
|
|
601
|
+
incident,
|
|
602
|
+
recoveryKind: "retry",
|
|
603
|
+
callback: "_chatRecoveryRetry",
|
|
604
|
+
data: {
|
|
605
|
+
...data,
|
|
606
|
+
incidentId: incident.incidentId,
|
|
607
|
+
},
|
|
608
|
+
reason: this.activeRecoveryRootRequestId
|
|
609
|
+
? "stable_timeout_retry"
|
|
610
|
+
: "initial",
|
|
611
|
+
});
|
|
612
|
+
return "scheduled";
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/** 终态化一个 Turn 时取消同请求的恢复调度,并关闭恢复事故与客户端状态。 */
|
|
616
|
+
protected async settleChatRecovery(
|
|
617
|
+
requestId: string,
|
|
618
|
+
status: "completed" | "skipped" | "failed",
|
|
619
|
+
reason?: string,
|
|
620
|
+
): Promise<void> {
|
|
621
|
+
const incidentIds = new Set<string>();
|
|
622
|
+
try {
|
|
623
|
+
for (const schedule of await this.listSchedules()) {
|
|
624
|
+
if (schedule.callback !== "_chatRecoveryRetry") continue;
|
|
625
|
+
const payload = schedule.payload;
|
|
626
|
+
if (!payload || typeof payload !== "object") continue;
|
|
627
|
+
const recovery = payload as Partial<
|
|
628
|
+
RecoveryScheduleData<RecoveryData>
|
|
629
|
+
>;
|
|
630
|
+
if (recovery.requestId !== requestId) continue;
|
|
631
|
+
if (typeof recovery.incidentId === "string") {
|
|
632
|
+
incidentIds.add(recovery.incidentId);
|
|
633
|
+
}
|
|
634
|
+
await this.cancelSchedule(schedule.id);
|
|
635
|
+
}
|
|
636
|
+
for (const incidentId of incidentIds) {
|
|
637
|
+
await this.engine().updateIncident(incidentId, status, reason);
|
|
638
|
+
}
|
|
639
|
+
} finally {
|
|
640
|
+
await this.setRecovering(false, requestId);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
566
644
|
/**
|
|
567
645
|
* 把 Agents SDK 发现的孤立 Fiber 交给聊天恢复引擎。
|
|
568
646
|
*
|
package/src/kernel/state.ts
CHANGED
|
@@ -51,6 +51,10 @@ export interface RuntimeQueuedSubmission {
|
|
|
51
51
|
|
|
52
52
|
export interface RuntimeTurnState {
|
|
53
53
|
activeSubmissionId?: string;
|
|
54
|
+
activeRequestId?: string;
|
|
55
|
+
recoveryAttempt?: number;
|
|
56
|
+
recoveryMax?: number;
|
|
57
|
+
recoveryReason?: "no_meaningful_model_progress" | "transient_model_error";
|
|
54
58
|
steerable: boolean;
|
|
55
59
|
hasPendingSteer: boolean;
|
|
56
60
|
queued: RuntimeQueuedSubmission[];
|
|
@@ -587,13 +587,14 @@ export class SubmissionLifecycle<
|
|
|
587
587
|
return submission ? [submission.submissionId] : [];
|
|
588
588
|
})()
|
|
589
589
|
: (() => {
|
|
590
|
-
const submission = this.options.store.findRunning()
|
|
590
|
+
const submission = this.options.store.findRunning() ??
|
|
591
|
+
this.options.store.findNextPending();
|
|
591
592
|
return submission ? [submission.submissionId] : [];
|
|
592
593
|
})();
|
|
593
594
|
for (const submissionId of submissionIds) {
|
|
594
595
|
await this.cancel(submissionId, reason);
|
|
595
596
|
}
|
|
596
|
-
return { ok:
|
|
597
|
+
return { ok: submissionIds.length > 0 };
|
|
597
598
|
}
|
|
598
599
|
|
|
599
600
|
// #endregion
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { RuntimeExtensionConfig } from "../../../kernel/extensions";
|
|
2
|
-
import {
|
|
3
|
-
requiresExecutionApproval,
|
|
4
|
-
type ExecutionLevel,
|
|
5
|
-
} from "../../../lib/execution-level";
|
|
6
|
-
import type { PiToolCandidate } from "../../../pi/tool/compiler";
|
|
7
2
|
|
|
8
3
|
const BLOCKED_TOOLS = new Set([
|
|
9
4
|
"dispatch_background",
|
|
@@ -46,66 +41,6 @@ export function temporaryAgentToolAllowed(
|
|
|
46
41
|
);
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
/**
|
|
50
|
-
* 给临时 Agent 里需要审批的 Tool 包上父 Session 审批。
|
|
51
|
-
*
|
|
52
|
-
* @remarks
|
|
53
|
-
* 宿主在筛完 Tool 候选项后调用;返回的候选项可直接交给临时 Agent 的 Pi Tool 编译流程。
|
|
54
|
-
*
|
|
55
|
-
* 包装层先按父 Agent 的执行档位请求批准,然后才调原 `execute`;审批完成后把包装候选降为 `safe`,避免临时 Agent 重复审批。
|
|
56
|
-
*
|
|
57
|
-
* Runtime、Session 和 Tool 的术语见 `src/index.ts`。
|
|
58
|
-
*/
|
|
59
|
-
export function bridgeTemporaryAgentToolApprovals(
|
|
60
|
-
candidates: readonly PiToolCandidate[],
|
|
61
|
-
executionLevel: ExecutionLevel,
|
|
62
|
-
requestApproval: (request: {
|
|
63
|
-
toolName: string;
|
|
64
|
-
executionLevel: ExecutionLevel;
|
|
65
|
-
requiredExecutionLevel: ExecutionLevel;
|
|
66
|
-
input: unknown;
|
|
67
|
-
toolCallId: string;
|
|
68
|
-
signal: AbortSignal;
|
|
69
|
-
}) => Promise<void>,
|
|
70
|
-
): PiToolCandidate[] {
|
|
71
|
-
return candidates.map((candidate) => {
|
|
72
|
-
if (!requiresExecutionApproval(
|
|
73
|
-
executionLevel,
|
|
74
|
-
candidate.requiredExecutionLevel,
|
|
75
|
-
)) {
|
|
76
|
-
return candidate;
|
|
77
|
-
}
|
|
78
|
-
const execute = candidate.tool.execute;
|
|
79
|
-
return {
|
|
80
|
-
...candidate,
|
|
81
|
-
requiredExecutionLevel: "safe",
|
|
82
|
-
tool: {
|
|
83
|
-
...candidate.tool,
|
|
84
|
-
// 作用:在执行原 Tool 前向父 Session 请求审批。
|
|
85
|
-
// 调用:Pi Tool 执行器调用,参数和进度回调原样传给原 Tool。
|
|
86
|
-
// 原因:某些调用方不传 signal,但审批契约需要一个 `AbortSignal`,因此只在缺失时创建一个不会自行取消的信号。
|
|
87
|
-
async execute(toolCallId, input, signal, onUpdate) {
|
|
88
|
-
const activeSignal = signal ?? new AbortController().signal;
|
|
89
|
-
await requestApproval({
|
|
90
|
-
toolName: candidate.tool.name,
|
|
91
|
-
executionLevel,
|
|
92
|
-
requiredExecutionLevel: candidate.requiredExecutionLevel,
|
|
93
|
-
input,
|
|
94
|
-
toolCallId,
|
|
95
|
-
signal: activeSignal,
|
|
96
|
-
});
|
|
97
|
-
return execute(
|
|
98
|
-
toolCallId,
|
|
99
|
-
input,
|
|
100
|
-
activeSignal,
|
|
101
|
-
onUpdate,
|
|
102
|
-
);
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
44
|
/**
|
|
110
45
|
* 判断一个 Extension 是否安全到可以交给临时 Agent。
|
|
111
46
|
*
|
package/src/lib/prompt.ts
CHANGED
|
@@ -46,7 +46,7 @@ export const BEHAVIOR =
|
|
|
46
46
|
export const PLANNING =
|
|
47
47
|
"Planning: For work with 3 or more distinct steps, or any non-trivial / multi-file change, call " +
|
|
48
48
|
"update_plan with the complete plan (it fully replaces the previous one) so the user sees live " +
|
|
49
|
-
"progress. Mark a step in_progress before starting it and
|
|
49
|
+
"progress. Mark a step in_progress before starting it and done the moment it's done; keep only " +
|
|
50
50
|
"one step in_progress at a time and don't batch completions. Do not make a plan for a single trivial " +
|
|
51
51
|
"step or a purely conversational reply — just do it.";
|
|
52
52
|
|
|
@@ -116,7 +116,7 @@ export async function compactPiContext(input: Readonly<{
|
|
|
116
116
|
const settings = DEFAULT_COMPACTION_SETTINGS;
|
|
117
117
|
if (
|
|
118
118
|
!settings.enabled ||
|
|
119
|
-
estimateContextTokens(currentMessages).tokens
|
|
119
|
+
estimateContextTokens(currentMessages).tokens <
|
|
120
120
|
input.compactAfterTokens
|
|
121
121
|
) {
|
|
122
122
|
return {
|
|
@@ -479,7 +479,6 @@ export async function assemblePiSystemContext(input: Readonly<{
|
|
|
479
479
|
) => Promise<string | null>;
|
|
480
480
|
}>): Promise<Readonly<{
|
|
481
481
|
systemPrompt: string;
|
|
482
|
-
compactAfterTokens: number;
|
|
483
482
|
degradations: readonly RuntimeDegradation[];
|
|
484
483
|
}>> {
|
|
485
484
|
const [memory, extensions] = await Promise.all([
|
|
@@ -499,7 +498,6 @@ export async function assemblePiSystemContext(input: Readonly<{
|
|
|
499
498
|
|
|
500
499
|
return Object.freeze({
|
|
501
500
|
systemPrompt: parts.join("\n\n"),
|
|
502
|
-
compactAfterTokens: input.memory.compactAfterTokens,
|
|
503
501
|
degradations: Object.freeze([
|
|
504
502
|
...memory.degradations,
|
|
505
503
|
...extensions.degradations,
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
ThinkingLevel,
|
|
4
4
|
} from "@earendil-works/pi-agent-core";
|
|
5
5
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
6
|
+
import { clampReasoning } from "@earendil-works/pi-ai/api/simple-options";
|
|
6
7
|
import type {
|
|
7
8
|
RuntimeMcpServer,
|
|
8
9
|
RuntimeProfile,
|
|
@@ -16,13 +17,22 @@ import { assembleSystemPrompt } from "../../lib/prompt";
|
|
|
16
17
|
import { resolvePiModel } from "../runtime-adapter/models";
|
|
17
18
|
import type { PiToolCandidate } from "../tool/compiler";
|
|
18
19
|
|
|
20
|
+
/**
|
|
21
|
+
* 收集完动态 Extension/MCP 候选后,一次性生成最终 Tool Surface。
|
|
22
|
+
*/
|
|
23
|
+
export interface PiToolSurface {
|
|
24
|
+
finalize(
|
|
25
|
+
authorizedCandidates: readonly PiToolCandidate[],
|
|
26
|
+
): readonly PiToolCandidate[];
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
/** Immutable inputs consumed directly by Pi Agent Core. */
|
|
20
30
|
export interface PiRuntimeAssembly {
|
|
21
31
|
readonly model: Model<Api>;
|
|
22
32
|
readonly thinkingLevel: ThinkingLevel;
|
|
23
33
|
readonly systemPrompt: string;
|
|
24
34
|
readonly messages: readonly AgentMessage[];
|
|
25
|
-
readonly
|
|
35
|
+
readonly toolSurface: PiToolSurface;
|
|
26
36
|
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
27
37
|
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
28
38
|
}
|
|
@@ -34,7 +44,7 @@ interface PiRuntimeAssemblyInput {
|
|
|
34
44
|
>;
|
|
35
45
|
readonly provider: RuntimeProviderPort;
|
|
36
46
|
readonly messages?: readonly AgentMessage[];
|
|
37
|
-
readonly
|
|
47
|
+
readonly toolSurface: PiToolSurface;
|
|
38
48
|
readonly mcpServers: readonly RuntimeMcpServer[];
|
|
39
49
|
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
40
50
|
}
|
|
@@ -70,11 +80,13 @@ function freezeModel(model: Model<Api>): Model<Api> {
|
|
|
70
80
|
|
|
71
81
|
// 作用:把 Runtime 的思考强度名称转成 Pi Agent Core 接受的名称。
|
|
72
82
|
// 调用:创建 Runtime 装配快照时对 profile 中的 `thinking` 调用。
|
|
73
|
-
//
|
|
83
|
+
// 原因:`none` 映射成 `off`;xhigh/max 用 clampReasoning 夹到 API 真正支持的最高档,
|
|
84
|
+
// 避免超出范围的等级在 provider 侧静默失败。
|
|
74
85
|
function toPiThinkingLevel(
|
|
75
86
|
thinking: ThinkingEffort,
|
|
76
87
|
): ThinkingLevel {
|
|
77
|
-
|
|
88
|
+
if (thinking === "none") return "off";
|
|
89
|
+
return clampReasoning(thinking) ?? "off";
|
|
78
90
|
}
|
|
79
91
|
|
|
80
92
|
// 作用:复制并冻结一个 Extension 配置的 manifest 和权限集合。
|
|
@@ -172,15 +184,11 @@ export function createPiRuntimeAssembly(
|
|
|
172
184
|
input: PiRuntimeAssemblyInput,
|
|
173
185
|
): PiRuntimeAssembly {
|
|
174
186
|
const messages = [...(input.messages ?? [])];
|
|
175
|
-
const toolCandidates = input.toolCandidates.map(
|
|
176
|
-
(candidate) => Object.freeze({ ...candidate }),
|
|
177
|
-
);
|
|
178
187
|
const mcpServers = input.mcpServers.map(
|
|
179
188
|
(server) => Object.freeze({ ...server }),
|
|
180
189
|
);
|
|
181
190
|
const extensions = input.extensions.map(freezeExtension);
|
|
182
191
|
Object.freeze(messages);
|
|
183
|
-
Object.freeze(toolCandidates);
|
|
184
192
|
Object.freeze(mcpServers);
|
|
185
193
|
Object.freeze(extensions);
|
|
186
194
|
|
|
@@ -193,7 +201,7 @@ export function createPiRuntimeAssembly(
|
|
|
193
201
|
input.profile.systemPrompt,
|
|
194
202
|
),
|
|
195
203
|
messages,
|
|
196
|
-
|
|
204
|
+
toolSurface: input.toolSurface,
|
|
197
205
|
mcpServers,
|
|
198
206
|
extensions,
|
|
199
207
|
});
|