@springbrand/agent-runtime 0.1.3-alpha.7 → 0.2.0-alpha.13
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 +4 -3
- package/src/adapter/cloudflare/index.ts +1 -0
- package/src/adapter/cloudflare/sandbox/adapter.ts +4 -0
- package/src/adapter/cloudflare/subagent/definition.ts +60 -5
- package/src/adapter/cloudflare/universal-agent/hooks.ts +23 -1
- package/src/adapter/cloudflare/universal-agent/preparation.ts +34 -0
- package/src/adapter/cloudflare/universal-agent/tools.ts +7 -7
- package/src/adapter/cloudflare/workspace/git-fs.ts +178 -0
- package/src/adapter/cloudflare/workspace/version-control.ts +374 -0
- package/src/db/index.ts +4 -0
- package/src/db/runtime-event-outbox.repo.ts +34 -1
- package/src/db/schema.ts +42 -0
- package/src/db/submission-admission.repo.ts +127 -0
- package/src/db/submission.repo.ts +54 -3
- package/src/index.ts +3 -0
- package/src/kernel/bindings.ts +52 -0
- package/src/kernel/durable-lifecycle.ts +100 -0
- package/src/kernel/public-contracts.ts +11 -0
- package/src/kernel/receipts.ts +1 -0
- package/src/kernel/subagent-runtime.ts +137 -0
- package/src/kernel/submission-authority.ts +114 -0
- package/src/kernel/submission-lifecycle.ts +17 -5
- package/src/lib/prompt.ts +3 -0
- package/src/pi/runtime-adapter/assembly.ts +2 -1
- package/src/pi/runtime-adapter/execution.ts +8 -1
- package/src/pi/runtime-adapter/index.ts +33 -0
- package/src/pi/runtime-adapter/models.ts +9 -0
- package/src/pi/tool/core-host.ts +5 -1
- package/src/pi/tool/core.ts +1 -10
- package/src/pi/tool/index.ts +1 -0
- package/src/pi/tool/mcp.ts +2 -2
- package/src/pi/tool/schedule.ts +30 -18
- package/src/pi/tool/subagent.ts +142 -16
- package/src/pi/tool/workspace-revision.ts +64 -0
- package/src/runtime-agent-context.ts +1 -0
- package/src/runtime-assembler.ts +16 -1
- package/src/runtime-definition.ts +12 -0
- package/src/runtime.ts +427 -46
- package/src/tool-registry.ts +2 -0
- package/src/workspace-versioning.ts +46 -0
package/src/runtime.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import { InteractionLifecycle } from "./kernel/interaction-lifecycle";
|
|
21
21
|
import {
|
|
22
22
|
SubmissionLifecycle,
|
|
23
|
+
MAX_PENDING_SUBMISSIONS,
|
|
23
24
|
SubmissionQueueFullError,
|
|
24
25
|
type SubmissionInput,
|
|
25
26
|
type SubmissionStore,
|
|
@@ -88,10 +89,21 @@ import { serializeOutput } from "./lib/artifacts";
|
|
|
88
89
|
import type {
|
|
89
90
|
RuntimeModelUsageEvent,
|
|
90
91
|
RuntimeToolSettlementEvent,
|
|
92
|
+
RuntimeLifecycleFact,
|
|
93
|
+
RuntimeSubagentUsageEvent,
|
|
91
94
|
} from "./kernel/bindings";
|
|
95
|
+
import { RuntimeSubagentLifecycleRecorder } from "./kernel/durable-lifecycle";
|
|
96
|
+
import {
|
|
97
|
+
decodeSubagentCompletion,
|
|
98
|
+
isZeroSubagentUsage,
|
|
99
|
+
runtimeSubagentRunMetadata,
|
|
100
|
+
subagentTerminalStatus,
|
|
101
|
+
type RuntimeSubagentRunMetadata,
|
|
102
|
+
} from "./kernel/subagent-runtime";
|
|
92
103
|
import {
|
|
93
104
|
isTerminalSubmissionStatus,
|
|
94
105
|
RuntimeDatabase,
|
|
106
|
+
type StoredSubmissionAdmission,
|
|
95
107
|
type SubmissionStatus,
|
|
96
108
|
} from "./db/index";
|
|
97
109
|
import {
|
|
@@ -114,6 +126,11 @@ import {
|
|
|
114
126
|
|
|
115
127
|
const SCHEDULED_STABLE_TIMEOUT_MS = 30_000;
|
|
116
128
|
const TURN_EVENT_RETRY_SECONDS = 10;
|
|
129
|
+
// 一次 Host admission 调用最多被视为「在途」多久。停在 pending 的准入行永久保留
|
|
130
|
+
// —— 它固定了同一 identity 重投时必须复用的 runId,不能靠删行来让容量回收 ——
|
|
131
|
+
// 但一次远端失败或崩溃留下的行超过这个窗口后就不再占用 pending 容量,否则
|
|
132
|
+
// Host 侧的一次抖动会把 Session 永久卡在「队列已满」。
|
|
133
|
+
const ADMISSION_ATTEMPT_WINDOW_MS = 60_000;
|
|
117
134
|
export const CHAT_RECOVERY_MAX_ATTEMPTS = 5;
|
|
118
135
|
// stall 单独收窄。理由不是「stall 更不值得救」,而是它的重试**期望值和别的错不一样**:
|
|
119
136
|
// 瞬时错(5xx / 断流)重跑一次往往就好了;stall 的重跑是拿同一份 transcript 让模型
|
|
@@ -129,7 +146,9 @@ type RuntimeEventOutboxPayload =
|
|
|
129
146
|
| {
|
|
130
147
|
readonly type: "tool-settlement";
|
|
131
148
|
readonly event: RuntimeToolSettlementEvent;
|
|
132
|
-
}
|
|
149
|
+
}
|
|
150
|
+
| { readonly type: "subagent-usage"; readonly event: RuntimeSubagentUsageEvent }
|
|
151
|
+
| { readonly type: "lifecycle"; readonly event: RuntimeLifecycleFact };
|
|
133
152
|
interface StoredSubmission extends SubmissionReceipt {
|
|
134
153
|
status: SubmissionStatus;
|
|
135
154
|
requestId: string;
|
|
@@ -144,6 +163,10 @@ interface StoredSubmission extends SubmissionReceipt {
|
|
|
144
163
|
regenerateMessageId?: string | null;
|
|
145
164
|
recoveryErrorCount: number;
|
|
146
165
|
recoveryReason?: "no_meaningful_model_progress" | "transient_model_error";
|
|
166
|
+
runId: string | null;
|
|
167
|
+
accountId: string | null;
|
|
168
|
+
rateVersion: number | null;
|
|
169
|
+
slotIdentity: string | null;
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
interface SubmitMessageOptions {
|
|
@@ -178,6 +201,32 @@ function json(value: unknown): string {
|
|
|
178
201
|
return JSON.stringify(value);
|
|
179
202
|
}
|
|
180
203
|
|
|
204
|
+
function validateAdmissionResult(
|
|
205
|
+
result: import("./kernel/bindings").RuntimeSubmissionAdmissionResult,
|
|
206
|
+
): import("./kernel/bindings").RuntimeSubmissionAdmissionResult {
|
|
207
|
+
if (!result || typeof result !== "object" || typeof result.accepted !== "boolean") {
|
|
208
|
+
throw new Error("Runtime admission result is invalid");
|
|
209
|
+
}
|
|
210
|
+
if (!result.accepted) {
|
|
211
|
+
if (
|
|
212
|
+
typeof result.code !== "string" || !result.code.trim() ||
|
|
213
|
+
(result.retryable !== undefined && typeof result.retryable !== "boolean")
|
|
214
|
+
) {
|
|
215
|
+
throw new Error("Runtime admission rejection is invalid");
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
if (
|
|
220
|
+
typeof result.accountId !== "string" || !result.accountId.trim() ||
|
|
221
|
+
typeof result.slotIdentity !== "string" || !result.slotIdentity.trim() ||
|
|
222
|
+
!Number.isSafeInteger(result.rateVersion) ||
|
|
223
|
+
result.rateVersion <= 0
|
|
224
|
+
) {
|
|
225
|
+
throw new Error("Runtime admission context is invalid");
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
|
|
181
230
|
async function closeRuntimeGatewaySession(
|
|
182
231
|
session: RuntimeGatewaySession | undefined,
|
|
183
232
|
step: string,
|
|
@@ -192,10 +241,17 @@ function assistantUsageEvent(
|
|
|
192
241
|
eventId: string,
|
|
193
242
|
submissionId: string,
|
|
194
243
|
message: AssistantMessage,
|
|
244
|
+
pinned?: Pick<StoredSubmission, "accountId" | "rateVersion" | "slotIdentity">,
|
|
195
245
|
): RuntimeModelUsageEvent {
|
|
196
246
|
return {
|
|
197
247
|
eventId,
|
|
198
248
|
submissionId,
|
|
249
|
+
runId: submissionId,
|
|
250
|
+
...(pinned?.accountId ? { accountId: pinned.accountId } : {}),
|
|
251
|
+
...(pinned?.rateVersion !== null && pinned?.rateVersion !== undefined
|
|
252
|
+
? { rateVersion: pinned.rateVersion }
|
|
253
|
+
: {}),
|
|
254
|
+
...(pinned?.slotIdentity ? { slotIdentity: pinned.slotIdentity } : {}),
|
|
199
255
|
kind: "assistant",
|
|
200
256
|
api: message.api,
|
|
201
257
|
provider: message.provider,
|
|
@@ -327,6 +383,10 @@ export abstract class AgentRuntimeKernel<
|
|
|
327
383
|
private migratedSubmissionIds?: Set<string>;
|
|
328
384
|
private db!: RuntimeDatabase;
|
|
329
385
|
private readonly temporaryAgents = new TemporaryAgentCoordinator();
|
|
386
|
+
private readonly admissionsInFlight = new Map<
|
|
387
|
+
string,
|
|
388
|
+
Promise<import("./kernel/bindings").RuntimeSubmissionAdmissionResult>
|
|
389
|
+
>();
|
|
330
390
|
private readonly temporaryAgentApprovals =
|
|
331
391
|
new Map<string, PendingTemporaryAgentApproval>();
|
|
332
392
|
|
|
@@ -663,6 +723,37 @@ export abstract class AgentRuntimeKernel<
|
|
|
663
723
|
} = {},
|
|
664
724
|
): PreparedPiTurnAdapter {
|
|
665
725
|
const startedAt = output.startedAt ?? Date.now();
|
|
726
|
+
const snapshot = this.assembly();
|
|
727
|
+
const runMetadata: RuntimeSubagentRunMetadata = {
|
|
728
|
+
submissionId: submission.submissionId,
|
|
729
|
+
parentRunId: submission.runId ?? submission.submissionId,
|
|
730
|
+
...(submission.accountId ? { accountId: submission.accountId } : {}),
|
|
731
|
+
...(submission.rateVersion !== null
|
|
732
|
+
? { rateVersion: submission.rateVersion }
|
|
733
|
+
: {}),
|
|
734
|
+
...(submission.slotIdentity
|
|
735
|
+
? { slotIdentity: submission.slotIdentity }
|
|
736
|
+
: {}),
|
|
737
|
+
};
|
|
738
|
+
const lifecycle = this.subagentLifecycle(runMetadata);
|
|
739
|
+
const toolExecutors = snapshot.bindings.subagents
|
|
740
|
+
? this.pi.subagentToolExecutors(
|
|
741
|
+
snapshot.bindings.subagents,
|
|
742
|
+
snapshot.profile.enabledSubagents,
|
|
743
|
+
{
|
|
744
|
+
...runMetadata,
|
|
745
|
+
onRegistered: async (runId) => {
|
|
746
|
+
await lifecycle.register(runId);
|
|
747
|
+
},
|
|
748
|
+
onUsage: async (event) => {
|
|
749
|
+
await lifecycle.usage(event);
|
|
750
|
+
},
|
|
751
|
+
onTerminal: async (terminal) => {
|
|
752
|
+
await lifecycle.terminal(terminal);
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
)
|
|
756
|
+
: undefined;
|
|
666
757
|
return this.pi.createTurn({
|
|
667
758
|
prepared: this.preparedPi(),
|
|
668
759
|
pinnedDescriptor: submission.assemblyDescriptor,
|
|
@@ -707,6 +798,9 @@ export abstract class AgentRuntimeKernel<
|
|
|
707
798
|
settleTool: (call) =>
|
|
708
799
|
this.settleTool(submission.submissionId, call),
|
|
709
800
|
},
|
|
801
|
+
...(toolExecutors && Object.keys(toolExecutors).length > 0
|
|
802
|
+
? { toolExecutors }
|
|
803
|
+
: {}),
|
|
710
804
|
onToolTelemetry: (event) => {
|
|
711
805
|
this._emit("ua:tool" as never, { ...event });
|
|
712
806
|
},
|
|
@@ -732,6 +826,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
732
826
|
persisted.id,
|
|
733
827
|
submission.submissionId,
|
|
734
828
|
commit.message,
|
|
829
|
+
submission,
|
|
735
830
|
);
|
|
736
831
|
this.db.runtimeEvents.insert({
|
|
737
832
|
eventId: event.eventId,
|
|
@@ -774,6 +869,29 @@ export abstract class AgentRuntimeKernel<
|
|
|
774
869
|
});
|
|
775
870
|
}
|
|
776
871
|
|
|
872
|
+
private subagentLifecycle(
|
|
873
|
+
input: RuntimeSubagentRunMetadata,
|
|
874
|
+
): RuntimeSubagentLifecycleRecorder {
|
|
875
|
+
return new RuntimeSubagentLifecycleRecorder(input, {
|
|
876
|
+
save: async (event) => {
|
|
877
|
+
this.db.transaction(() => {
|
|
878
|
+
this.db.runtimeEvents.insert({
|
|
879
|
+
eventId: event.eventId,
|
|
880
|
+
body: json({
|
|
881
|
+
type: event.kind === "subagent" ? "subagent-usage" : "lifecycle",
|
|
882
|
+
event,
|
|
883
|
+
}),
|
|
884
|
+
createdAt: Date.now(),
|
|
885
|
+
});
|
|
886
|
+
});
|
|
887
|
+
await this.drainRuntimeEvents();
|
|
888
|
+
},
|
|
889
|
+
has: (eventId) => this.db.runtimeEvents.has(eventId),
|
|
890
|
+
hasSubagentUsage: (submissionId, subagentRunId) =>
|
|
891
|
+
this.db.runtimeEvents.hasSubagentUsage(submissionId, subagentRunId),
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
|
|
777
895
|
/**
|
|
778
896
|
* 在 Agent 实例启动或从休眠唤醒后恢复 Session 本地后台状态。
|
|
779
897
|
*
|
|
@@ -822,9 +940,23 @@ export abstract class AgentRuntimeKernel<
|
|
|
822
940
|
if (payload.type === "model-usage") {
|
|
823
941
|
if (!turnEvents.onModelUsage) continue;
|
|
824
942
|
await turnEvents.onModelUsage(payload.event);
|
|
825
|
-
} else {
|
|
943
|
+
} else if (payload.type === "tool-settlement") {
|
|
826
944
|
if (!turnEvents.onToolSettled) continue;
|
|
827
945
|
await turnEvents.onToolSettled(payload.event);
|
|
946
|
+
} else if (payload.type === "subagent-usage") {
|
|
947
|
+
if (!turnEvents.onSubagentUsage) continue;
|
|
948
|
+
const confirmation = await turnEvents.onSubagentUsage(payload.event);
|
|
949
|
+
if (confirmation?.confirmed !== true) {
|
|
950
|
+
failed = true;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
} else {
|
|
954
|
+
if (!turnEvents.onLifecycleFact) continue;
|
|
955
|
+
const confirmation = await turnEvents.onLifecycleFact(payload.event);
|
|
956
|
+
if (confirmation?.confirmed !== true) {
|
|
957
|
+
failed = true;
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
828
960
|
}
|
|
829
961
|
this.db.transaction(() =>
|
|
830
962
|
this.db.runtimeEvents.markDelivered(row.eventId, Date.now())
|
|
@@ -835,6 +967,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
835
967
|
"[runtime-turn-event:degraded]",
|
|
836
968
|
json({ eventId: row.eventId, error: errorText(error) }),
|
|
837
969
|
);
|
|
970
|
+
break;
|
|
838
971
|
}
|
|
839
972
|
}
|
|
840
973
|
if (!failed) return;
|
|
@@ -1255,7 +1388,23 @@ export abstract class AgentRuntimeKernel<
|
|
|
1255
1388
|
onCompactionPersisted: (event) =>
|
|
1256
1389
|
this.db.runtimeEvents.insert({
|
|
1257
1390
|
eventId: event.eventId,
|
|
1258
|
-
body: json({
|
|
1391
|
+
body: json({
|
|
1392
|
+
type: "model-usage",
|
|
1393
|
+
event: {
|
|
1394
|
+
...event,
|
|
1395
|
+
runId: submissionId,
|
|
1396
|
+
...(this.readSubmission(submissionId)?.accountId
|
|
1397
|
+
? { accountId: this.readSubmission(submissionId)!.accountId! }
|
|
1398
|
+
: {}),
|
|
1399
|
+
...(this.readSubmission(submissionId)?.rateVersion !== null &&
|
|
1400
|
+
this.readSubmission(submissionId)?.rateVersion !== undefined
|
|
1401
|
+
? { rateVersion: this.readSubmission(submissionId)!.rateVersion! }
|
|
1402
|
+
: {}),
|
|
1403
|
+
...(this.readSubmission(submissionId)?.slotIdentity
|
|
1404
|
+
? { slotIdentity: this.readSubmission(submissionId)!.slotIdentity! }
|
|
1405
|
+
: {}),
|
|
1406
|
+
},
|
|
1407
|
+
}),
|
|
1259
1408
|
createdAt: Date.now(),
|
|
1260
1409
|
}),
|
|
1261
1410
|
});
|
|
@@ -1303,31 +1452,147 @@ export abstract class AgentRuntimeKernel<
|
|
|
1303
1452
|
? { idempotencyKey: options.idempotencyKey }
|
|
1304
1453
|
: {}),
|
|
1305
1454
|
prepareAdmission: async () => {
|
|
1455
|
+
await this.ensureRuntimeReady();
|
|
1306
1456
|
const regenerateEntry =
|
|
1307
1457
|
options.regenerate && options.userMessageId
|
|
1308
1458
|
? await this.transcript.findUserMessage(options.userMessageId)
|
|
1309
1459
|
: undefined;
|
|
1460
|
+
if (
|
|
1461
|
+
options.regenerate &&
|
|
1462
|
+
(!regenerateEntry ||
|
|
1463
|
+
userContentKey(regenerateEntry) !== userContentKey(userMessage))
|
|
1464
|
+
) {
|
|
1465
|
+
throw new Error(
|
|
1466
|
+
"Regenerate request must match an existing user message",
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
const hook = this.runtimeHooks?.onSubmissionAdmission;
|
|
1470
|
+
let existing: StoredSubmissionAdmission | null = null;
|
|
1471
|
+
if (hook) {
|
|
1472
|
+
const attemptedAt = Date.now();
|
|
1473
|
+
const reservedRunId = crypto.randomUUID();
|
|
1474
|
+
this.db.transaction(() => {
|
|
1475
|
+
const current = this.db.submissionAdmissions.find(
|
|
1476
|
+
options.requestId,
|
|
1477
|
+
options.idempotencyKey,
|
|
1478
|
+
);
|
|
1479
|
+
if (current) {
|
|
1480
|
+
// 同一 identity 重投:沿用已固定的 runId,只把这次尝试的时间推到现在,
|
|
1481
|
+
// 使它重新占住一个在途名额。
|
|
1482
|
+
if (current.status === "pending") {
|
|
1483
|
+
this.db.submissionAdmissions.recordAttempt(
|
|
1484
|
+
current.requestId,
|
|
1485
|
+
attemptedAt,
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (
|
|
1491
|
+
this.db.submissions.countPending() +
|
|
1492
|
+
this.db.submissionAdmissions.countPending(
|
|
1493
|
+
attemptedAt - ADMISSION_ATTEMPT_WINDOW_MS,
|
|
1494
|
+
) >=
|
|
1495
|
+
MAX_PENDING_SUBMISSIONS
|
|
1496
|
+
) {
|
|
1497
|
+
throw new SubmissionQueueFullError();
|
|
1498
|
+
}
|
|
1499
|
+
this.db.submissionAdmissions.begin(
|
|
1500
|
+
options.requestId,
|
|
1501
|
+
options.idempotencyKey ?? null,
|
|
1502
|
+
reservedRunId,
|
|
1503
|
+
attemptedAt,
|
|
1504
|
+
);
|
|
1505
|
+
});
|
|
1506
|
+
existing = this.db.submissionAdmissions.find(
|
|
1507
|
+
options.requestId,
|
|
1508
|
+
options.idempotencyKey,
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
const runId = existing?.runId ?? crypto.randomUUID();
|
|
1512
|
+
const admissionRequestId = existing?.requestId ?? options.requestId;
|
|
1513
|
+
const admissionIdempotencyKey = existing
|
|
1514
|
+
? existing.idempotencyKey ?? undefined
|
|
1515
|
+
: options.idempotencyKey;
|
|
1516
|
+
const admissionKey = admissionIdempotencyKey
|
|
1517
|
+
? `idempotency:${admissionIdempotencyKey}`
|
|
1518
|
+
: `request:${admissionRequestId}`;
|
|
1519
|
+
const pendingAdmission = hook && existing?.status === "pending"
|
|
1520
|
+
? this.admissionsInFlight.get(admissionKey)
|
|
1521
|
+
: undefined;
|
|
1522
|
+
const admissionPromise = pendingAdmission ?? (hook && (!existing || existing.status === "pending")
|
|
1523
|
+
? hook({
|
|
1524
|
+
requestId: admissionRequestId,
|
|
1525
|
+
...(admissionIdempotencyKey
|
|
1526
|
+
? { idempotencyKey: admissionIdempotencyKey }
|
|
1527
|
+
: {}),
|
|
1528
|
+
runId,
|
|
1529
|
+
}).then(validateAdmissionResult).catch((error) => {
|
|
1530
|
+
this.admissionsInFlight.delete(admissionKey);
|
|
1531
|
+
throw error;
|
|
1532
|
+
})
|
|
1533
|
+
: undefined);
|
|
1534
|
+
if (hook && admissionPromise && !pendingAdmission) {
|
|
1535
|
+
this.admissionsInFlight.set(
|
|
1536
|
+
admissionKey,
|
|
1537
|
+
admissionPromise,
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
let admission = existing?.status === "accepted"
|
|
1541
|
+
? {
|
|
1542
|
+
accepted: true as const,
|
|
1543
|
+
accountId: existing.accountId!,
|
|
1544
|
+
rateVersion: existing.rateVersion!,
|
|
1545
|
+
slotIdentity: existing.slotIdentity!,
|
|
1546
|
+
}
|
|
1547
|
+
: existing?.status === "rejected"
|
|
1548
|
+
? {
|
|
1549
|
+
accepted: false as const,
|
|
1550
|
+
code: existing.code ?? "admission_rejected",
|
|
1551
|
+
retryable: existing.retryable ?? false,
|
|
1552
|
+
}
|
|
1553
|
+
: admissionPromise
|
|
1554
|
+
? await admissionPromise
|
|
1555
|
+
: undefined;
|
|
1310
1556
|
return () => {
|
|
1311
|
-
|
|
1557
|
+
if (admission && !admission.accepted) {
|
|
1558
|
+
this.db.submissionAdmissions.saveRejected(
|
|
1559
|
+
admissionRequestId,
|
|
1560
|
+
runId,
|
|
1561
|
+
admission.code,
|
|
1562
|
+
admission.retryable ?? false,
|
|
1563
|
+
);
|
|
1564
|
+
const rejected = this.insertSkippedSubmission({
|
|
1565
|
+
submissionId: runId,
|
|
1566
|
+
requestId: admissionRequestId,
|
|
1567
|
+
idempotencyKey: admissionIdempotencyKey ?? null,
|
|
1568
|
+
createdAt: userMessage.timestamp,
|
|
1569
|
+
code: admission.code,
|
|
1570
|
+
retryable: admission.retryable ?? false,
|
|
1571
|
+
});
|
|
1572
|
+
this.admissionsInFlight.delete(admissionKey);
|
|
1573
|
+
return {
|
|
1574
|
+
...rejected,
|
|
1575
|
+
admissionRejected: true,
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
const submissionId = runId;
|
|
1312
1579
|
const assistantMessageId = crypto.randomUUID();
|
|
1313
1580
|
const createdAt = userMessage.timestamp;
|
|
1314
1581
|
const userMessageId =
|
|
1315
1582
|
options.userMessageId ?? crypto.randomUUID();
|
|
1316
|
-
if (
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1583
|
+
if (admission?.accepted) {
|
|
1584
|
+
this.db.submissionAdmissions.saveAccepted(
|
|
1585
|
+
admissionRequestId,
|
|
1586
|
+
runId,
|
|
1587
|
+
admission.accountId,
|
|
1588
|
+
admission.rateVersion,
|
|
1589
|
+
admission.slotIdentity,
|
|
1590
|
+
);
|
|
1326
1591
|
}
|
|
1327
1592
|
this.db.submissions.insert({
|
|
1328
1593
|
submissionId,
|
|
1329
|
-
requestId:
|
|
1330
|
-
idempotencyKey:
|
|
1594
|
+
requestId: admissionRequestId,
|
|
1595
|
+
idempotencyKey: admissionIdempotencyKey ?? null,
|
|
1331
1596
|
createdAt,
|
|
1332
1597
|
assemblyRevision: "",
|
|
1333
1598
|
assemblyDescriptor: "",
|
|
@@ -1340,7 +1605,28 @@ export abstract class AgentRuntimeKernel<
|
|
|
1340
1605
|
regenerateMessageId: options.regenerate
|
|
1341
1606
|
? options.userMessageId ?? null
|
|
1342
1607
|
: null,
|
|
1608
|
+
runId,
|
|
1609
|
+
accountId: admission?.accepted ? admission.accountId : null,
|
|
1610
|
+
rateVersion: admission?.accepted ? admission.rateVersion : null,
|
|
1611
|
+
slotIdentity: admission?.accepted ? admission.slotIdentity : null,
|
|
1343
1612
|
});
|
|
1613
|
+
if (admission?.accepted) {
|
|
1614
|
+
const fact: RuntimeLifecycleFact = {
|
|
1615
|
+
kind: "submission-admitted",
|
|
1616
|
+
eventId: `${submissionId}:admitted`,
|
|
1617
|
+
submissionId,
|
|
1618
|
+
runId: submissionId,
|
|
1619
|
+
accountId: admission.accountId,
|
|
1620
|
+
rateVersion: admission.rateVersion,
|
|
1621
|
+
slotIdentity: admission.slotIdentity,
|
|
1622
|
+
};
|
|
1623
|
+
this.db.runtimeEvents.insert({
|
|
1624
|
+
eventId: fact.eventId,
|
|
1625
|
+
body: json({ type: "lifecycle", event: fact }),
|
|
1626
|
+
createdAt,
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
if (hook) this.admissionsInFlight.delete(admissionKey);
|
|
1344
1630
|
return this.readSubmission(submissionId)!;
|
|
1345
1631
|
};
|
|
1346
1632
|
},
|
|
@@ -1364,6 +1650,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1364
1650
|
const submitted = await this.submissions.submit(
|
|
1365
1651
|
this.submissionInput(userMessage, options),
|
|
1366
1652
|
);
|
|
1653
|
+
await this.drainRuntimeEvents();
|
|
1367
1654
|
await this.broadcastApprovals();
|
|
1368
1655
|
return submitted;
|
|
1369
1656
|
}
|
|
@@ -1380,7 +1667,10 @@ export abstract class AgentRuntimeKernel<
|
|
|
1380
1667
|
this.submissionInput(userMessage, options),
|
|
1381
1668
|
timeoutMs,
|
|
1382
1669
|
);
|
|
1383
|
-
if (submitted)
|
|
1670
|
+
if (submitted) {
|
|
1671
|
+
await this.drainRuntimeEvents();
|
|
1672
|
+
await this.broadcastApprovals();
|
|
1673
|
+
}
|
|
1384
1674
|
return submitted;
|
|
1385
1675
|
}
|
|
1386
1676
|
|
|
@@ -1410,6 +1700,46 @@ export abstract class AgentRuntimeKernel<
|
|
|
1410
1700
|
return submission;
|
|
1411
1701
|
}
|
|
1412
1702
|
|
|
1703
|
+
private insertSubmissionTerminalFact(submission: StoredSubmission): void {
|
|
1704
|
+
const terminalFact: RuntimeLifecycleFact = {
|
|
1705
|
+
kind: "submission-terminal",
|
|
1706
|
+
eventId: `${submission.submissionId}:terminal`,
|
|
1707
|
+
submissionId: submission.submissionId,
|
|
1708
|
+
runId: submission.runId ?? submission.submissionId,
|
|
1709
|
+
status: submission.status as "completed" | "aborted" | "skipped" | "error",
|
|
1710
|
+
childRegistrationClosed: true,
|
|
1711
|
+
zeroUsage: !this.db.runtimeEvents.hasNonZeroModelUsage(
|
|
1712
|
+
submission.submissionId,
|
|
1713
|
+
),
|
|
1714
|
+
...(submission.accountId ? { accountId: submission.accountId } : {}),
|
|
1715
|
+
...(submission.rateVersion !== null
|
|
1716
|
+
? { rateVersion: submission.rateVersion }
|
|
1717
|
+
: {}),
|
|
1718
|
+
...(submission.slotIdentity
|
|
1719
|
+
? { slotIdentity: submission.slotIdentity }
|
|
1720
|
+
: {}),
|
|
1721
|
+
};
|
|
1722
|
+
this.db.runtimeEvents.insert({
|
|
1723
|
+
eventId: terminalFact.eventId,
|
|
1724
|
+
body: json({ type: "lifecycle", event: terminalFact }),
|
|
1725
|
+
createdAt: submission.completedAt ?? Date.now(),
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
private insertSkippedSubmission(input: {
|
|
1730
|
+
submissionId: string;
|
|
1731
|
+
requestId: string;
|
|
1732
|
+
idempotencyKey: string | null;
|
|
1733
|
+
createdAt: number;
|
|
1734
|
+
code: string;
|
|
1735
|
+
retryable: boolean;
|
|
1736
|
+
}): StoredSubmission {
|
|
1737
|
+
this.db.submissions.insertRejected(input);
|
|
1738
|
+
const skipped = this.readSubmission(input.submissionId)!;
|
|
1739
|
+
this.insertSubmissionTerminalFact(skipped);
|
|
1740
|
+
return skipped;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1413
1743
|
// 作用:读取某个 Submission 已持久的全部 Pi 恢复里程碑。
|
|
1414
1744
|
// 调用:恢复决策器每次检查或应用命令前调用。
|
|
1415
1745
|
// 原因:决策只依赖持久事实,重启后才会得到同样结果。
|
|
@@ -1713,6 +2043,23 @@ export abstract class AgentRuntimeKernel<
|
|
|
1713
2043
|
}
|
|
1714
2044
|
if (isTerminalSubmissionStatus(latest.status)) return latest;
|
|
1715
2045
|
|
|
2046
|
+
for (const steer of this.db.steers.listForSubmission(latest.submissionId)) {
|
|
2047
|
+
const userMessage = JSON.parse(steer.canonicalJson) as PiCanonicalUserInput;
|
|
2048
|
+
const uiMessage = steer.uiMessageJson
|
|
2049
|
+
? JSON.parse(steer.uiMessageJson) as UIMessage & { role: "user" }
|
|
2050
|
+
: undefined;
|
|
2051
|
+
const submitted = await this.submitMessage(userMessage, {
|
|
2052
|
+
requestId: steer.messageId,
|
|
2053
|
+
idempotencyKey: steer.messageId,
|
|
2054
|
+
userMessageId: steer.messageId,
|
|
2055
|
+
...(uiMessage ? { userMessage: uiMessage } : {}),
|
|
2056
|
+
});
|
|
2057
|
+
this.ctx.waitUntil(submitted.completion);
|
|
2058
|
+
this.db.transaction(() =>
|
|
2059
|
+
this.db.steers.deleteByMessageId(steer.messageId)
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
1716
2063
|
const durableIntent = this.decidePiRecovery(latest).terminal;
|
|
1717
2064
|
const effectiveOutcome = latest.abortReason
|
|
1718
2065
|
? "aborted"
|
|
@@ -1742,31 +2089,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
1742
2089
|
: effectiveOutcome === "aborted"
|
|
1743
2090
|
? "aborted"
|
|
1744
2091
|
: "error";
|
|
1745
|
-
for (const steer of this.db.steers.listForSubmission(
|
|
1746
|
-
latest.submissionId,
|
|
1747
|
-
)) {
|
|
1748
|
-
if (!this.db.submissions.findByRequestId(steer.messageId)) {
|
|
1749
|
-
this.db.submissions.insert({
|
|
1750
|
-
submissionId: steer.steerId,
|
|
1751
|
-
requestId: steer.messageId,
|
|
1752
|
-
idempotencyKey: steer.messageId,
|
|
1753
|
-
createdAt: steer.createdAt,
|
|
1754
|
-
assemblyRevision: latest.assemblyRevision,
|
|
1755
|
-
assemblyDescriptor: latest.assemblyDescriptor,
|
|
1756
|
-
assistantMessageId: crypto.randomUUID(),
|
|
1757
|
-
queuedInputJson: steer.canonicalJson,
|
|
1758
|
-
queuedUiMessageJson: steer.uiMessageJson,
|
|
1759
|
-
userMessageId: steer.messageId,
|
|
1760
|
-
regenerateMessageId: null,
|
|
1761
|
-
});
|
|
1762
|
-
}
|
|
1763
|
-
this.db.steers.deleteByMessageId(steer.messageId);
|
|
1764
|
-
}
|
|
1765
2092
|
const terminal = this.updateSubmission(
|
|
1766
2093
|
latest.submissionId,
|
|
1767
2094
|
status,
|
|
1768
2095
|
effectiveMessage,
|
|
1769
2096
|
);
|
|
2097
|
+
this.insertSubmissionTerminalFact(terminal);
|
|
1770
2098
|
if (
|
|
1771
2099
|
effectiveOutcome === "aborted" &&
|
|
1772
2100
|
effectiveMessage === USER_STOP_REASON
|
|
@@ -3048,7 +3376,9 @@ export abstract class AgentRuntimeKernel<
|
|
|
3048
3376
|
const reason = "Moved to active Turn";
|
|
3049
3377
|
this.appendTerminalIntent(latest, "aborted", reason);
|
|
3050
3378
|
this.db.submissions.updateAbortReason(submissionId, reason);
|
|
3051
|
-
this.
|
|
3379
|
+
this.insertSubmissionTerminalFact(
|
|
3380
|
+
this.updateSubmission(submissionId, "aborted", reason),
|
|
3381
|
+
);
|
|
3052
3382
|
return true;
|
|
3053
3383
|
});
|
|
3054
3384
|
if (!moved) {
|
|
@@ -3064,6 +3394,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
3064
3394
|
active.agent.steer(message, queued.userMessageId);
|
|
3065
3395
|
}
|
|
3066
3396
|
} finally {
|
|
3397
|
+
await this.drainRuntimeEvents();
|
|
3067
3398
|
await this.broadcastApprovals();
|
|
3068
3399
|
}
|
|
3069
3400
|
return {
|
|
@@ -3101,13 +3432,22 @@ export abstract class AgentRuntimeKernel<
|
|
|
3101
3432
|
SCHEDULED_STABLE_TIMEOUT_MS,
|
|
3102
3433
|
);
|
|
3103
3434
|
if (!submitted) {
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3435
|
+
const skipped = this.db.transaction(() => {
|
|
3436
|
+
const duplicate = options?.idempotencyKey
|
|
3437
|
+
? this.db.submissions.findByIdempotencyKey(options.idempotencyKey)
|
|
3438
|
+
: this.db.submissions.findByRequestId(requestId);
|
|
3439
|
+
return duplicate ?? this.insertSkippedSubmission({
|
|
3440
|
+
submissionId: requestId,
|
|
3441
|
+
requestId,
|
|
3442
|
+
idempotencyKey: options?.idempotencyKey ?? null,
|
|
3443
|
+
createdAt: Date.now(),
|
|
3444
|
+
code: "Runtime not stable within timeout",
|
|
3445
|
+
retryable: true,
|
|
3446
|
+
});
|
|
3447
|
+
}) as StoredSubmission;
|
|
3448
|
+
await this.drainRuntimeEvents();
|
|
3449
|
+
await this.broadcastApprovals();
|
|
3450
|
+
return skipped;
|
|
3111
3451
|
}
|
|
3112
3452
|
this.ctx.waitUntil(submitted.completion.then(() => undefined));
|
|
3113
3453
|
return submitted.receipt;
|
|
@@ -3352,6 +3692,10 @@ export abstract class AgentRuntimeKernel<
|
|
|
3352
3692
|
// 原因:分离的子运行不进 Submission 表,不在这里重算,Host 的列表会显示成已经空闲。
|
|
3353
3693
|
override async onAgentToolStart(run: AgentToolRunInfo): Promise<void> {
|
|
3354
3694
|
await super.onAgentToolStart(run);
|
|
3695
|
+
const metadata = runtimeSubagentRunMetadata(run.inputPreview);
|
|
3696
|
+
if (metadata) {
|
|
3697
|
+
await this.subagentLifecycle(metadata).register(run.runId);
|
|
3698
|
+
}
|
|
3355
3699
|
await this.broadcastApprovals();
|
|
3356
3700
|
}
|
|
3357
3701
|
|
|
@@ -3363,6 +3707,40 @@ export abstract class AgentRuntimeKernel<
|
|
|
3363
3707
|
result: AgentToolLifecycleResult,
|
|
3364
3708
|
): Promise<void> {
|
|
3365
3709
|
await super.onAgentToolFinish(run, result);
|
|
3710
|
+
const metadata = runtimeSubagentRunMetadata(run.inputPreview);
|
|
3711
|
+
if (metadata) {
|
|
3712
|
+
const lifecycle = this.subagentLifecycle(metadata);
|
|
3713
|
+
await lifecycle.register(run.runId);
|
|
3714
|
+
if (!(result.status === "interrupted" && result.childStillRunning)) {
|
|
3715
|
+
const completion = decodeSubagentCompletion(result.summary);
|
|
3716
|
+
const hasUsage = completion && !isZeroSubagentUsage(completion.usage);
|
|
3717
|
+
const status = subagentTerminalStatus(result.status);
|
|
3718
|
+
if (completion && hasUsage) {
|
|
3719
|
+
await lifecycle.usage({
|
|
3720
|
+
eventId: `${run.runId}:usage`,
|
|
3721
|
+
submissionId: metadata.submissionId,
|
|
3722
|
+
runId: run.runId,
|
|
3723
|
+
subagentRunId: run.runId,
|
|
3724
|
+
parentRunId: metadata.parentRunId,
|
|
3725
|
+
...(metadata.accountId ? { accountId: metadata.accountId } : {}),
|
|
3726
|
+
...(metadata.rateVersion !== undefined
|
|
3727
|
+
? { rateVersion: metadata.rateVersion }
|
|
3728
|
+
: {}),
|
|
3729
|
+
...(metadata.slotIdentity
|
|
3730
|
+
? { slotIdentity: metadata.slotIdentity }
|
|
3731
|
+
: {}),
|
|
3732
|
+
kind: "subagent",
|
|
3733
|
+
status,
|
|
3734
|
+
usage: completion.usage,
|
|
3735
|
+
});
|
|
3736
|
+
}
|
|
3737
|
+
await lifecycle.terminal({
|
|
3738
|
+
subagentRunId: run.runId,
|
|
3739
|
+
status,
|
|
3740
|
+
zeroUsage: !hasUsage,
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3366
3744
|
await this.broadcastApprovals();
|
|
3367
3745
|
}
|
|
3368
3746
|
|
|
@@ -3370,8 +3748,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
3370
3748
|
run: AgentToolRunInfo,
|
|
3371
3749
|
result: AgentToolLifecycleResult,
|
|
3372
3750
|
): Promise<void> {
|
|
3751
|
+
const completion = decodeSubagentCompletion(result.summary);
|
|
3373
3752
|
const outcome = result.status === "completed"
|
|
3374
|
-
?
|
|
3753
|
+
? completion
|
|
3754
|
+
? JSON.stringify(completion.output)
|
|
3755
|
+
: result.summary ?? "Completed without a result."
|
|
3375
3756
|
: result.error ?? `Background run ${result.status}.`;
|
|
3376
3757
|
await this.submitPrompt(
|
|
3377
3758
|
[
|