@springbrand/agent-runtime 0.2.0-alpha.15 → 0.2.0-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/adapter/cloudflare/sandbox/adapter.ts +61 -36
- package/src/adapter/cloudflare/universal-agent/preparation.ts +0 -2
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +23 -18
- package/src/db/index.ts +5 -0
- package/src/db/schema.ts +15 -0
- package/src/db/telemetry-outbox.repo.ts +151 -0
- package/src/index.ts +1 -0
- package/src/kernel/approval-lifecycle.ts +35 -3
- package/src/kernel/bindings.ts +2 -1
- package/src/kernel/interaction-lifecycle.ts +35 -6
- package/src/layers/orchestration/temporary-agent/workspace.ts +4 -4
- package/src/lib/prompt.ts +22 -15
- package/src/pi/assembly/context.ts +2 -2
- package/src/pi/message/conversion.ts +13 -1
- package/src/pi/runtime-adapter/assembly.ts +1 -0
- package/src/pi/runtime-adapter/execution.ts +63 -27
- package/src/pi/runtime-adapter/models.ts +144 -44
- package/src/pi/tool/ai-adapter.ts +2 -2
- package/src/pi/tool/base.ts +31 -25
- package/src/pi/tool/compiler.ts +6 -102
- package/src/pi/turn/tool-recovery.ts +11 -1
- package/src/runtime-agent.ts +24 -0
- package/src/runtime-assembler.ts +38 -19
- package/src/runtime-definition.ts +2 -0
- package/src/runtime.ts +362 -20
- package/src/telemetry/contract.ts +389 -0
- package/src/telemetry/coordinator.ts +143 -0
- package/src/telemetry/delivery.ts +138 -0
- package/src/telemetry/ids.ts +60 -0
- package/src/telemetry/index.ts +7 -0
- package/src/telemetry/recorder.ts +61 -0
- package/src/telemetry/runtime-telemetry.ts +484 -0
- package/src/telemetry/sanitize.ts +97 -0
- package/src/tool-registry.ts +18 -11
- package/src/lib/telemetry-dev.ts +0 -47
package/src/runtime.ts
CHANGED
|
@@ -58,8 +58,10 @@ import {
|
|
|
58
58
|
} from "./runtime-assembler";
|
|
59
59
|
import type { RuntimeAgentHooks } from "./runtime-definition";
|
|
60
60
|
import type { RuntimeTurnEventsPort } from "./kernel/bindings";
|
|
61
|
+
import {
|
|
62
|
+
RuntimeTelemetryCoordinator,
|
|
63
|
+
} from "./telemetry";
|
|
61
64
|
import { connectConfiguredMcpServers } from "./lib/mcp";
|
|
62
|
-
import { installConsoleSink } from "./lib/telemetry-dev";
|
|
63
65
|
import {
|
|
64
66
|
PiRuntimeAdapter,
|
|
65
67
|
MODEL_STREAM_STALL_TIMEOUT_MS,
|
|
@@ -190,6 +192,8 @@ interface ActiveTurn {
|
|
|
190
192
|
interface PlannedContinuationData {
|
|
191
193
|
readonly submissionId: string;
|
|
192
194
|
readonly requestId: string;
|
|
195
|
+
/** Identifies the yielded execution slice so retries dedupe without swallowing the next slice. */
|
|
196
|
+
readonly continuationId: string;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
// 作用:把任意异常整理成可以持久化或发给客户端的文字。
|
|
@@ -339,6 +343,8 @@ function requestedCapabilitiesOf(metadata: unknown): RequestedCapability[] {
|
|
|
339
343
|
|
|
340
344
|
interface PendingTemporaryAgentApproval {
|
|
341
345
|
receipt: ApprovalReceipt;
|
|
346
|
+
submissionId?: string;
|
|
347
|
+
toolCallId: string;
|
|
342
348
|
resolve(decision: TemporaryAgentApprovalDecision): void;
|
|
343
349
|
}
|
|
344
350
|
|
|
@@ -375,6 +381,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
375
381
|
private runtimeGatewaySession?: RuntimeGatewaySession;
|
|
376
382
|
private runtimeTurnEvents?: RuntimeTurnEventsPort;
|
|
377
383
|
private runtimeHooks?: RuntimeAgentHooks;
|
|
384
|
+
private telemetryCoordinator?: RuntimeTelemetryCoordinator;
|
|
378
385
|
private piAdapter?: PiRuntimeAdapter;
|
|
379
386
|
private readonly transcript: PiRuntimeTranscript;
|
|
380
387
|
private readonly submissions: SubmissionLifecycle<
|
|
@@ -422,6 +429,27 @@ export abstract class AgentRuntimeKernel<
|
|
|
422
429
|
});
|
|
423
430
|
}
|
|
424
431
|
|
|
432
|
+
private get telemetry(): RuntimeTelemetryCoordinator {
|
|
433
|
+
return this.telemetryCoordinator ??= new RuntimeTelemetryCoordinator({
|
|
434
|
+
outbox: () => this.db?.telemetry,
|
|
435
|
+
waitUntil: (promise) => this.ctx.waitUntil(promise),
|
|
436
|
+
scheduleRetry: async (delaySeconds, idempotent) => {
|
|
437
|
+
await this.schedule(
|
|
438
|
+
delaySeconds,
|
|
439
|
+
"_drainTelemetry",
|
|
440
|
+
undefined,
|
|
441
|
+
{ idempotent },
|
|
442
|
+
);
|
|
443
|
+
},
|
|
444
|
+
onError: (error) => {
|
|
445
|
+
console.warn(
|
|
446
|
+
"[runtime-telemetry:degraded]",
|
|
447
|
+
json({ error: errorText(error) }),
|
|
448
|
+
);
|
|
449
|
+
},
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
425
453
|
/**
|
|
426
454
|
* 为一个 Cloudflare Durable Object 实例建立持久化与执行协作器。
|
|
427
455
|
*
|
|
@@ -510,6 +538,27 @@ export abstract class AgentRuntimeKernel<
|
|
|
510
538
|
);
|
|
511
539
|
},
|
|
512
540
|
onApprovalsChanged: () => this.broadcastApprovals(),
|
|
541
|
+
onTelemetryRequested: (approval) => {
|
|
542
|
+
this.telemetry.capture("approvalRequested", {
|
|
543
|
+
submissionId: approval.submissionId,
|
|
544
|
+
toolCallId: approval.toolCallId,
|
|
545
|
+
executionId: approval.executionId,
|
|
546
|
+
toolName: approval.toolName,
|
|
547
|
+
summary: approval.summary,
|
|
548
|
+
input: safeParseJson(approval.inputJson),
|
|
549
|
+
occurredAt: approval.createdAt,
|
|
550
|
+
});
|
|
551
|
+
},
|
|
552
|
+
onTelemetryDecided: (approval, decision) => {
|
|
553
|
+
this.telemetry.capture("approvalDecided", {
|
|
554
|
+
submissionId: approval.submissionId,
|
|
555
|
+
toolCallId: approval.toolCallId,
|
|
556
|
+
executionId: approval.executionId,
|
|
557
|
+
decision,
|
|
558
|
+
...(approval.reason ? { reason: approval.reason } : {}),
|
|
559
|
+
occurredAt: approval.decidedAt ?? Date.now(),
|
|
560
|
+
});
|
|
561
|
+
},
|
|
513
562
|
});
|
|
514
563
|
this.interactions = new InteractionLifecycle({
|
|
515
564
|
db: this.db,
|
|
@@ -526,6 +575,38 @@ export abstract class AgentRuntimeKernel<
|
|
|
526
575
|
await this.submissions.recover(submissionId);
|
|
527
576
|
},
|
|
528
577
|
onInteractionsChanged: () => this.broadcastApprovals(),
|
|
578
|
+
onTelemetryRequested: (interaction) => {
|
|
579
|
+
this.telemetry.capture("interactionRequested", {
|
|
580
|
+
submissionId: interaction.submissionId,
|
|
581
|
+
toolCallId: interaction.toolCallId,
|
|
582
|
+
interactionId: interaction.interactionId,
|
|
583
|
+
toolName: interaction.toolName,
|
|
584
|
+
input: safeParseJson(interaction.inputJson),
|
|
585
|
+
occurredAt: interaction.createdAt,
|
|
586
|
+
});
|
|
587
|
+
},
|
|
588
|
+
onTelemetrySettled: (interaction) => {
|
|
589
|
+
if (interaction.status === "responded") {
|
|
590
|
+
this.telemetry.capture("interactionResponded", {
|
|
591
|
+
submissionId: interaction.submissionId,
|
|
592
|
+
toolCallId: interaction.toolCallId,
|
|
593
|
+
interactionId: interaction.interactionId,
|
|
594
|
+
...(interaction.responseJson
|
|
595
|
+
? { response: safeParseJson(interaction.responseJson) }
|
|
596
|
+
: {}),
|
|
597
|
+
occurredAt: interaction.respondedAt ?? Date.now(),
|
|
598
|
+
});
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (interaction.status === "cancelled") {
|
|
602
|
+
this.telemetry.capture("interactionCancelled", {
|
|
603
|
+
submissionId: interaction.submissionId,
|
|
604
|
+
toolCallId: interaction.toolCallId,
|
|
605
|
+
interactionId: interaction.interactionId,
|
|
606
|
+
occurredAt: interaction.respondedAt ?? Date.now(),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
},
|
|
529
610
|
});
|
|
530
611
|
}
|
|
531
612
|
|
|
@@ -671,9 +752,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
671
752
|
for (const [index, guard] of candidate.commitGuards.entries()) {
|
|
672
753
|
await withRuntimeLoadTimeout(`commit-guard:${index}`, guard);
|
|
673
754
|
}
|
|
674
|
-
if (candidateSnapshot.bindings.platform.telemetryConsole) {
|
|
675
|
-
installConsoleSink();
|
|
676
|
-
}
|
|
677
755
|
this.pi.activate(candidateSnapshot);
|
|
678
756
|
activated = true;
|
|
679
757
|
repin?.commit();
|
|
@@ -681,6 +759,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
681
759
|
this.runtimeRevision = revision;
|
|
682
760
|
this.runtimePi = prepared;
|
|
683
761
|
this.runtimeGatewaySession = gatewaySession;
|
|
762
|
+
this.telemetry.configure(candidate.telemetry);
|
|
684
763
|
} catch (error) {
|
|
685
764
|
repin?.abort();
|
|
686
765
|
if (activated && previous) this.pi.activate(previous);
|
|
@@ -707,6 +786,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
707
786
|
}
|
|
708
787
|
}
|
|
709
788
|
|
|
789
|
+
async _drainTelemetry(): Promise<void> {
|
|
790
|
+
await this.ensureRuntimeReady();
|
|
791
|
+
await this.telemetry.drainScheduled();
|
|
792
|
+
}
|
|
793
|
+
|
|
710
794
|
// 作用:为某个已准入 Submission 建立一次 Pi Turn 执行适配器。
|
|
711
795
|
// 调用:新 Turn、恢复 Turn 和审批后重试 Tool 都在开始 Pi 执行前调用。
|
|
712
796
|
// 原因:在这里一次性固定 revision、持久化回调与终态回调,防止各执行路径绕过同一耐久性规则。
|
|
@@ -803,12 +887,43 @@ export abstract class AgentRuntimeKernel<
|
|
|
803
887
|
settleTool: (call) =>
|
|
804
888
|
this.settleTool(submission.submissionId, call),
|
|
805
889
|
},
|
|
890
|
+
onGeneration: (event) => {
|
|
891
|
+
if (event.type === "started") {
|
|
892
|
+
this.telemetry.capture("generationStarted", {
|
|
893
|
+
submissionId: submission.submissionId,
|
|
894
|
+
generationId: event.generationId,
|
|
895
|
+
provider: event.provider,
|
|
896
|
+
model: event.model,
|
|
897
|
+
input: event.input,
|
|
898
|
+
occurredAt: event.timestamp,
|
|
899
|
+
});
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
this.telemetry.capture("generationFinished", {
|
|
903
|
+
submissionId: submission.submissionId,
|
|
904
|
+
generationId: event.generationId,
|
|
905
|
+
outcome: event.outcome === "success"
|
|
906
|
+
? "completed"
|
|
907
|
+
: event.outcome === "cancelled"
|
|
908
|
+
? "cancelled"
|
|
909
|
+
: "failed",
|
|
910
|
+
durationMs: event.durationMs,
|
|
911
|
+
...(event.usage
|
|
912
|
+
? {
|
|
913
|
+
inputTokens: event.usage.input,
|
|
914
|
+
outputTokens: event.usage.output,
|
|
915
|
+
totalTokens: event.usage.totalTokens,
|
|
916
|
+
cost: event.usage.cost.total,
|
|
917
|
+
}
|
|
918
|
+
: {}),
|
|
919
|
+
...(event.errorName ? { error: event.errorName } : {}),
|
|
920
|
+
...(event.output ? { output: event.output } : {}),
|
|
921
|
+
occurredAt: event.timestamp,
|
|
922
|
+
});
|
|
923
|
+
},
|
|
806
924
|
...(toolExecutors && Object.keys(toolExecutors).length > 0
|
|
807
925
|
? { toolExecutors }
|
|
808
926
|
: {}),
|
|
809
|
-
onToolTelemetry: (event) => {
|
|
810
|
-
this._emit("ua:tool" as never, { ...event });
|
|
811
|
-
},
|
|
812
927
|
transformContext: (messages, signal) =>
|
|
813
928
|
this.transformPiContext(submission.submissionId, messages, signal),
|
|
814
929
|
abortReason: () =>
|
|
@@ -911,6 +1026,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
911
1026
|
*/
|
|
912
1027
|
async onStart(): Promise<void> {
|
|
913
1028
|
this.runtimeLoad.reset();
|
|
1029
|
+
this.telemetry.recover(() => this.ensureRuntimeReady());
|
|
914
1030
|
if (this.db.runtimeEvents.hasPending()) {
|
|
915
1031
|
this.ctx.waitUntil(
|
|
916
1032
|
this.ensureRuntimeReady().then(() => this.drainRuntimeEvents()),
|
|
@@ -1012,7 +1128,54 @@ export abstract class AgentRuntimeKernel<
|
|
|
1012
1128
|
context: TemporaryAgentRunContext,
|
|
1013
1129
|
execute: TemporaryAgentExecutor,
|
|
1014
1130
|
): Promise<string> {
|
|
1015
|
-
return this.temporaryAgents.run(
|
|
1131
|
+
return this.temporaryAgents.run(
|
|
1132
|
+
request,
|
|
1133
|
+
async (normalized) => {
|
|
1134
|
+
const submission = this.findSubmissionByRequest(context.requestId);
|
|
1135
|
+
const subagentId = `temporary:${context.toolCallId}`;
|
|
1136
|
+
const startedAt = Date.now();
|
|
1137
|
+
if (submission) {
|
|
1138
|
+
this.telemetry.capture("subagentStarted", {
|
|
1139
|
+
submissionId: submission.submissionId,
|
|
1140
|
+
toolCallId: context.toolCallId,
|
|
1141
|
+
subagentId,
|
|
1142
|
+
agentType: normalized.subagentName,
|
|
1143
|
+
prompt: {
|
|
1144
|
+
instructions: normalized.instructions,
|
|
1145
|
+
task: normalized.task,
|
|
1146
|
+
},
|
|
1147
|
+
occurredAt: startedAt,
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
try {
|
|
1151
|
+
const output = await execute(normalized);
|
|
1152
|
+
if (submission) {
|
|
1153
|
+
this.telemetry.capture("subagentFinished", {
|
|
1154
|
+
submissionId: submission.submissionId,
|
|
1155
|
+
toolCallId: context.toolCallId,
|
|
1156
|
+
subagentId,
|
|
1157
|
+
outcome: "completed",
|
|
1158
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
1159
|
+
output,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
return output;
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
if (submission) {
|
|
1165
|
+
this.telemetry.capture("subagentFinished", {
|
|
1166
|
+
submissionId: submission.submissionId,
|
|
1167
|
+
toolCallId: context.toolCallId,
|
|
1168
|
+
subagentId,
|
|
1169
|
+
outcome: context.signal.aborted ? "cancelled" : "failed",
|
|
1170
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
1171
|
+
error: errorText(error),
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
throw error;
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
context.signal,
|
|
1178
|
+
);
|
|
1016
1179
|
}
|
|
1017
1180
|
|
|
1018
1181
|
/**
|
|
@@ -1032,6 +1195,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1032
1195
|
`temporary agent approval already pending: ${request.executionId}`,
|
|
1033
1196
|
);
|
|
1034
1197
|
}
|
|
1198
|
+
const submission = this.findSubmissionByRequest(request.requestId);
|
|
1035
1199
|
return new Promise((resolve, reject) => {
|
|
1036
1200
|
this.temporaryAgentApprovals.set(request.executionId, {
|
|
1037
1201
|
receipt: {
|
|
@@ -1044,8 +1208,20 @@ export abstract class AgentRuntimeKernel<
|
|
|
1044
1208
|
inputJson: JSON.stringify(request.input ?? null),
|
|
1045
1209
|
requestId: request.requestId,
|
|
1046
1210
|
},
|
|
1211
|
+
...(submission ? { submissionId: submission.submissionId } : {}),
|
|
1212
|
+
toolCallId: request.toolCallId,
|
|
1047
1213
|
resolve,
|
|
1048
1214
|
});
|
|
1215
|
+
if (submission) {
|
|
1216
|
+
this.telemetry.capture("approvalRequested", {
|
|
1217
|
+
submissionId: submission.submissionId,
|
|
1218
|
+
toolCallId: request.toolCallId,
|
|
1219
|
+
executionId: request.executionId,
|
|
1220
|
+
toolName: request.toolName,
|
|
1221
|
+
summary: `${request.subagentName} requests ${request.toolName}`,
|
|
1222
|
+
input: request.input,
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1049
1225
|
void this.broadcastApprovals().catch((error) => {
|
|
1050
1226
|
this.temporaryAgentApprovals.delete(request.executionId);
|
|
1051
1227
|
reject(error);
|
|
@@ -1069,6 +1245,15 @@ export abstract class AgentRuntimeKernel<
|
|
|
1069
1245
|
if (!pending) return { ok: false };
|
|
1070
1246
|
this.temporaryAgentApprovals.delete(executionId);
|
|
1071
1247
|
pending.resolve({ approved: false, reason });
|
|
1248
|
+
if (pending.submissionId) {
|
|
1249
|
+
this.telemetry.capture("approvalDecided", {
|
|
1250
|
+
submissionId: pending.submissionId!,
|
|
1251
|
+
toolCallId: pending.toolCallId,
|
|
1252
|
+
executionId,
|
|
1253
|
+
decision: "cancelled",
|
|
1254
|
+
reason,
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1072
1257
|
await this.broadcastApprovals();
|
|
1073
1258
|
return { ok: true };
|
|
1074
1259
|
}
|
|
@@ -1390,7 +1575,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1390
1575
|
),
|
|
1391
1576
|
signal,
|
|
1392
1577
|
submissionId,
|
|
1393
|
-
onCompactionPersisted: (event) =>
|
|
1578
|
+
onCompactionPersisted: (event) => {
|
|
1394
1579
|
this.db.runtimeEvents.insert({
|
|
1395
1580
|
eventId: event.eventId,
|
|
1396
1581
|
body: json({
|
|
@@ -1411,7 +1596,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
1411
1596
|
},
|
|
1412
1597
|
}),
|
|
1413
1598
|
createdAt: Date.now(),
|
|
1414
|
-
})
|
|
1599
|
+
});
|
|
1600
|
+
this.telemetry.capture("contextCompacted", {
|
|
1601
|
+
submissionId,
|
|
1602
|
+
compactionId: event.eventId,
|
|
1603
|
+
inputTokens: event.usage.input,
|
|
1604
|
+
outputTokens: event.usage.output,
|
|
1605
|
+
});
|
|
1606
|
+
},
|
|
1415
1607
|
});
|
|
1416
1608
|
await this.drainRuntimeEvents();
|
|
1417
1609
|
return compacted;
|
|
@@ -1631,6 +1823,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
1631
1823
|
createdAt,
|
|
1632
1824
|
});
|
|
1633
1825
|
}
|
|
1826
|
+
this.telemetry.capture("turnAdmitted", {
|
|
1827
|
+
submissionId,
|
|
1828
|
+
requestId: admissionRequestId,
|
|
1829
|
+
...(admissionIdempotencyKey
|
|
1830
|
+
? { idempotencyKey: admissionIdempotencyKey }
|
|
1831
|
+
: {}),
|
|
1832
|
+
occurredAt: createdAt,
|
|
1833
|
+
});
|
|
1634
1834
|
if (hook) this.admissionsInFlight.delete(admissionKey);
|
|
1635
1835
|
return this.readSubmission(submissionId)!;
|
|
1636
1836
|
};
|
|
@@ -1840,12 +2040,21 @@ export abstract class AgentRuntimeKernel<
|
|
|
1840
2040
|
kind: "record-tool-input",
|
|
1841
2041
|
record: input,
|
|
1842
2042
|
});
|
|
1843
|
-
const appended = this.db.transaction(() =>
|
|
1844
|
-
this.applyPiRecoveryMutations(
|
|
2043
|
+
const appended = this.db.transaction(() => {
|
|
2044
|
+
const inserted = this.applyPiRecoveryMutations(
|
|
1845
2045
|
submission,
|
|
1846
2046
|
decision.mutations,
|
|
1847
|
-
)
|
|
1848
|
-
|
|
2047
|
+
);
|
|
2048
|
+
if (inserted) {
|
|
2049
|
+
this.telemetry.capture("toolStarted", {
|
|
2050
|
+
submissionId: submission.submissionId,
|
|
2051
|
+
toolCallId: input.toolCallId,
|
|
2052
|
+
toolName: input.toolName,
|
|
2053
|
+
input: input.input,
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
return inserted;
|
|
2057
|
+
});
|
|
1849
2058
|
if (appended) this.announceToolStart(submission.submissionId, input);
|
|
1850
2059
|
return appended;
|
|
1851
2060
|
}
|
|
@@ -1934,6 +2143,26 @@ export abstract class AgentRuntimeKernel<
|
|
|
1934
2143
|
body: json({ type: "tool-settlement", event }),
|
|
1935
2144
|
createdAt,
|
|
1936
2145
|
});
|
|
2146
|
+
const details = call.result.details as {
|
|
2147
|
+
kind?: string;
|
|
2148
|
+
bytes?: number;
|
|
2149
|
+
} | undefined;
|
|
2150
|
+
this.telemetry.capture("toolFinished", {
|
|
2151
|
+
submissionId,
|
|
2152
|
+
toolCallId: call.toolCallId,
|
|
2153
|
+
toolName: call.toolName,
|
|
2154
|
+
outcome: call.isError ? "failed" : "completed",
|
|
2155
|
+
outputBytes: details?.kind === "artifact_ref" &&
|
|
2156
|
+
typeof details.bytes === "number"
|
|
2157
|
+
? details.bytes
|
|
2158
|
+
: result.length,
|
|
2159
|
+
spilled: details?.kind === "artifact_ref",
|
|
2160
|
+
output: call.result,
|
|
2161
|
+
...(call.isError && call.cause
|
|
2162
|
+
? { error: errorText(call.cause) }
|
|
2163
|
+
: {}),
|
|
2164
|
+
occurredAt: createdAt,
|
|
2165
|
+
});
|
|
1937
2166
|
}
|
|
1938
2167
|
const decision = this.decidePiRecovery(submission, {
|
|
1939
2168
|
kind: "record-tool-result",
|
|
@@ -2030,6 +2259,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
2030
2259
|
...(message === undefined ? {} : { message }),
|
|
2031
2260
|
});
|
|
2032
2261
|
this.applyPiRecoveryMutations(submission, decision.mutations);
|
|
2262
|
+
if (outcome === "aborted") {
|
|
2263
|
+
this.telemetry.capture("turnCancelled", {
|
|
2264
|
+
submissionId: submission.submissionId,
|
|
2265
|
+
...(message ? { reason: message } : {}),
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2033
2268
|
}
|
|
2034
2269
|
|
|
2035
2270
|
// 作用:把 Turn 的权威结果一次提交到 Submission、恢复里程碑和聊天续传标记。
|
|
@@ -2071,6 +2306,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
2071
2306
|
: durableIntent?.outcome ?? outcome;
|
|
2072
2307
|
const effectiveMessage =
|
|
2073
2308
|
latest.abortReason ?? durableIntent?.message ?? message;
|
|
2309
|
+
const turnOutput = (await this.transcript.storedMessages())
|
|
2310
|
+
.filter((entry) =>
|
|
2311
|
+
entry.submissionId === latest.submissionId &&
|
|
2312
|
+
entry.message.role === "assistant"
|
|
2313
|
+
)
|
|
2314
|
+
.at(-1)?.message;
|
|
2074
2315
|
const terminal = this.db.transaction(() => {
|
|
2075
2316
|
this.appendTerminalIntent(
|
|
2076
2317
|
latest,
|
|
@@ -2126,6 +2367,21 @@ export abstract class AgentRuntimeKernel<
|
|
|
2126
2367
|
latest,
|
|
2127
2368
|
committed.mutations,
|
|
2128
2369
|
);
|
|
2370
|
+
this.telemetry.capture("turnFinished", {
|
|
2371
|
+
submissionId: latest.submissionId,
|
|
2372
|
+
outcome: effectiveOutcome === "succeeded"
|
|
2373
|
+
? "completed"
|
|
2374
|
+
: effectiveOutcome === "aborted"
|
|
2375
|
+
? "cancelled"
|
|
2376
|
+
: "failed",
|
|
2377
|
+
durationMs: Math.max(
|
|
2378
|
+
0,
|
|
2379
|
+
(terminal.completedAt ?? Date.now()) - latest.createdAt,
|
|
2380
|
+
),
|
|
2381
|
+
...(turnOutput === undefined ? {} : { output: turnOutput }),
|
|
2382
|
+
...(effectiveMessage ? { error: effectiveMessage } : {}),
|
|
2383
|
+
occurredAt: terminal.completedAt ?? Date.now(),
|
|
2384
|
+
});
|
|
2129
2385
|
return terminal;
|
|
2130
2386
|
});
|
|
2131
2387
|
await this.drainRuntimeEvents();
|
|
@@ -2240,12 +2496,18 @@ export abstract class AgentRuntimeKernel<
|
|
|
2240
2496
|
submission: StoredSubmission,
|
|
2241
2497
|
): Promise<StoredSubmission> {
|
|
2242
2498
|
if (!submission.queuedInputJson) {
|
|
2243
|
-
this.db.submissions.transition(
|
|
2499
|
+
const started = this.db.submissions.transition(
|
|
2244
2500
|
submission.submissionId,
|
|
2245
2501
|
"running",
|
|
2246
2502
|
["pending"],
|
|
2247
2503
|
);
|
|
2248
2504
|
const activated = this.readSubmission(submission.submissionId)!;
|
|
2505
|
+
if (started) {
|
|
2506
|
+
this.telemetry.capture("turnStarted", {
|
|
2507
|
+
submissionId: activated.submissionId,
|
|
2508
|
+
assemblyRevision: activated.assemblyRevision,
|
|
2509
|
+
});
|
|
2510
|
+
}
|
|
2249
2511
|
await this.broadcastApprovals();
|
|
2250
2512
|
return activated;
|
|
2251
2513
|
}
|
|
@@ -2328,7 +2590,13 @@ export abstract class AgentRuntimeKernel<
|
|
|
2328
2590
|
this.db.submissions.clearQueuedPayload(
|
|
2329
2591
|
submission.submissionId,
|
|
2330
2592
|
);
|
|
2331
|
-
|
|
2593
|
+
const started = this.readSubmission(submission.submissionId)!;
|
|
2594
|
+
this.telemetry.capture("turnStarted", {
|
|
2595
|
+
submissionId: started.submissionId,
|
|
2596
|
+
assemblyRevision: started.assemblyRevision,
|
|
2597
|
+
input: userMessage,
|
|
2598
|
+
});
|
|
2599
|
+
return started;
|
|
2332
2600
|
});
|
|
2333
2601
|
this.broadcast(json({
|
|
2334
2602
|
type: MessageType.CF_AGENT_CHAT_MESSAGES,
|
|
@@ -2360,6 +2628,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
2360
2628
|
);
|
|
2361
2629
|
}
|
|
2362
2630
|
if (recovery) {
|
|
2631
|
+
this.telemetry.capture("turnRecovering", {
|
|
2632
|
+
submissionId,
|
|
2633
|
+
reason: submission.recoveryReason ?? "runtime_restart",
|
|
2634
|
+
attempt: Math.max(1, submission.recoveryErrorCount),
|
|
2635
|
+
});
|
|
2363
2636
|
this.db.submissions.clearRecoveryReason(submissionId);
|
|
2364
2637
|
await this.broadcastApprovals();
|
|
2365
2638
|
await this.ensureRuntimeReady();
|
|
@@ -2471,7 +2744,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
2471
2744
|
);
|
|
2472
2745
|
}
|
|
2473
2746
|
if (ready.status === "pending") {
|
|
2474
|
-
this.db.submissions.transition(submissionId, "running", ["pending"])
|
|
2747
|
+
if (this.db.submissions.transition(submissionId, "running", ["pending"])) {
|
|
2748
|
+
this.telemetry.capture("turnStarted", {
|
|
2749
|
+
submissionId,
|
|
2750
|
+
assemblyRevision: ready.assemblyRevision,
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2475
2753
|
}
|
|
2476
2754
|
const storedMessages = await this.transcript.storedMessages();
|
|
2477
2755
|
const assistantOrdinal = storedMessages.filter(
|
|
@@ -2563,10 +2841,17 @@ export abstract class AgentRuntimeKernel<
|
|
|
2563
2841
|
latest.abortReason,
|
|
2564
2842
|
);
|
|
2565
2843
|
}
|
|
2844
|
+
if (!streamId) {
|
|
2845
|
+
throw new Error("Yielded SpringBrand Turn has no recoverable stream");
|
|
2846
|
+
}
|
|
2566
2847
|
await this.schedule(
|
|
2567
2848
|
0,
|
|
2568
2849
|
"_piPlannedContinuation",
|
|
2569
|
-
{
|
|
2850
|
+
{
|
|
2851
|
+
submissionId,
|
|
2852
|
+
requestId: submission.requestId,
|
|
2853
|
+
continuationId: streamId,
|
|
2854
|
+
},
|
|
2570
2855
|
{ idempotent: true },
|
|
2571
2856
|
);
|
|
2572
2857
|
return latest;
|
|
@@ -3249,16 +3534,24 @@ export abstract class AgentRuntimeKernel<
|
|
|
3249
3534
|
message: "The current Turn is waiting for approval",
|
|
3250
3535
|
};
|
|
3251
3536
|
}
|
|
3537
|
+
const steerId = crypto.randomUUID();
|
|
3252
3538
|
const inserted = this.db.transaction(() => {
|
|
3253
3539
|
if (this.db.steers.findByMessageId(message.id)) return false;
|
|
3254
3540
|
this.db.steers.insert({
|
|
3255
|
-
steerId
|
|
3541
|
+
steerId,
|
|
3256
3542
|
submissionId: target.submissionId,
|
|
3257
3543
|
messageId: message.id,
|
|
3258
3544
|
canonicalJson: json(userMessage),
|
|
3259
3545
|
uiMessageJson: json(message),
|
|
3260
3546
|
createdAt: userMessage.timestamp,
|
|
3261
3547
|
});
|
|
3548
|
+
this.telemetry.capture("turnSteered", {
|
|
3549
|
+
submissionId: target.submissionId,
|
|
3550
|
+
steerId,
|
|
3551
|
+
messageId: message.id,
|
|
3552
|
+
content: userMessage,
|
|
3553
|
+
occurredAt: userMessage.timestamp,
|
|
3554
|
+
});
|
|
3262
3555
|
return true;
|
|
3263
3556
|
});
|
|
3264
3557
|
if (inserted) {
|
|
@@ -3376,6 +3669,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
3376
3669
|
};
|
|
3377
3670
|
}
|
|
3378
3671
|
|
|
3672
|
+
const steerId = crypto.randomUUID();
|
|
3379
3673
|
const moved = this.db.transaction(() => {
|
|
3380
3674
|
const latest = this.readSubmission(submissionId);
|
|
3381
3675
|
const current = this.db.submissions.findRunning() ??
|
|
@@ -3389,13 +3683,20 @@ export abstract class AgentRuntimeKernel<
|
|
|
3389
3683
|
return false;
|
|
3390
3684
|
}
|
|
3391
3685
|
this.db.steers.insert({
|
|
3392
|
-
steerId
|
|
3686
|
+
steerId,
|
|
3393
3687
|
submissionId: target.submissionId,
|
|
3394
3688
|
messageId: queued.userMessageId!,
|
|
3395
3689
|
canonicalJson: queued.queuedInputJson!,
|
|
3396
3690
|
uiMessageJson: queued.queuedUiMessageJson ?? null,
|
|
3397
3691
|
createdAt: queued.createdAt,
|
|
3398
3692
|
});
|
|
3693
|
+
this.telemetry.capture("turnSteered", {
|
|
3694
|
+
submissionId: target.submissionId,
|
|
3695
|
+
steerId,
|
|
3696
|
+
messageId: queued.userMessageId!,
|
|
3697
|
+
content: message,
|
|
3698
|
+
occurredAt: queued.createdAt,
|
|
3699
|
+
});
|
|
3399
3700
|
const reason = "Moved to active Turn";
|
|
3400
3701
|
this.appendTerminalIntent(latest, "aborted", reason);
|
|
3401
3702
|
this.db.submissions.updateAbortReason(submissionId, reason);
|
|
@@ -3620,6 +3921,19 @@ export abstract class AgentRuntimeKernel<
|
|
|
3620
3921
|
? { approved: true }
|
|
3621
3922
|
: { approved: false, reason: decision.reason },
|
|
3622
3923
|
);
|
|
3924
|
+
if (temporary.submissionId) {
|
|
3925
|
+
this.telemetry.capture("approvalDecided", {
|
|
3926
|
+
submissionId: temporary.submissionId!,
|
|
3927
|
+
toolCallId: temporary.toolCallId,
|
|
3928
|
+
executionId,
|
|
3929
|
+
decision: decision.decision === "allow_once"
|
|
3930
|
+
? "approved"
|
|
3931
|
+
: "rejected",
|
|
3932
|
+
...(decision.decision === "deny" && decision.reason
|
|
3933
|
+
? { reason: decision.reason }
|
|
3934
|
+
: {}),
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3623
3937
|
await this.broadcastApprovals();
|
|
3624
3938
|
return { ok: true };
|
|
3625
3939
|
}
|
|
@@ -3733,6 +4047,15 @@ export abstract class AgentRuntimeKernel<
|
|
|
3733
4047
|
const metadata = runtimeSubagentRunMetadata(run.inputPreview);
|
|
3734
4048
|
if (metadata) {
|
|
3735
4049
|
await this.subagentLifecycle(metadata).register(run.runId);
|
|
4050
|
+
this.telemetry.capture("subagentStarted", {
|
|
4051
|
+
submissionId: metadata.submissionId,
|
|
4052
|
+
...(run.parentToolCallId
|
|
4053
|
+
? { toolCallId: run.parentToolCallId }
|
|
4054
|
+
: {}),
|
|
4055
|
+
subagentId: run.runId,
|
|
4056
|
+
agentType: run.agentType,
|
|
4057
|
+
occurredAt: run.startedAt,
|
|
4058
|
+
});
|
|
3736
4059
|
}
|
|
3737
4060
|
await this.broadcastApprovals();
|
|
3738
4061
|
}
|
|
@@ -3777,6 +4100,25 @@ export abstract class AgentRuntimeKernel<
|
|
|
3777
4100
|
status,
|
|
3778
4101
|
zeroUsage: !hasUsage,
|
|
3779
4102
|
});
|
|
4103
|
+
this.telemetry.capture("subagentFinished", {
|
|
4104
|
+
submissionId: metadata.submissionId,
|
|
4105
|
+
...(run.parentToolCallId
|
|
4106
|
+
? { toolCallId: run.parentToolCallId }
|
|
4107
|
+
: {}),
|
|
4108
|
+
subagentId: run.runId,
|
|
4109
|
+
outcome: status === "completed"
|
|
4110
|
+
? "completed"
|
|
4111
|
+
: status === "aborted" || status === "skipped"
|
|
4112
|
+
? "cancelled"
|
|
4113
|
+
: "failed",
|
|
4114
|
+
durationMs: Math.max(
|
|
4115
|
+
0,
|
|
4116
|
+
(run.completedAt ?? Date.now()) - run.startedAt,
|
|
4117
|
+
),
|
|
4118
|
+
...(completion ? { output: completion.output } : {}),
|
|
4119
|
+
...(result.error ? { error: result.error } : {}),
|
|
4120
|
+
occurredAt: run.completedAt ?? Date.now(),
|
|
4121
|
+
});
|
|
3780
4122
|
}
|
|
3781
4123
|
}
|
|
3782
4124
|
await this.broadcastApprovals();
|