@springbrand/agent-runtime 0.2.0-alpha.72 → 0.3.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/{agent-tool-runtime.ts → adapter/cloudflare/agent-tool.ts} +1 -1
- package/src/adapter/cloudflare/agent.ts +1682 -0
- package/src/{db/index.ts → adapter/cloudflare/database.ts} +18 -55
- package/src/{runtime-agent.ts → adapter/cloudflare/define-agent.ts} +37 -42
- package/src/adapter/cloudflare/dynamic-worker-observability.ts +1 -1
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +4 -4
- package/src/adapter/cloudflare/sandbox/adapter.ts +1 -1
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/{db → adapter/cloudflare}/schema.ts +28 -80
- package/src/adapter/cloudflare/subagent/definition.ts +63 -33
- package/src/adapter/cloudflare/subagent/runner.ts +174 -120
- package/src/adapter/cloudflare/subagent/tools.ts +13 -14
- package/src/adapter/cloudflare/universal-agent/preparation.ts +7 -7
- package/src/adapter/cloudflare/universal-agent/tools.ts +10 -12
- package/src/adapter/cloudflare/workspace/git-fs.ts +1 -1
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +1 -1
- package/src/adapter/cloudflare/workspace/version-control.ts +2 -2
- package/src/config/activation.ts +140 -0
- package/src/{runtime-agent-context.ts → config/agent-context.ts} +3 -3
- package/src/{runtime-assembler.ts → config/assembler.ts} +45 -46
- package/src/{kernel/runtime-assembly.ts → config/assembly-projection.ts} +4 -4
- package/src/{pi/assembly/snapshot.ts → config/assembly-snapshot.ts} +6 -6
- package/src/{runtime-definition.ts → config/definition.ts} +13 -13
- package/src/config/harness-runtime.ts +190 -0
- package/src/config/index.ts +14 -0
- package/src/config/model-guidance.ts +21 -0
- package/src/{pi/runtime-adapter/assembly.ts → config/pinning.ts} +60 -19
- package/src/{kernel/bindings.ts → config/ports.ts} +32 -16
- package/src/{kernel → config}/profile.ts +7 -0
- package/src/{kernel → config}/public-contracts.ts +6 -6
- package/src/{kernel → config}/runtime-load.ts +1 -1
- package/src/{pi/assembly/context.ts → context/assembly.ts} +5 -207
- package/src/{pi/assembly/extensions.ts → context/extension/assembly.ts} +5 -5
- package/src/context/extension/index.ts +3 -0
- package/src/context/index.ts +6 -0
- package/src/{layers/context → context/memory}/budget/gate.ts +21 -5
- package/src/conversation/harness-resume.ts +288 -0
- package/src/conversation/harness-stream.ts +99 -0
- package/src/{pi/runtime-adapter/transcript.ts → conversation/history.ts} +206 -233
- package/src/conversation/index.ts +6 -0
- package/src/{pi → conversation}/message/contract.ts +3 -0
- package/src/{pi → conversation}/message/conversion.ts +75 -0
- package/src/{pi → conversation}/message/index.ts +1 -0
- package/src/{pi → conversation}/message/model-input.ts +1 -1
- package/src/{pi → conversation}/message/projection.ts +113 -30
- package/src/{db/message-ui.repo.ts → conversation/repository.ts} +4 -0
- package/src/conversation/state.ts +196 -0
- package/src/harness/index.ts +7 -0
- package/src/harness/session-harness.ts +562 -0
- package/src/harness/submission-operation.ts +49 -0
- package/src/index.ts +38 -43
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/mcp.ts +1 -1
- package/src/lib/prompt.ts +7 -6
- package/src/lib/token-estimate.ts +8 -0
- package/src/{pi/runtime-adapter/models.ts → models/index.ts} +64 -17
- package/src/{pi/turn/tool-recovery.ts → session/cloudflare/legacy-tool-recovery.ts} +33 -525
- package/src/{pi/session → session/cloudflare}/migration.ts +404 -14
- package/src/{pi/session → session/cloudflare}/storage.ts +8 -36
- package/src/session/index.ts +39 -0
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +286 -0
- package/src/{kernel/receipts.ts → submission/contracts.ts} +56 -2
- package/src/submission/delivery.ts +212 -0
- package/src/submission/finalization.ts +111 -0
- package/src/submission/index.ts +9 -0
- package/src/{kernel/submission-lifecycle.ts → submission/lifecycle.ts} +41 -28
- package/src/{db/submission.repo.ts → submission/repository.ts} +117 -114
- package/src/submission/steer-delivery.ts +167 -0
- package/src/{db/steer.repo.ts → submission/steer-repository.ts} +27 -5
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/contract.ts +12 -0
- package/src/telemetry/coordinator.ts +65 -0
- package/src/telemetry/delivery.ts +2 -2
- package/src/telemetry/index.ts +5 -0
- package/src/{model-usage.ts → telemetry/model-usage.ts} +1 -1
- package/src/telemetry/recorder.ts +1 -1
- package/src/telemetry/runtime-telemetry.ts +12 -0
- package/src/{kernel/durable-lifecycle.ts → telemetry/subagent-lifecycle.ts} +1 -1
- package/src/telemetry/turn-events.ts +279 -0
- package/src/tools/README.md +44 -0
- package/src/{pi/tool/ai-adapter.ts → tools/adapters/ai-to-pi.ts} +6 -9
- package/src/{pi/tool → tools/adapters}/declared.ts +6 -2
- package/src/{pi/tool/nested-tools.ts → tools/adapters/pi-to-ai.ts} +2 -2
- package/src/tools/assistant/ask-user.ts +317 -0
- package/src/tools/assistant/candidate.ts +15 -0
- package/src/tools/assistant/followups.ts +34 -0
- package/src/tools/assistant/index.ts +19 -0
- package/src/tools/assistant/memory.ts +73 -0
- package/src/tools/assistant/plan.ts +98 -0
- package/src/{pi/tool → tools/assistant}/time.ts +1 -1
- package/src/{pi/tool/core-host.ts → tools/codemode/adapter.ts} +50 -49
- package/src/{pi/tool/core.ts → tools/codemode/index.ts} +35 -91
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +32 -0
- package/src/{pi/tool → tools/mcp}/gateway.ts +19 -19
- package/src/{pi/tool/mcp.ts → tools/mcp/index.ts} +5 -5
- package/src/tools/runtime/approval/index.ts +1 -0
- package/src/{db/approval.repo.ts → tools/runtime/approval/repository.ts} +22 -41
- package/src/tools/runtime/compiler.ts +264 -0
- package/src/tools/runtime/contracts.ts +117 -0
- package/src/tools/runtime/decision-gate.ts +337 -0
- package/src/tools/runtime/human-decisions.ts +127 -0
- package/src/tools/runtime/interaction/index.ts +1 -0
- package/src/{db/interaction.repo.ts → tools/runtime/interaction/repository.ts} +1 -7
- package/src/tools/runtime/result.ts +22 -0
- package/src/tools/runtime/unattended-error.ts +4 -0
- package/src/tools/sandbox/index.ts +228 -0
- package/src/tools/schedule/index.ts +301 -0
- package/src/tools/skill/contracts.ts +20 -0
- package/src/tools/skill/index.ts +99 -0
- package/src/tools/skill/resources.ts +322 -0
- package/src/tools/skill/scripts.ts +168 -0
- package/src/{kernel/submission-authority.ts → tools/subagent/authority.ts} +31 -31
- package/src/{pi/tool/subagent.ts → tools/subagent/index.ts} +33 -36
- package/src/{db/agent-tool.repo.ts → tools/subagent/repository.ts} +1 -1
- package/src/{kernel/subagent-runtime.ts → tools/subagent/runtime.ts} +1 -1
- package/src/tools/subagent/temporary/approvals.ts +123 -0
- package/src/{layers/orchestration/temporary-agent/core.ts → tools/subagent/temporary/coordinator.ts} +41 -49
- package/src/tools/subagent/temporary/core.ts +53 -0
- package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/runner.ts +1 -1
- package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/workspace.ts +1 -4
- package/src/{pi/tool/web-fetch.ts → tools/web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1320 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/{pi/tool/web-search/web-search.ts → tools/web/search/search.ts} +5 -7
- package/src/{pi/tool/workspace-sandbox.ts → tools/workspace/index.ts} +12 -272
- package/src/{pi/tool/workspace-revision.ts → tools/workspace/revision.ts} +2 -2
- package/src/db/milestone.repo.ts +0 -104
- package/src/db/settlement.repo.ts +0 -104
- package/src/kernel/approval-lifecycle.ts +0 -638
- package/src/kernel/interaction-lifecycle.ts +0 -433
- package/src/kernel/recoverable-chat-agent.ts +0 -1020
- package/src/kernel/state.ts +0 -91
- package/src/pi/assembly/index.ts +0 -19
- package/src/pi/runtime-adapter/execution.ts +0 -1163
- package/src/pi/runtime-adapter/index.ts +0 -293
- package/src/pi/runtime-adapter/recovery.ts +0 -982
- package/src/pi/session/index.ts +0 -24
- package/src/pi/tool/base.ts +0 -550
- package/src/pi/tool/compiler.ts +0 -483
- package/src/pi/tool/index.ts +0 -38
- package/src/pi/tool/schedule.ts +0 -261
- package/src/pi/tool/skill.ts +0 -565
- package/src/pi/tool/web-search/api.ts +0 -1318
- package/src/pi/tool/web-search/index.ts +0 -2
- package/src/pi/tool/workspace-state-connector.ts +0 -146
- package/src/pi/turn/approval.ts +0 -181
- package/src/pi/turn/index.ts +0 -82
- package/src/pi/turn/interaction.ts +0 -181
- package/src/runtime.ts +0 -4760
- package/src/{kernel/runtime-assembly-view.ts → config/assembly-view.ts} +0 -0
- package/src/{kernel → config}/degradation.ts +0 -0
- package/src/{kernel → config}/runtime-config.ts +0 -0
- package/src/{kernel/extensions.ts → context/extension/contracts.ts} +0 -0
- package/src/{db/ext-context.repo.ts → context/extension/repository.ts} +0 -0
- package/src/{pi/runtime-adapter/openrouter-messages.ts → models/openrouter/messages.ts} +0 -0
- package/src/{pi/runtime-adapter → models/openrouter}/test-fixtures/openrouter-tool-search.sse +0 -0
- package/src/{db/submission-admission.repo.ts → submission/admission-repository.ts} +0 -0
- package/src/{db/telemetry-outbox.repo.ts → telemetry/repository.ts} +0 -0
- package/src/{db/runtime-event-outbox.repo.ts → telemetry/runtime-event-repository.ts} +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/contract.ts +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/extract/index.ts +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/fanout/index.ts +0 -0
- package/src/{layers/orchestration/subagents/agent-types → tools/subagent/types}/registry.ts +1 -1
- /package/src/{workspace-versioning.ts → tools/workspace/versioning.ts} +0 -0
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isTerminalSubmissionStatus,
|
|
3
|
-
type RuntimeDatabase,
|
|
4
|
-
type StoredToolInteraction,
|
|
5
|
-
type SubmissionStatus,
|
|
6
|
-
} from "../db";
|
|
7
|
-
import type {
|
|
8
|
-
PiDurableMutation,
|
|
9
|
-
PiRecoveryCommand,
|
|
10
|
-
PiRecoveryDecision,
|
|
11
|
-
PiRuntimeAdapter,
|
|
12
|
-
PiToolInteraction,
|
|
13
|
-
PiToolInteractionCancelReason,
|
|
14
|
-
} from "../pi/runtime-adapter";
|
|
15
|
-
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
16
|
-
|
|
17
|
-
// #region 类型约定
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 提供 interaction 流程定位一次持久化提交所需的最小信息。
|
|
21
|
-
*
|
|
22
|
-
* @remarks
|
|
23
|
-
* 与 `ApprovalSubmission` 同形。这里只要求 park/续跑真正需要的字段,
|
|
24
|
-
* 避免 interaction 流程依赖完整提交模型。
|
|
25
|
-
*/
|
|
26
|
-
export interface InteractionSubmission {
|
|
27
|
-
readonly submissionId: string;
|
|
28
|
-
readonly requestId: string;
|
|
29
|
-
readonly assemblyRevision: string;
|
|
30
|
-
readonly status: SubmissionStatus;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 表示已经绑定到持久化提交的完整 interaction 记录。
|
|
35
|
-
*/
|
|
36
|
-
export interface InteractionRecord extends PiToolInteraction {
|
|
37
|
-
readonly submissionId: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 描述一次响应或取消要写入的 ToolResult 载荷。
|
|
42
|
-
*
|
|
43
|
-
* `content` / `details` 由 Tool 自己的 `settle` 映射产出 —— 本流程不解释响应体语义。
|
|
44
|
-
* 取消不带载荷,由纯函数层用固定文案兜底。
|
|
45
|
-
*/
|
|
46
|
-
export type InteractionSettlementPayload = Omit<
|
|
47
|
-
AgentToolResult<unknown>,
|
|
48
|
-
"terminate"
|
|
49
|
-
>;
|
|
50
|
-
|
|
51
|
-
// 宿主用这些依赖把 interaction 状态机接入数据库、Pi 恢复计算和 Turn 续跑。
|
|
52
|
-
interface InteractionLifecycleOptions<
|
|
53
|
-
TSubmission extends InteractionSubmission,
|
|
54
|
-
> {
|
|
55
|
-
readonly db: RuntimeDatabase;
|
|
56
|
-
readonly pi: PiRuntimeAdapter;
|
|
57
|
-
readonly findSubmission: (submissionId: string) => TSubmission | null;
|
|
58
|
-
readonly applyRecoveryMutations: (
|
|
59
|
-
submission: TSubmission,
|
|
60
|
-
mutations: readonly PiDurableMutation[],
|
|
61
|
-
) => boolean;
|
|
62
|
-
readonly materializeRecoveredToolResults: (
|
|
63
|
-
submission: TSubmission,
|
|
64
|
-
) => Promise<PiRecoveryDecision>;
|
|
65
|
-
/** 没有内存等待器时(DO 睡过一觉)把原 Turn 重新拉起来。 */
|
|
66
|
-
readonly resumeSubmission: (submissionId: string) => Promise<void>;
|
|
67
|
-
/**
|
|
68
|
-
* pending 集合发生变化后通知宿主重算活动状态投影。
|
|
69
|
-
*
|
|
70
|
-
* park 和结算都会触发。宿主的 needs-input 是 pending 行的纯函数,
|
|
71
|
-
* 没有这个回调,park 的那一刻不会有人去重算 —— 侧栏要等下一次广播才翻牌。
|
|
72
|
-
*/
|
|
73
|
-
readonly onInteractionsChanged?: () => Promise<void>;
|
|
74
|
-
readonly onTelemetryRequested?: (interaction: InteractionRecord) => void;
|
|
75
|
-
readonly onTelemetrySettled?: (interaction: InteractionRecord) => void;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// #endregion
|
|
79
|
-
|
|
80
|
-
// #region interaction 流程 API
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* 管理「结果由客户端提供」的 Tool 从 park、等待、响应到续跑的完整过程。
|
|
84
|
-
*
|
|
85
|
-
* @remarks
|
|
86
|
-
* 与 `ApprovalLifecycle` 结构同源但显著更小 —— 不需要 `allow_level` 的 Host 协调、
|
|
87
|
-
* 不需要批准后 `retryTool`、不需要执行档位矩阵。两者的重复是**当前有意接受的**:
|
|
88
|
-
* 审批那条路已经跑在线上,等 interaction 跑稳后再考虑抽公共核,届时改的是这两个类,
|
|
89
|
-
* 不是恢复状态机。
|
|
90
|
-
*
|
|
91
|
-
* 续跑不走 interaction 专属的调度键:响应会同时写下一条权威 `tool-result` 里程碑,
|
|
92
|
-
* 因此复用 `planPiToolRecovery` 中既有的 `tool:<id>:settled` 分支。
|
|
93
|
-
*/
|
|
94
|
-
export class InteractionLifecycle<
|
|
95
|
-
TSubmission extends InteractionSubmission,
|
|
96
|
-
> {
|
|
97
|
-
private readonly waiters = new Map<string, () => void>();
|
|
98
|
-
|
|
99
|
-
constructor(
|
|
100
|
-
private readonly options: InteractionLifecycleOptions<TSubmission>,
|
|
101
|
-
) {}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 用工具调用标识查找唯一一条仍待响应的 interaction。
|
|
105
|
-
*
|
|
106
|
-
* @remarks
|
|
107
|
-
* `Runtime.respondToolInteraction` 调用它 —— 前端只有 `toolCallId`。
|
|
108
|
-
* 调用方必须把 `null` 当作没有安全匹配,不应猜测要处理哪条记录。
|
|
109
|
-
*/
|
|
110
|
-
findPending(toolCallId: string): InteractionRecord | null {
|
|
111
|
-
const stored = this.options.db.interactions
|
|
112
|
-
.findPendingByToolCallId(toolCallId);
|
|
113
|
-
return stored ? toRecord(stored) : null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 按 interactionId 读取一条完整记录,不存在时返回 null。
|
|
118
|
-
*/
|
|
119
|
-
read(interactionId: string): InteractionRecord | null {
|
|
120
|
-
const stored = this.options.db.interactions.find(interactionId);
|
|
121
|
-
return stored ? toRecord(stored) : null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* 持久化一条 interaction,并暂停当前工具执行直到客户端投递响应。
|
|
126
|
-
*
|
|
127
|
-
* @remarks
|
|
128
|
-
* 执行适配器在 Tool 声明了 `interaction` 时调用它。
|
|
129
|
-
* 记录与恢复里程碑先在事务中落盘,再建立内存等待,保证重启仍能恢复 park 状态。
|
|
130
|
-
*/
|
|
131
|
-
async request(
|
|
132
|
-
submission: TSubmission,
|
|
133
|
-
interaction: PiToolInteraction,
|
|
134
|
-
signal?: AbortSignal,
|
|
135
|
-
): Promise<void> {
|
|
136
|
-
const pending = this.options.db.transaction(() => {
|
|
137
|
-
const value = this.ensurePending(submission, interaction);
|
|
138
|
-
if (value.created) {
|
|
139
|
-
try {
|
|
140
|
-
this.options.onTelemetryRequested?.(value.interaction);
|
|
141
|
-
} catch {
|
|
142
|
-
// Observability must not change interaction durability.
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return value.interaction;
|
|
146
|
-
});
|
|
147
|
-
// Register synchronously, then publish while waiting for the person. An
|
|
148
|
-
// early response must release this execution instead of starting recovery.
|
|
149
|
-
const waiting = this.wait(pending, signal);
|
|
150
|
-
try {
|
|
151
|
-
await Promise.all([
|
|
152
|
-
waiting,
|
|
153
|
-
(async () => { await this.options.onInteractionsChanged?.(); })(),
|
|
154
|
-
]);
|
|
155
|
-
} catch (error) {
|
|
156
|
-
this.waiters.get(pending.interactionId)?.();
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* 应用客户端的首次响应,结算对应 Tool 并唤醒原 Turn。
|
|
163
|
-
*
|
|
164
|
-
* @remarks
|
|
165
|
-
* `Runtime.respondToolInteraction` 在校验并映射出 ToolResult 后调用它。
|
|
166
|
-
* 不存在、已结束或提交已终态都返回 `{ ok: false }`,不抛 —— 口径对齐 `ApprovalLifecycle.decide`。
|
|
167
|
-
*/
|
|
168
|
-
async respond(
|
|
169
|
-
interactionId: string,
|
|
170
|
-
response: unknown,
|
|
171
|
-
payload: InteractionSettlementPayload,
|
|
172
|
-
): Promise<{ ok: boolean }> {
|
|
173
|
-
return this.settle(interactionId, {
|
|
174
|
-
kind: "respond",
|
|
175
|
-
response,
|
|
176
|
-
result: payload,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* 在用户没有作答的情况下结束一条 interaction 并让 Turn 继续。
|
|
182
|
-
*
|
|
183
|
-
* @remarks
|
|
184
|
-
* park 期间收到用户消息时调用(`user_replied_freeform`),提交进终态时也调用。
|
|
185
|
-
* 取消同样产出稳定 ToolResult,不是 Tool 失败 —— 否则模型会收到 tool_error 并倾向重试。
|
|
186
|
-
*/
|
|
187
|
-
async cancel(
|
|
188
|
-
interactionId: string,
|
|
189
|
-
reason: PiToolInteractionCancelReason,
|
|
190
|
-
): Promise<{ ok: boolean }> {
|
|
191
|
-
return this.settle(interactionId, { kind: "cancel", reason });
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* 把某次提交下仍待响应的全部 interaction 一次性取消。
|
|
196
|
-
*
|
|
197
|
-
* @remarks
|
|
198
|
-
* `dispatchMessage` 发现 park 期间来了用户消息时、以及提交进终态时调用。
|
|
199
|
-
* 返回是否真的取消过记录,调用方据此决定要不要广播。
|
|
200
|
-
*/
|
|
201
|
-
async cancelPendingForSubmission(
|
|
202
|
-
submissionId: string,
|
|
203
|
-
reason: PiToolInteractionCancelReason,
|
|
204
|
-
): Promise<boolean> {
|
|
205
|
-
const pending = this.options.db.interactions
|
|
206
|
-
.listPendingForSubmission(submissionId);
|
|
207
|
-
if (pending.length === 0) return false;
|
|
208
|
-
let cancelled = false;
|
|
209
|
-
for (const interactionId of pending) {
|
|
210
|
-
const result = await this.cancel(interactionId, reason);
|
|
211
|
-
cancelled ||= result.ok;
|
|
212
|
-
}
|
|
213
|
-
return cancelled;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* 在同步终态事务里把某次提交下仍待响应的 interaction 全部取消。
|
|
218
|
-
*
|
|
219
|
-
* @remarks
|
|
220
|
-
* `Runtime.commitTerminalOutcome` 在提交终态的 `transaction` 内调用,因此必须是同步的。
|
|
221
|
-
* 这里只推进状态和里程碑,**不** materialize、**不** 续跑 —— Turn 正在结束,
|
|
222
|
-
* 调用方负责在同一事务内调用 `materializeRecoveredToolResultsSync`,口径对齐审批的 `rejectPending`。
|
|
223
|
-
*
|
|
224
|
-
* 返回是否真的取消过记录;没有 pending 时返回 false,调用方可据此跳过 materialize。
|
|
225
|
-
*/
|
|
226
|
-
cancelPendingForSubmissionSync(
|
|
227
|
-
submission: TSubmission,
|
|
228
|
-
reason: PiToolInteractionCancelReason = "submission_terminal",
|
|
229
|
-
): boolean {
|
|
230
|
-
const pending = this.options.db.interactions
|
|
231
|
-
.listPendingForSubmission(submission.submissionId);
|
|
232
|
-
if (pending.length === 0) return false;
|
|
233
|
-
for (const interactionId of pending) {
|
|
234
|
-
const decision = this.decideRecovery(submission, {
|
|
235
|
-
kind: "interaction-settlement",
|
|
236
|
-
interactionId,
|
|
237
|
-
settlement: { kind: "cancel", reason },
|
|
238
|
-
});
|
|
239
|
-
this.options.applyRecoveryMutations(submission, decision.mutations);
|
|
240
|
-
const settled = this.read(interactionId);
|
|
241
|
-
if (settled) {
|
|
242
|
-
try {
|
|
243
|
-
this.options.onTelemetrySettled?.(settled);
|
|
244
|
-
} catch {
|
|
245
|
-
// Observability must not change terminal cleanup.
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return true;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* 判断某次提交当前是否停在等待客户端响应上。
|
|
254
|
-
*
|
|
255
|
-
* @remarks
|
|
256
|
-
* `dispatchMessage` 用它决定要不要把 delivery 提升成 steer —— 否则 enqueue 的消息
|
|
257
|
-
* 要等这个 Turn 结束,而这个 Turn 正在等一个不会来的答案,死锁。
|
|
258
|
-
*/
|
|
259
|
-
hasPendingForSubmission(submissionId: string): boolean {
|
|
260
|
-
return this.options.db.interactions
|
|
261
|
-
.listPendingForSubmission(submissionId).length > 0;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// #endregion
|
|
265
|
-
|
|
266
|
-
// #region 内部
|
|
267
|
-
|
|
268
|
-
// 作用:把响应或取消统一走同一条持久化 + 唤醒路径。
|
|
269
|
-
// 调用:`respond` 和 `cancel`。
|
|
270
|
-
// 原因:两种结局的差别只在 ToolResult 从哪来,状态推进和续跑必须完全一致。
|
|
271
|
-
private async settle(
|
|
272
|
-
interactionId: string,
|
|
273
|
-
settlement:
|
|
274
|
-
| {
|
|
275
|
-
kind: "respond";
|
|
276
|
-
response: unknown;
|
|
277
|
-
result: InteractionSettlementPayload;
|
|
278
|
-
}
|
|
279
|
-
| { kind: "cancel"; reason: PiToolInteractionCancelReason },
|
|
280
|
-
): Promise<{ ok: boolean }> {
|
|
281
|
-
const stored = this.read(interactionId);
|
|
282
|
-
if (!stored) return { ok: false };
|
|
283
|
-
const submission = this.options.findSubmission(stored.submissionId);
|
|
284
|
-
if (!submission || isTerminalSubmissionStatus(submission.status)) {
|
|
285
|
-
return { ok: false };
|
|
286
|
-
}
|
|
287
|
-
if (stored.status !== "pending") return { ok: false };
|
|
288
|
-
|
|
289
|
-
const decision = this.decideRecovery(submission, {
|
|
290
|
-
kind: "interaction-settlement",
|
|
291
|
-
interactionId,
|
|
292
|
-
settlement,
|
|
293
|
-
});
|
|
294
|
-
if (!decision.applied) return { ok: false };
|
|
295
|
-
this.options.db.transaction(() => {
|
|
296
|
-
this.options.applyRecoveryMutations(submission, decision.mutations);
|
|
297
|
-
const settled = this.read(interactionId);
|
|
298
|
-
if (settled && settled.status !== "pending") {
|
|
299
|
-
try {
|
|
300
|
-
this.options.onTelemetrySettled?.(settled);
|
|
301
|
-
} catch {
|
|
302
|
-
// Observability must not change interaction settlement.
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
const persisted = this.read(interactionId);
|
|
307
|
-
if (!persisted || persisted.status === "pending") {
|
|
308
|
-
return { ok: false };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// 把刚写下的权威 tool-result 落成 settlement 行 —— park 住的执行链醒来后
|
|
312
|
-
// 只认 findToolSettlement,不读里程碑。这一步必须早于唤醒。
|
|
313
|
-
await this.options.materializeRecoveredToolResults(submission);
|
|
314
|
-
// 先重算活动状态再唤醒:唤醒会把 Turn 一路跑下去,晚通知就会让「等待回复」
|
|
315
|
-
// 在续跑期间多挂一段时间。
|
|
316
|
-
await this.options.onInteractionsChanged?.();
|
|
317
|
-
|
|
318
|
-
const waiter = this.waiters.get(interactionId);
|
|
319
|
-
if (waiter) {
|
|
320
|
-
waiter();
|
|
321
|
-
return { ok: true };
|
|
322
|
-
}
|
|
323
|
-
// 没有内存等待器 = DO 在 park 期间睡过一觉,原 Turn 的执行栈已经没了。
|
|
324
|
-
// 走持久恢复把它重新拉起来,里程碑里的 tool-result 会被当成已结算结果复用。
|
|
325
|
-
await this.options.resumeSubmission(submission.submissionId);
|
|
326
|
-
return { ok: true };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// 作用:读取一个 Submission 的全部恢复里程碑正文。
|
|
330
|
-
// 原因:持久化历史必须作为恢复决策的唯一输入。
|
|
331
|
-
private milestoneBodies(submissionId: string): string[] {
|
|
332
|
-
return this.options.db.milestones.listBodies(submissionId);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// 作用:请求 Pi 根据当前里程碑给出下一步。
|
|
336
|
-
// 原因:所有路径共用同一决策入口,避免各自改写恢复状态。
|
|
337
|
-
private decideRecovery(
|
|
338
|
-
submission: TSubmission,
|
|
339
|
-
command: PiRecoveryCommand = { kind: "inspect" },
|
|
340
|
-
): PiRecoveryDecision {
|
|
341
|
-
return this.options.pi.decideRecovery({
|
|
342
|
-
milestoneBodies: this.milestoneBodies(submission.submissionId),
|
|
343
|
-
identity: {
|
|
344
|
-
turnId: submission.submissionId,
|
|
345
|
-
assemblyRevision: submission.assemblyRevision,
|
|
346
|
-
},
|
|
347
|
-
command,
|
|
348
|
-
now: Date.now(),
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// 作用:复用或创建一条 pending interaction。
|
|
353
|
-
// 调用:`request` 在准入事务内调用。
|
|
354
|
-
// 原因:已存在记录的字段冲突必须报错,不能把响应投给另一次调用。
|
|
355
|
-
private ensurePending(
|
|
356
|
-
submission: TSubmission,
|
|
357
|
-
interaction: PiToolInteraction,
|
|
358
|
-
): { interaction: InteractionRecord; created: boolean } {
|
|
359
|
-
const existing = this.read(interaction.interactionId);
|
|
360
|
-
if (existing) {
|
|
361
|
-
if (
|
|
362
|
-
existing.requestId !== submission.requestId ||
|
|
363
|
-
existing.toolCallId !== interaction.toolCallId ||
|
|
364
|
-
existing.toolName !== interaction.toolName ||
|
|
365
|
-
existing.inputJson !== interaction.inputJson
|
|
366
|
-
) {
|
|
367
|
-
throw new Error(
|
|
368
|
-
`Conflicting durable Tool interaction: ${interaction.toolCallId}`,
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
return { interaction: existing, created: false };
|
|
372
|
-
}
|
|
373
|
-
// 表行和恢复里程碑都由 record-interaction 这一条命令产出,
|
|
374
|
-
// 不在这里直接 insert —— 两份状态分叉正是恢复最难查的一类 bug。
|
|
375
|
-
const decision = this.decideRecovery(submission, {
|
|
376
|
-
kind: "record-interaction",
|
|
377
|
-
interaction,
|
|
378
|
-
});
|
|
379
|
-
this.options.applyRecoveryMutations(submission, decision.mutations);
|
|
380
|
-
return {
|
|
381
|
-
interaction: { ...interaction, submissionId: submission.submissionId },
|
|
382
|
-
created: true,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// 作用:等待当前进程里的客户端响应。
|
|
387
|
-
// 原因:内存 Promise 只负责唤醒当前请求,数据库仍负责持久状态以支持重启。
|
|
388
|
-
private wait(
|
|
389
|
-
interaction: InteractionRecord,
|
|
390
|
-
signal?: AbortSignal,
|
|
391
|
-
): Promise<void> {
|
|
392
|
-
if (interaction.status !== "pending") return Promise.resolve();
|
|
393
|
-
return new Promise((resolve, reject) => {
|
|
394
|
-
const abort = () => {
|
|
395
|
-
this.waiters.delete(interaction.interactionId);
|
|
396
|
-
reject(signal?.reason ?? new Error("Tool interaction aborted"));
|
|
397
|
-
};
|
|
398
|
-
if (signal?.aborted) {
|
|
399
|
-
abort();
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
403
|
-
this.waiters.set(interaction.interactionId, () => {
|
|
404
|
-
signal?.removeEventListener("abort", abort);
|
|
405
|
-
this.waiters.delete(interaction.interactionId);
|
|
406
|
-
resolve();
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// #endregion
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// 把持久行转成流程内部使用的记录。
|
|
415
|
-
// find / findPending 查到记录后调用,上层因而只看 Pi 的 interaction 形状。
|
|
416
|
-
function toRecord(stored: StoredToolInteraction): InteractionRecord {
|
|
417
|
-
return {
|
|
418
|
-
interactionId: stored.interactionId,
|
|
419
|
-
submissionId: stored.submissionId,
|
|
420
|
-
requestId: stored.requestId,
|
|
421
|
-
toolCallId: stored.toolCallId,
|
|
422
|
-
toolName: stored.toolName,
|
|
423
|
-
inputJson: stored.inputJson,
|
|
424
|
-
status: stored.status,
|
|
425
|
-
createdAt: stored.createdAt,
|
|
426
|
-
...(stored.respondedAt === null
|
|
427
|
-
? {}
|
|
428
|
-
: { respondedAt: stored.respondedAt }),
|
|
429
|
-
...(stored.responseJson === null
|
|
430
|
-
? {}
|
|
431
|
-
: { responseJson: stored.responseJson }),
|
|
432
|
-
};
|
|
433
|
-
}
|