@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
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { CancelQueuedResult } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type { ConversationHistory } from "../conversation/history";
|
|
4
|
+
import type { MessageUiRepository } from "../conversation/repository";
|
|
5
|
+
import type { RuntimeTelemetryCoordinator } from "../telemetry/coordinator";
|
|
6
|
+
import type { RuntimeTurnEvents } from "../telemetry/turn-events";
|
|
7
|
+
import type { HarnessHumanDecisions } from "../tools/runtime/human-decisions";
|
|
8
|
+
import { USER_STOP_REASON } from "./contracts";
|
|
9
|
+
import type { SubmissionOutcome } from "./lifecycle";
|
|
10
|
+
import { isTerminalSubmissionStatus, type StoredSubmission, type SubmissionRepository } from "./repository";
|
|
11
|
+
import { finishPendingSteers } from "./steer-delivery";
|
|
12
|
+
import type { SteerRepository } from "./steer-repository";
|
|
13
|
+
|
|
14
|
+
/** Commits the Submission outcome and human/Host facts after accepted steering is preserved. */
|
|
15
|
+
export class SubmissionFinalizer {
|
|
16
|
+
constructor(private readonly options: {
|
|
17
|
+
submissions: SubmissionRepository;
|
|
18
|
+
steers: SteerRepository;
|
|
19
|
+
messageUi: MessageUiRepository;
|
|
20
|
+
transaction<T>(run: () => T): T;
|
|
21
|
+
conversation: Pick<ConversationHistory, "append" | "appendTurnAbortedMarker" | "storedMessages">;
|
|
22
|
+
decisions: Pick<HarnessHumanDecisions, "cancelForSubmission">;
|
|
23
|
+
events: Pick<RuntimeTurnEvents, "submissionTerminal">;
|
|
24
|
+
telemetry: Pick<RuntimeTelemetryCoordinator, "capture">;
|
|
25
|
+
releaseAbortedHarness(): Promise<void>;
|
|
26
|
+
cancelQueued(entryId: string): Promise<CancelQueuedResult | undefined>;
|
|
27
|
+
submit: Parameters<typeof finishPendingSteers>[0]["submit"];
|
|
28
|
+
waitUntil(promise: Promise<unknown>): void;
|
|
29
|
+
}) {}
|
|
30
|
+
|
|
31
|
+
async finish(
|
|
32
|
+
submissionId: string,
|
|
33
|
+
outcome: SubmissionOutcome,
|
|
34
|
+
message?: string,
|
|
35
|
+
): Promise<StoredSubmission> {
|
|
36
|
+
const initial = this.read(submissionId);
|
|
37
|
+
if (isTerminalSubmissionStatus(initial.status)) return initial;
|
|
38
|
+
const aborted = Boolean(initial.abortReason) || outcome === "aborted";
|
|
39
|
+
if (aborted) await this.options.releaseAbortedHarness();
|
|
40
|
+
await finishPendingSteers({
|
|
41
|
+
submissionId,
|
|
42
|
+
aborted,
|
|
43
|
+
submissions: this.options.submissions,
|
|
44
|
+
cancelHandoff: (id, reason) => this.commit(id, "aborted", reason),
|
|
45
|
+
steers: this.options.steers,
|
|
46
|
+
messageUi: this.options.messageUi,
|
|
47
|
+
transaction: (run) => this.options.transaction(run),
|
|
48
|
+
conversation: this.options.conversation,
|
|
49
|
+
cancelQueued: (id) => this.options.cancelQueued(id),
|
|
50
|
+
submit: this.options.submit,
|
|
51
|
+
waitUntil: this.options.waitUntil,
|
|
52
|
+
});
|
|
53
|
+
const output = (await this.options.conversation.storedMessages())
|
|
54
|
+
.filter((entry) => entry.submissionId === submissionId && entry.message.role === "assistant")
|
|
55
|
+
.at(-1)?.message;
|
|
56
|
+
return this.commit(submissionId, outcome, message, output?.role === "assistant" ? output : undefined);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Shares atomic terminal facts with an admitted handoff cancelled before execution. */
|
|
60
|
+
commit(
|
|
61
|
+
submissionId: string,
|
|
62
|
+
outcome: SubmissionOutcome,
|
|
63
|
+
message?: string,
|
|
64
|
+
output?: AssistantMessage,
|
|
65
|
+
): StoredSubmission {
|
|
66
|
+
return this.options.transaction(() => {
|
|
67
|
+
// Host admission and history reads can await while an explicit stop wins.
|
|
68
|
+
const latest = this.read(submissionId);
|
|
69
|
+
if (isTerminalSubmissionStatus(latest.status)) return latest;
|
|
70
|
+
const effectiveOutcome = latest.abortReason ? "aborted" : outcome;
|
|
71
|
+
const reason = latest.abortReason ?? message;
|
|
72
|
+
this.options.decisions.cancelForSubmission(submissionId, reason ?? "Turn ended before the decision");
|
|
73
|
+
if (effectiveOutcome === "aborted") {
|
|
74
|
+
this.options.telemetry.capture("turnCancelled", {
|
|
75
|
+
submissionId,
|
|
76
|
+
...(reason ? { reason } : {}),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const status = effectiveOutcome === "succeeded" ? "completed" : effectiveOutcome === "aborted" ? "aborted" : "error";
|
|
80
|
+
this.options.submissions.updateTerminal(submissionId, status, reason, Date.now());
|
|
81
|
+
const completed = this.read(submissionId);
|
|
82
|
+
if (effectiveOutcome === "aborted" && reason === USER_STOP_REASON) {
|
|
83
|
+
if (completed.completedAt === null) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Aborted SpringBrand submission has no completion time: ${submissionId}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
this.options.conversation.appendTurnAbortedMarker(
|
|
89
|
+
submissionId,
|
|
90
|
+
completed.completedAt,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
this.options.events.submissionTerminal(completed);
|
|
94
|
+
this.options.telemetry.capture("turnFinished", {
|
|
95
|
+
submissionId,
|
|
96
|
+
outcome: effectiveOutcome === "succeeded" ? "completed" : effectiveOutcome === "aborted" ? "cancelled" : "failed",
|
|
97
|
+
durationMs: Math.max(0, (completed.completedAt ?? Date.now()) - latest.createdAt),
|
|
98
|
+
...(output === undefined ? {} : { output }),
|
|
99
|
+
...(reason ? { error: reason } : {}),
|
|
100
|
+
occurredAt: completed.completedAt ?? Date.now(),
|
|
101
|
+
});
|
|
102
|
+
return completed;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private read(submissionId: string): StoredSubmission {
|
|
107
|
+
const submission = this.options.submissions.find(submissionId);
|
|
108
|
+
if (!submission) throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
|
|
109
|
+
return submission;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./admission";
|
|
2
|
+
export * from "./admission-repository";
|
|
3
|
+
export * from "./contracts";
|
|
4
|
+
export * from "./lifecycle";
|
|
5
|
+
export * from "./repository";
|
|
6
|
+
export * from "./steer-repository";
|
|
7
|
+
export * from "./steer-delivery";
|
|
8
|
+
export * from "./delivery";
|
|
9
|
+
export * from "./finalization";
|
|
@@ -2,7 +2,7 @@ import { TurnQueue } from "agents/chat";
|
|
|
2
2
|
import {
|
|
3
3
|
isTerminalSubmissionStatus,
|
|
4
4
|
type SubmissionStatus,
|
|
5
|
-
} from "
|
|
5
|
+
} from "./repository";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 本文件统一管理一次提交从接收到结束的过程。
|
|
@@ -130,10 +130,9 @@ export type SubmissionOutcome = "succeeded" | "failed" | "aborted";
|
|
|
130
130
|
* @remarks
|
|
131
131
|
* `SubmissionLifecycle` 把它交给 `execute`,Runtime 据此选择执行前置动作。
|
|
132
132
|
*
|
|
133
|
-
* `recovery` 和 `continuation`
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* 而且重放成本会随让步次数增长。
|
|
133
|
+
* `recovery` 和 `continuation` 必须分开:前者的进程内配置已经丢失,需重新装配原 runtime key 再打开 Session;
|
|
134
|
+
* 后者是本轮主动让出执行片,持久 operation 和已固定配置保持完好。
|
|
135
|
+
* 区分两者可避免主动让出时重复加载原 runtime key。
|
|
137
136
|
*/
|
|
138
137
|
export type SubmissionExecutionMode =
|
|
139
138
|
/** 首次执行一条新接收的提交。 */
|
|
@@ -156,6 +155,8 @@ export interface SubmissionInput<
|
|
|
156
155
|
> {
|
|
157
156
|
requestId: string;
|
|
158
157
|
idempotencyKey?: string;
|
|
158
|
+
/** Only a repository-validated accepted Steer may transfer despite a full new-work queue. */
|
|
159
|
+
isSteerHandoff?: true;
|
|
159
160
|
/**
|
|
160
161
|
* 准备接收所需数据,并返回同步创建记录的函数。
|
|
161
162
|
*
|
|
@@ -202,10 +203,6 @@ interface SubmissionLifecycleOptions<
|
|
|
202
203
|
submissionId: string,
|
|
203
204
|
mode: SubmissionExecutionMode,
|
|
204
205
|
): Promise<TSubmission>;
|
|
205
|
-
// 作用:记录可恢复的取消意图。
|
|
206
|
-
// 调用:`cancel` 在同一存储事务内与取消原因一起写入。
|
|
207
|
-
// 原因:持久化里程碑可以让恢复路径看到已经发生的取消。
|
|
208
|
-
appendAbortIntent(submission: TSubmission, reason: string): void;
|
|
209
206
|
// 作用:把一条非终态提交写成最终结果。
|
|
210
207
|
// 调用:`finish` 在重读并确认仍非终态后调用。
|
|
211
208
|
// 原因:状态映射和其他终态副作用必须由 Runtime 在一个边界完成。
|
|
@@ -217,7 +214,9 @@ interface SubmissionLifecycleOptions<
|
|
|
217
214
|
// 作用:通知当前执行器中断运行。
|
|
218
215
|
// 调用:`cancel` 命中活动执行器,或 `activate` 发现早到取消时调用。
|
|
219
216
|
// 原因:生命周期不依赖具体执行器类型,只通过这个回调中断。
|
|
220
|
-
abortActive(active: TActive): void
|
|
217
|
+
abortActive(active: TActive): void | Promise<void>;
|
|
218
|
+
/** Close a persisted operation after its process-local executor has parked. */
|
|
219
|
+
abortDormant(submission: TSubmission): void | Promise<void>;
|
|
221
220
|
/**
|
|
222
221
|
* 中断活动执行器,但让这条提交留给持久续跑接手。
|
|
223
222
|
*
|
|
@@ -244,6 +243,7 @@ export class SubmissionLifecycle<
|
|
|
244
243
|
private readonly queue = new TurnQueue();
|
|
245
244
|
private readonly executions = new Map<string, Promise<TSubmission>>();
|
|
246
245
|
private readonly activeBySubmission = new Map<string, TActive>();
|
|
246
|
+
private readonly terminalCommits = new Map<string, Promise<TSubmission>>();
|
|
247
247
|
|
|
248
248
|
// #region 提交与恢复
|
|
249
249
|
|
|
@@ -322,10 +322,10 @@ export class SubmissionLifecycle<
|
|
|
322
322
|
* 等当前执行片退出后,接着跑同一条非终态 Submission。
|
|
323
323
|
*
|
|
324
324
|
* @remarks
|
|
325
|
-
* `
|
|
325
|
+
* `_piHarnessContinuation` 在计划让步或 Provider 等待的唤醒里调用。先 await 当前执行片,
|
|
326
326
|
* 是为了避开 `executions` 的实例内去重 —— 否则这次唤醒会被合流到正在退场的那一片上。
|
|
327
327
|
*
|
|
328
|
-
* 走 `continuation` 而不是 `recovery
|
|
328
|
+
* 走 `continuation` 而不是 `recovery`:这一轮什么都没丢,只需重新 drive 同一个持久 operation。
|
|
329
329
|
*/
|
|
330
330
|
async continueAfterCurrent(submissionId: string): Promise<TSubmission> {
|
|
331
331
|
const current = this.executions.get(submissionId);
|
|
@@ -382,16 +382,15 @@ export class SubmissionLifecycle<
|
|
|
382
382
|
rejected: false,
|
|
383
383
|
};
|
|
384
384
|
}
|
|
385
|
-
if (
|
|
386
|
-
this.options.store.countPending() >=
|
|
387
|
-
MAX_PENDING_SUBMISSIONS
|
|
385
|
+
if (!input.isSteerHandoff &&
|
|
386
|
+
this.options.store.countPending() >= MAX_PENDING_SUBMISSIONS
|
|
388
387
|
) {
|
|
389
388
|
throw new SubmissionQueueFullError();
|
|
390
389
|
}
|
|
391
390
|
const submission = create();
|
|
392
391
|
return {
|
|
393
392
|
submission,
|
|
394
|
-
admitted: submission.
|
|
393
|
+
admitted: !isTerminalSubmissionStatus(submission.status),
|
|
395
394
|
rejected: submission.admissionRejected === true,
|
|
396
395
|
};
|
|
397
396
|
});
|
|
@@ -520,17 +519,27 @@ export class SubmissionLifecycle<
|
|
|
520
519
|
*
|
|
521
520
|
* 写入前重读最新记录并保留已有终态,避免较晚到达的结果覆盖先完成的结果。
|
|
522
521
|
*/
|
|
523
|
-
|
|
522
|
+
finish(
|
|
524
523
|
submission: TSubmission,
|
|
525
524
|
outcome: SubmissionOutcome,
|
|
526
525
|
message?: string,
|
|
527
526
|
): Promise<TSubmission> {
|
|
527
|
+
const existing = this.terminalCommits.get(submission.submissionId);
|
|
528
|
+
if (existing) return existing;
|
|
528
529
|
const latest = this.options.store.find(submission.submissionId);
|
|
529
530
|
if (!latest) {
|
|
530
|
-
|
|
531
|
+
return Promise.reject(
|
|
532
|
+
new Error(`Unknown SpringBrand submission: ${submission.submissionId}`),
|
|
533
|
+
);
|
|
531
534
|
}
|
|
532
|
-
if (isTerminalSubmissionStatus(latest.status)) return latest;
|
|
533
|
-
|
|
535
|
+
if (isTerminalSubmissionStatus(latest.status)) return Promise.resolve(latest);
|
|
536
|
+
const commit = this.options.commitTerminal(latest, outcome, message);
|
|
537
|
+
this.terminalCommits.set(submission.submissionId, commit);
|
|
538
|
+
void commit.then(
|
|
539
|
+
() => this.terminalCommits.delete(submission.submissionId),
|
|
540
|
+
() => this.terminalCommits.delete(submission.submissionId),
|
|
541
|
+
);
|
|
542
|
+
return commit;
|
|
534
543
|
}
|
|
535
544
|
|
|
536
545
|
/**
|
|
@@ -648,14 +657,16 @@ export class SubmissionLifecycle<
|
|
|
648
657
|
if (!submission || isTerminalSubmissionStatus(submission.status)) {
|
|
649
658
|
return { ok: false };
|
|
650
659
|
}
|
|
651
|
-
this.options.store.transaction(() =>
|
|
652
|
-
this.options.store.updateAbortReason(submissionId, reason)
|
|
653
|
-
|
|
654
|
-
});
|
|
660
|
+
this.options.store.transaction(() =>
|
|
661
|
+
this.options.store.updateAbortReason(submissionId, reason)
|
|
662
|
+
);
|
|
655
663
|
const active = this.activeBySubmission.get(submissionId);
|
|
656
|
-
if (active) this.options.abortActive(active);
|
|
664
|
+
if (active) await this.options.abortActive(active);
|
|
665
|
+
else if (submission.status === "running") {
|
|
666
|
+
await this.options.abortDormant(submission);
|
|
667
|
+
}
|
|
657
668
|
await this.finish(submission, "aborted", reason);
|
|
658
|
-
|
|
669
|
+
this.pump();
|
|
659
670
|
return { ok: true };
|
|
660
671
|
}
|
|
661
672
|
|
|
@@ -699,12 +710,14 @@ export class SubmissionLifecycle<
|
|
|
699
710
|
this.options.store.transaction(() => {
|
|
700
711
|
for (const submission of submissions) {
|
|
701
712
|
this.options.store.updateAbortReason(submission.submissionId, reason);
|
|
702
|
-
this.options.appendAbortIntent(submission, reason);
|
|
703
713
|
}
|
|
704
714
|
});
|
|
705
715
|
for (const submission of submissions) {
|
|
706
716
|
const active = this.activeBySubmission.get(submission.submissionId);
|
|
707
|
-
if (active) this.options.abortActive(active);
|
|
717
|
+
if (active) await this.options.abortActive(active);
|
|
718
|
+
else if (submission.status === "running") {
|
|
719
|
+
await this.options.abortDormant(submission);
|
|
720
|
+
}
|
|
708
721
|
await this.finish(submission, "aborted", reason);
|
|
709
722
|
}
|
|
710
723
|
this.pump();
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { SqlTaggedTemplate } from "agents/chat";
|
|
2
|
+
import type { LaneSnapshot } from "@earendil-works/pi-agent-core";
|
|
3
|
+
import type { ExecutionLevel } from "../lib/execution-level";
|
|
4
|
+
import type { RuntimeUnattendedExecutionGrant } from "./unattended";
|
|
2
5
|
|
|
3
6
|
// Submission 的统一定义见 ./index.ts。
|
|
4
7
|
// #region 状态与行类型
|
|
@@ -9,11 +12,14 @@ export type SubmissionStatus =
|
|
|
9
12
|
| "aborted"
|
|
10
13
|
| "skipped"
|
|
11
14
|
| "error";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 谁发起了这条 Submission。
|
|
18
|
+
*
|
|
19
|
+
* `scheduled` 是定时 Run 的注入:它在本 Session 未结束期间独占输入,
|
|
20
|
+
* 普通 `user` Submission 必须被拒绝,而不是排队等它结束。
|
|
21
|
+
*/
|
|
22
|
+
export type SubmissionOrigin = "user" | "scheduled";
|
|
17
23
|
|
|
18
24
|
const TERMINAL_SUBMISSION_STATUSES: ReadonlySet<SubmissionStatus> = new Set([
|
|
19
25
|
"completed",
|
|
@@ -23,11 +29,11 @@ const TERMINAL_SUBMISSION_STATUSES: ReadonlySet<SubmissionStatus> = new Set([
|
|
|
23
29
|
]);
|
|
24
30
|
|
|
25
31
|
// 判断 Submission 是否已经结束。
|
|
26
|
-
// Runtime、SubmissionLifecycle 和
|
|
32
|
+
// Runtime、SubmissionLifecycle 和 HarnessDecisionGate 在决定是否继续执行、等待或接受审批时调用。
|
|
27
33
|
// 终态集合收口在一处防止各生命周期的判断漂移;增加状态时必须先确认它是否真的不可再继续。
|
|
28
34
|
export function isTerminalSubmissionStatus(
|
|
29
35
|
status: SubmissionStatus,
|
|
30
|
-
):
|
|
36
|
+
): status is Exclude<SubmissionStatus, "pending" | "running"> {
|
|
31
37
|
return TERMINAL_SUBMISSION_STATUSES.has(status);
|
|
32
38
|
}
|
|
33
39
|
|
|
@@ -42,20 +48,25 @@ export interface StoredSubmission {
|
|
|
42
48
|
completedAt: number | null;
|
|
43
49
|
assemblyRevision: string;
|
|
44
50
|
assemblyDescriptor: string;
|
|
51
|
+
runtimeKey: string;
|
|
45
52
|
assistantMessageId: string;
|
|
46
53
|
abortReason: string | null;
|
|
47
54
|
queuedInputJson: string | null;
|
|
48
55
|
queuedUiMessageJson: string | null;
|
|
49
56
|
userMessageId: string | null;
|
|
50
57
|
regenerateMessageId: string | null;
|
|
51
|
-
recoveryErrorCount: number;
|
|
52
|
-
recoveryReason: SubmissionRecoveryReason | null;
|
|
53
58
|
runId: string | null;
|
|
59
|
+
origin: SubmissionOrigin;
|
|
60
|
+
scheduledMaxExecutionLevel: ExecutionLevel | null;
|
|
61
|
+
unattendedExecutionGrant: RuntimeUnattendedExecutionGrant | null;
|
|
62
|
+
unattendedFailure: string | null;
|
|
54
63
|
retryable?: boolean;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Reuse durable stop intent when transferring work out of the source Submission. */
|
|
67
|
+
export function submissionAbortReason(submission: StoredSubmission | null): string | undefined {
|
|
68
|
+
return submission?.abortReason ||
|
|
69
|
+
(submission?.status === "aborted" ? submission.error || "Cancelled" : undefined);
|
|
59
70
|
}
|
|
60
71
|
|
|
61
72
|
export interface NewSubmission {
|
|
@@ -65,12 +76,16 @@ export interface NewSubmission {
|
|
|
65
76
|
createdAt: number;
|
|
66
77
|
assemblyRevision: string;
|
|
67
78
|
assemblyDescriptor: string;
|
|
79
|
+
runtimeKey?: string;
|
|
68
80
|
assistantMessageId: string;
|
|
69
81
|
queuedInputJson?: string | null;
|
|
70
82
|
queuedUiMessageJson?: string | null;
|
|
71
83
|
userMessageId?: string | null;
|
|
72
84
|
regenerateMessageId?: string | null;
|
|
73
85
|
runId?: string | null;
|
|
86
|
+
origin?: SubmissionOrigin;
|
|
87
|
+
scheduledMaxExecutionLevel?: ExecutionLevel;
|
|
88
|
+
unattendedExecutionGrant?: RuntimeUnattendedExecutionGrant;
|
|
74
89
|
}
|
|
75
90
|
|
|
76
91
|
type SubmissionRow = {
|
|
@@ -84,18 +99,19 @@ type SubmissionRow = {
|
|
|
84
99
|
completed_at: number | null;
|
|
85
100
|
assembly_revision: string;
|
|
86
101
|
assembly_descriptor: string;
|
|
102
|
+
runtime_key: string;
|
|
87
103
|
assistant_message_id: string;
|
|
88
104
|
abort_reason: string | null;
|
|
89
105
|
queued_input_json: string | null;
|
|
90
106
|
queued_ui_message_json: string | null;
|
|
91
107
|
user_message_id: string | null;
|
|
92
108
|
regenerate_message_id: string | null;
|
|
93
|
-
recovery_error_count: number;
|
|
94
|
-
recovery_reason: string | null;
|
|
95
109
|
run_id: string | null;
|
|
110
|
+
origin: string | null;
|
|
111
|
+
scheduled_max_execution_level: string | null;
|
|
96
112
|
admission_retryable: number | null;
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
unattended_execution_grant_json: string | null;
|
|
114
|
+
unattended_failure: string | null;
|
|
99
115
|
};
|
|
100
116
|
|
|
101
117
|
// #endregion
|
|
@@ -115,20 +131,23 @@ function mapRow(row: SubmissionRow): StoredSubmission {
|
|
|
115
131
|
completedAt: row.completed_at,
|
|
116
132
|
assemblyRevision: row.assembly_revision,
|
|
117
133
|
assemblyDescriptor: row.assembly_descriptor,
|
|
134
|
+
runtimeKey: row.runtime_key,
|
|
118
135
|
assistantMessageId: row.assistant_message_id,
|
|
119
136
|
abortReason: row.abort_reason,
|
|
120
137
|
queuedInputJson: row.queued_input_json,
|
|
121
138
|
queuedUiMessageJson: row.queued_ui_message_json,
|
|
122
139
|
userMessageId: row.user_message_id,
|
|
123
140
|
regenerateMessageId: row.regenerate_message_id,
|
|
124
|
-
recoveryErrorCount: row.recovery_error_count,
|
|
125
|
-
recoveryReason: row.recovery_reason as SubmissionRecoveryReason | null,
|
|
126
141
|
runId: row.run_id,
|
|
142
|
+
origin: row.origin === "scheduled" ? "scheduled" : "user",
|
|
143
|
+
scheduledMaxExecutionLevel: row.scheduled_max_execution_level as ExecutionLevel | null,
|
|
144
|
+
unattendedExecutionGrant: row.unattended_execution_grant_json
|
|
145
|
+
? JSON.parse(row.unattended_execution_grant_json) as RuntimeUnattendedExecutionGrant
|
|
146
|
+
: null,
|
|
147
|
+
unattendedFailure: row.unattended_failure,
|
|
127
148
|
retryable: row.admission_retryable === null
|
|
128
149
|
? undefined
|
|
129
150
|
: row.admission_retryable === 1,
|
|
130
|
-
modelTurns: row.model_turns ?? 0,
|
|
131
|
-
continuationId: row.continuation_id,
|
|
132
151
|
};
|
|
133
152
|
}
|
|
134
153
|
|
|
@@ -138,7 +157,14 @@ export class SubmissionRepository {
|
|
|
138
157
|
// 保存当前 Agent SQLite 的查询入口。
|
|
139
158
|
// RuntimeDatabase 构造时调用,SubmissionLifecycle 和 Runtime 随后共享这个 Repository。
|
|
140
159
|
// Submission 的准入、执行和恢复必须看同一 Agent 存储,不能混入其他 Durable Object 的 sql。
|
|
141
|
-
constructor(
|
|
160
|
+
constructor(
|
|
161
|
+
private readonly sql: SqlTaggedTemplate,
|
|
162
|
+
private readonly transact: <T>(run: () => T) => T,
|
|
163
|
+
) {}
|
|
164
|
+
|
|
165
|
+
transaction<T>(run: () => T): T {
|
|
166
|
+
return this.transact(run);
|
|
167
|
+
}
|
|
142
168
|
|
|
143
169
|
// 按 submissionId 读取一条完整 Submission,不存在时返回 null。
|
|
144
170
|
// Runtime 和 SubmissionLifecycle 在准入、执行、取消、结束与恢复时调用,调用方必须处理 null。
|
|
@@ -147,12 +173,15 @@ export class SubmissionRepository {
|
|
|
147
173
|
const row = this.sql<SubmissionRow>`
|
|
148
174
|
SELECT submission_id, request_id, idempotency_key, status, accepted,
|
|
149
175
|
error, created_at, completed_at, assembly_revision,
|
|
150
|
-
assembly_descriptor, assistant_message_id, abort_reason,
|
|
176
|
+
assembly_descriptor, runtime_key, assistant_message_id, abort_reason,
|
|
151
177
|
queued_input_json, queued_ui_message_json, user_message_id,
|
|
152
|
-
regenerate_message_id,
|
|
178
|
+
regenerate_message_id,
|
|
153
179
|
run_id,
|
|
180
|
+
origin,
|
|
181
|
+
scheduled_max_execution_level,
|
|
154
182
|
admission_retryable,
|
|
155
|
-
|
|
183
|
+
unattended_execution_grant_json,
|
|
184
|
+
unattended_failure
|
|
156
185
|
FROM pi_submissions
|
|
157
186
|
WHERE submission_id = ${id}
|
|
158
187
|
`[0];
|
|
@@ -250,10 +279,10 @@ export class SubmissionRepository {
|
|
|
250
279
|
this.sql`
|
|
251
280
|
INSERT INTO pi_submissions (
|
|
252
281
|
submission_id, request_id, idempotency_key, status, accepted,
|
|
253
|
-
created_at, assembly_revision, assembly_descriptor,
|
|
282
|
+
created_at, assembly_revision, assembly_descriptor, runtime_key,
|
|
254
283
|
assistant_message_id, queued_input_json, queued_ui_message_json,
|
|
255
284
|
user_message_id, regenerate_message_id,
|
|
256
|
-
run_id
|
|
285
|
+
run_id, origin, scheduled_max_execution_level, unattended_execution_grant_json
|
|
257
286
|
) VALUES (
|
|
258
287
|
${s.submissionId},
|
|
259
288
|
${s.requestId},
|
|
@@ -263,16 +292,37 @@ export class SubmissionRepository {
|
|
|
263
292
|
${s.createdAt},
|
|
264
293
|
${s.assemblyRevision},
|
|
265
294
|
${s.assemblyDescriptor},
|
|
295
|
+
${s.runtimeKey ?? ""},
|
|
266
296
|
${s.assistantMessageId},
|
|
267
297
|
${s.queuedInputJson ?? null},
|
|
268
298
|
${s.queuedUiMessageJson ?? null},
|
|
269
299
|
${s.userMessageId ?? null},
|
|
270
300
|
${s.regenerateMessageId ?? null},
|
|
271
|
-
${s.runId ?? s.submissionId}
|
|
301
|
+
${s.runId ?? s.submissionId},
|
|
302
|
+
${s.origin ?? "user"},
|
|
303
|
+
${s.scheduledMaxExecutionLevel ?? null},
|
|
304
|
+
${s.unattendedExecutionGrant
|
|
305
|
+
? JSON.stringify(s.unattendedExecutionGrant)
|
|
306
|
+
: null}
|
|
272
307
|
)
|
|
273
308
|
`;
|
|
274
309
|
}
|
|
275
310
|
|
|
311
|
+
/**
|
|
312
|
+
* 是否存在一条未结束的定时 Submission。
|
|
313
|
+
*
|
|
314
|
+
* 定时 Run 未结束时它独占本 Session 的输入:普通消息在准入前被拒绝,
|
|
315
|
+
* 不排队等待。Run 进入终态后查询自然为空,普通追问恢复。
|
|
316
|
+
*/
|
|
317
|
+
hasUnfinishedScheduled(): boolean {
|
|
318
|
+
return (
|
|
319
|
+
this.sql<{ count: number }>`
|
|
320
|
+
SELECT COUNT(*) AS count FROM pi_submissions
|
|
321
|
+
WHERE status IN ('pending', 'running') AND origin = 'scheduled'
|
|
322
|
+
`[0]?.count ?? 0
|
|
323
|
+
) > 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
276
326
|
insertRejected(s: {
|
|
277
327
|
submissionId: string;
|
|
278
328
|
requestId: string;
|
|
@@ -280,17 +330,19 @@ export class SubmissionRepository {
|
|
|
280
330
|
createdAt: number;
|
|
281
331
|
code: string;
|
|
282
332
|
retryable: boolean;
|
|
333
|
+
userMessageId?: string;
|
|
334
|
+
assistantMessageId?: string;
|
|
283
335
|
}): void {
|
|
284
336
|
this.sql`
|
|
285
337
|
INSERT INTO pi_submissions (
|
|
286
338
|
submission_id, request_id, idempotency_key, status, accepted,
|
|
287
339
|
error, created_at, completed_at, assembly_revision,
|
|
288
|
-
assembly_descriptor, assistant_message_id, abort_reason,
|
|
289
|
-
|
|
340
|
+
assembly_descriptor, runtime_key, assistant_message_id, abort_reason,
|
|
341
|
+
run_id, admission_retryable, user_message_id
|
|
290
342
|
) VALUES (
|
|
291
343
|
${s.submissionId}, ${s.requestId}, ${s.idempotencyKey}, 'skipped', 0,
|
|
292
|
-
${s.code}, ${s.createdAt}, ${s.createdAt}, '', '', '', NULL,
|
|
293
|
-
|
|
344
|
+
${s.code}, ${s.createdAt}, ${s.createdAt}, '', '', '', ${s.assistantMessageId ?? s.submissionId}, NULL,
|
|
345
|
+
${s.submissionId}, ${s.retryable ? 1 : 0}, ${s.userMessageId ?? null}
|
|
294
346
|
)
|
|
295
347
|
`;
|
|
296
348
|
}
|
|
@@ -309,22 +361,13 @@ export class SubmissionRepository {
|
|
|
309
361
|
return updated.length === 1;
|
|
310
362
|
}
|
|
311
363
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// 与 pinAssembly 的区别就是它要求已经 pin 过:首次 pin 走 pinAssembly,
|
|
315
|
-
// 这里不接受空值,避免把一次漏写的准入 pin 伪装成一次迁移。
|
|
316
|
-
repinAssembly(id: string, revision: string, descriptor: string): boolean {
|
|
317
|
-
const updated = this.sql<{ submission_id: string }>`
|
|
364
|
+
recordUnattendedFailure(id: string, reason: string): void {
|
|
365
|
+
this.sql`
|
|
318
366
|
UPDATE pi_submissions
|
|
319
|
-
SET
|
|
320
|
-
assembly_descriptor = ${descriptor}
|
|
367
|
+
SET unattended_failure = COALESCE(unattended_failure, ${reason})
|
|
321
368
|
WHERE submission_id = ${id}
|
|
322
|
-
AND assembly_revision <> ''
|
|
323
|
-
AND assembly_descriptor <> ''
|
|
324
369
|
AND status IN ('pending', 'running')
|
|
325
|
-
RETURNING submission_id
|
|
326
370
|
`;
|
|
327
|
-
return updated.length === 1;
|
|
328
371
|
}
|
|
329
372
|
|
|
330
373
|
// 只在当前状态属于 from 时把 Submission 切换到 to,否则返回 false。
|
|
@@ -393,84 +436,44 @@ export class SubmissionRepository {
|
|
|
393
436
|
`;
|
|
394
437
|
}
|
|
395
438
|
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
reason: SubmissionRecoveryReason,
|
|
400
|
-
): number {
|
|
401
|
-
return this.sql<{ recovery_error_count: number }>`
|
|
402
|
-
UPDATE pi_submissions
|
|
403
|
-
SET recovery_error_count = recovery_error_count + 1,
|
|
404
|
-
recovery_reason = ${reason}
|
|
405
|
-
WHERE submission_id = ${id}
|
|
406
|
-
AND status IN ('pending', 'running')
|
|
407
|
-
RETURNING recovery_error_count
|
|
408
|
-
`[0]?.recovery_error_count ?? 0;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
claimContextOverflowRecovery(id: string): boolean {
|
|
412
|
-
return Boolean(this.sql<{ claimed: number }>`
|
|
413
|
-
UPDATE pi_submissions
|
|
414
|
-
SET context_overflow_retried = 1
|
|
415
|
-
WHERE submission_id = ${id}
|
|
416
|
-
AND status IN ('pending', 'running')
|
|
417
|
-
AND context_overflow_retried = 0
|
|
418
|
-
RETURNING 1 AS claimed
|
|
419
|
-
`[0]?.claimed);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* 记录一个让步执行片的累计模型回合数和续跑标识。
|
|
424
|
-
*
|
|
425
|
-
* @remarks
|
|
426
|
-
* Runtime 在一片主动让出后、排出续跑调度之前调用。
|
|
427
|
-
*
|
|
428
|
-
* 两个值必须一起写:全局回合预算靠 `model_turns` 拦住不收敛的工具循环,
|
|
429
|
-
* 续跑去重和看门狗靠 `continuation_id` 判断这一轮到底有没有推进。
|
|
430
|
-
* 只写非终态记录,避免一次迟到的写入复活已经结束的 Submission。
|
|
431
|
-
*/
|
|
432
|
-
recordYieldedSlice(
|
|
433
|
-
id: string,
|
|
434
|
-
modelTurns: number,
|
|
435
|
-
continuationId: string,
|
|
436
|
-
): void {
|
|
437
|
-
this.sql`
|
|
438
|
-
UPDATE pi_submissions
|
|
439
|
-
SET model_turns = ${modelTurns},
|
|
440
|
-
continuation_id = ${continuationId}
|
|
441
|
-
WHERE submission_id = ${id}
|
|
442
|
-
AND status IN ('pending', 'running')
|
|
443
|
-
`;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/** 记录累计模型回合数,不改动续跑标识。终态提交前的最后一次结算用它。 */
|
|
447
|
-
recordModelTurns(id: string, modelTurns: number): void {
|
|
439
|
+
// 持久化 Session entry 到 Submission 的关联,使 Host 可以从 Pi 分支
|
|
440
|
+
// 回填 message、ToolResult 和 compaction 的用量与结算 Outbox。
|
|
441
|
+
linkSessionEntry(entryId: string, submissionId: string): void {
|
|
448
442
|
this.sql`
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
AND status IN ('pending', 'running')
|
|
443
|
+
INSERT OR IGNORE INTO pi_session_entry_submission
|
|
444
|
+
(entry_id, submission_id)
|
|
445
|
+
VALUES (${entryId}, ${submissionId})
|
|
453
446
|
`;
|
|
454
447
|
}
|
|
455
448
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
449
|
+
/** Recover ownership from the matching Pi operation, using only its active branch. */
|
|
450
|
+
associateSessionEntries(
|
|
451
|
+
submissionId: string,
|
|
452
|
+
snapshot: Pick<LaneSnapshot, "operation" | "lastResult" | "tipId" | "transcript">,
|
|
453
|
+
): void {
|
|
454
|
+
const boundary = snapshot.operation?.id === submissionId
|
|
455
|
+
? { fromTipId: snapshot.operation.fromTipId, tipId: snapshot.tipId }
|
|
456
|
+
: snapshot.lastResult?.operationId === submissionId
|
|
457
|
+
? snapshot.lastResult
|
|
458
|
+
: undefined;
|
|
459
|
+
if (!boundary) return;
|
|
460
|
+
const start = boundary.fromTipId === null
|
|
461
|
+
? 0
|
|
462
|
+
: snapshot.transcript.findIndex(({ id }) => id === boundary.fromTipId) + 1;
|
|
463
|
+
const end = boundary.tipId === null
|
|
464
|
+
? snapshot.transcript.length
|
|
465
|
+
: snapshot.transcript.findIndex(({ id }) => id === boundary.tipId) + 1;
|
|
466
|
+
for (const entry of snapshot.transcript.slice(start, end || undefined)) {
|
|
467
|
+
this.linkSessionEntry(entry.id, submissionId);
|
|
468
|
+
}
|
|
463
469
|
}
|
|
464
470
|
|
|
465
|
-
|
|
466
|
-
// Transcript.storedMessages 读取 Pi Session 分支时调用,它用 Map 为每条消息补回 submissionId。
|
|
467
|
-
// 关联表由 Pi Session 存储拥有,本方法只读不写;不能在 RuntimeDatabase.clearAll 中单独删它而破坏 Transcript 的所有权。
|
|
468
|
-
listMessageSubmissionLinks(): Map<string, string> {
|
|
471
|
+
listSessionEntrySubmissionLinks(): Map<string, string> {
|
|
469
472
|
const rows = this.sql<{
|
|
470
|
-
|
|
473
|
+
entry_id: string;
|
|
471
474
|
submission_id: string;
|
|
472
|
-
}>`SELECT
|
|
473
|
-
return new Map(rows.map((r) => [r.
|
|
475
|
+
}>`SELECT entry_id, submission_id FROM pi_session_entry_submission`;
|
|
476
|
+
return new Map(rows.map((r) => [r.entry_id, r.submission_id] as [string, string]));
|
|
474
477
|
}
|
|
475
478
|
}
|
|
476
479
|
// #endregion
|