@springbrand/agent-runtime 0.2.0-alpha.44 → 0.2.0-alpha.45
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/subagent/definition.ts +0 -3
- package/src/adapter/cloudflare/subagent/runner.ts +19 -6
- package/src/db/schema.ts +6 -1
- package/src/db/submission.repo.ts +13 -1
- package/src/index.ts +0 -1
- package/src/kernel/bindings.ts +0 -4
- package/src/kernel/submission-lifecycle.ts +25 -8
- package/src/pi/assembly/context.ts +23 -10
- package/src/pi/assembly/snapshot.ts +14 -8
- package/src/pi/message/projection.ts +47 -21
- package/src/pi/runtime-adapter/assembly.ts +2 -30
- package/src/pi/runtime-adapter/execution.ts +56 -46
- package/src/pi/runtime-adapter/index.ts +2 -8
- package/src/pi/runtime-adapter/models.ts +63 -26
- package/src/pi/runtime-adapter/transcript.ts +6 -3
- package/src/pi/tool/base.ts +10 -15
- package/src/pi/tool/compiler.ts +1 -1
- package/src/pi/tool/core.ts +2 -0
- package/src/pi/tool/schedule.ts +15 -11
- package/src/pi/tool/skill.ts +3 -0
- package/src/pi/tool/subagent.ts +2 -0
- package/src/pi/tool/time.ts +1 -0
- package/src/pi/tool/web-search/api.ts +8 -0
- package/src/pi/tool/web-search/web-search.ts +1 -0
- package/src/pi/tool/workspace-sandbox.ts +3 -0
- package/src/runtime-agent.ts +9 -0
- package/src/runtime-assembler.ts +17 -11
- package/src/runtime.ts +111 -39
package/src/runtime-assembler.ts
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
codeExecutionPiToolCandidate,
|
|
59
59
|
} from "./pi/tool/core";
|
|
60
60
|
import { skillPiToolCandidates } from "./pi/tool/skill";
|
|
61
|
-
import { createWebSearch } from "./pi/tool/web-search";
|
|
61
|
+
import { createWebSearch, WEB_SEARCH_MODEL } from "./pi/tool/web-search";
|
|
62
62
|
import { subagentPiToolCandidates } from "./pi/tool/subagent";
|
|
63
63
|
import { resolvePiModel } from "./pi/runtime-adapter/models";
|
|
64
64
|
import {
|
|
@@ -170,7 +170,7 @@ interface ToolSurfaceInput {
|
|
|
170
170
|
readonly hostTools: readonly PiToolCandidate[];
|
|
171
171
|
readonly skills: readonly RuntimeSkillSourceBinding[];
|
|
172
172
|
readonly enabledSubagents: readonly string[];
|
|
173
|
-
readonly webSearch
|
|
173
|
+
readonly webSearch?: Parameters<typeof basePiToolCandidates>[0];
|
|
174
174
|
readonly extensions: readonly RuntimeExtensionConfig[];
|
|
175
175
|
readonly policy?: RuntimeToolSurfacePolicy;
|
|
176
176
|
}
|
|
@@ -581,14 +581,20 @@ class RuntimeBuilder {
|
|
|
581
581
|
: `Model is configured by multiple endpoints: ${profile.model}`,
|
|
582
582
|
);
|
|
583
583
|
}
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
584
|
+
const webSearchEndpoint = provider.endpoints.find((endpoint) =>
|
|
585
|
+
endpoint.models.includes(WEB_SEARCH_MODEL)
|
|
586
|
+
);
|
|
587
|
+
const webSearch = webSearchEndpoint
|
|
588
|
+
? (() => {
|
|
589
|
+
const webSearchModel = resolvePiModel(provider, WEB_SEARCH_MODEL);
|
|
590
|
+
return createWebSearch({
|
|
591
|
+
endpoint: webSearchEndpoint,
|
|
592
|
+
model: WEB_SEARCH_MODEL,
|
|
593
|
+
maxTokens: webSearchModel.maxTokens,
|
|
594
|
+
reasoning: webSearchModel.reasoning,
|
|
595
|
+
});
|
|
596
|
+
})()
|
|
597
|
+
: undefined;
|
|
592
598
|
const skillSources = [...this.skillSources.values()];
|
|
593
599
|
const toolSurface = await createToolSurface({
|
|
594
600
|
...(this.codeExecution ? { codeExecution: this.codeExecution } : {}),
|
|
@@ -606,7 +612,7 @@ class RuntimeBuilder {
|
|
|
606
612
|
],
|
|
607
613
|
skills: skillSources,
|
|
608
614
|
enabledSubagents: [...this.enabledSubagents],
|
|
609
|
-
webSearch,
|
|
615
|
+
...(webSearch ? { webSearch } : {}),
|
|
610
616
|
extensions: [...this.extensions.values()],
|
|
611
617
|
...(this.toolPolicy ? { policy: this.toolPolicy } : {}),
|
|
612
618
|
});
|
package/src/runtime.ts
CHANGED
|
@@ -48,7 +48,6 @@ import type {
|
|
|
48
48
|
} from "./kernel/extensions";
|
|
49
49
|
import type {
|
|
50
50
|
RuntimeActivity,
|
|
51
|
-
RuntimeActivityProjection,
|
|
52
51
|
RuntimeState,
|
|
53
52
|
RuntimeTurnState,
|
|
54
53
|
} from "./kernel/state";
|
|
@@ -67,6 +66,7 @@ import {
|
|
|
67
66
|
} from "./telemetry";
|
|
68
67
|
import { connectConfiguredMcpServers } from "./lib/mcp";
|
|
69
68
|
import {
|
|
69
|
+
ContextOverflowError,
|
|
70
70
|
PiRuntimeAdapter,
|
|
71
71
|
MODEL_STREAM_STALL_TIMEOUT_MS,
|
|
72
72
|
readModelStreamStallDetails,
|
|
@@ -110,6 +110,7 @@ import {
|
|
|
110
110
|
isTerminalSubmissionStatus,
|
|
111
111
|
RuntimeDatabase,
|
|
112
112
|
type StoredSubmissionAdmission,
|
|
113
|
+
type SubmissionRecoveryReason,
|
|
113
114
|
type SubmissionStatus,
|
|
114
115
|
} from "./db/index";
|
|
115
116
|
import {
|
|
@@ -151,10 +152,11 @@ export const CHAT_RECOVERY_MAX_ATTEMPTS = 5;
|
|
|
151
152
|
export const CHAT_STALL_MAX_ATTEMPTS = 3;
|
|
152
153
|
const CHAT_RECOVERY_TERMINAL_MESSAGE =
|
|
153
154
|
"Recovery failed after multiple attempts. This generation has stopped and your progress has been saved. Send a new message to continue.";
|
|
155
|
+
const CONTEXT_OVERFLOW_TERMINAL_MESSAGE =
|
|
156
|
+
"The model context is still too large after forced compaction. Start a new conversation or reduce the attached context.";
|
|
154
157
|
|
|
155
158
|
type RuntimeEventOutboxPayload =
|
|
156
159
|
| { readonly type: "model-usage"; readonly event: RuntimeModelUsageEvent }
|
|
157
|
-
| { readonly type: "activity"; readonly projection: RuntimeActivityProjection }
|
|
158
160
|
| {
|
|
159
161
|
readonly type: "tool-settlement";
|
|
160
162
|
readonly event: RuntimeToolSettlementEvent;
|
|
@@ -174,7 +176,10 @@ interface StoredSubmission extends SubmissionReceipt {
|
|
|
174
176
|
userMessageId?: string | null;
|
|
175
177
|
regenerateMessageId?: string | null;
|
|
176
178
|
recoveryErrorCount: number;
|
|
177
|
-
recoveryReason?:
|
|
179
|
+
recoveryReason?:
|
|
180
|
+
| "no_meaningful_model_progress"
|
|
181
|
+
| "transient_model_error"
|
|
182
|
+
| "context_overflow";
|
|
178
183
|
runId: string | null;
|
|
179
184
|
accountId: string | null;
|
|
180
185
|
rateVersion: number | null;
|
|
@@ -534,6 +539,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
534
539
|
this.db.submissions.findRunning() as StoredSubmission | null,
|
|
535
540
|
findNextPending: () =>
|
|
536
541
|
this.db.submissions.findNextPending() as StoredSubmission | null,
|
|
542
|
+
listPending: () =>
|
|
543
|
+
this.db.submissions.listPending() as StoredSubmission[],
|
|
537
544
|
updateAbortReason: (submissionId, reason) =>
|
|
538
545
|
this.db.submissions.updateAbortReason(submissionId, reason),
|
|
539
546
|
};
|
|
@@ -1001,6 +1008,26 @@ export abstract class AgentRuntimeKernel<
|
|
|
1001
1008
|
});
|
|
1002
1009
|
this.projectNestedToolSettlement(submission.submissionId, event);
|
|
1003
1010
|
},
|
|
1011
|
+
onUnknownToolStarted: (event) => {
|
|
1012
|
+
this.telemetry.capture("toolStarted", {
|
|
1013
|
+
submissionId: submission.submissionId,
|
|
1014
|
+
toolCallId: event.toolCallId,
|
|
1015
|
+
toolName: event.toolName,
|
|
1016
|
+
input: event.input,
|
|
1017
|
+
occurredAt: event.occurredAt,
|
|
1018
|
+
});
|
|
1019
|
+
},
|
|
1020
|
+
onUnknownToolFinished: (event) => {
|
|
1021
|
+
this.telemetry.capture("toolFinished", {
|
|
1022
|
+
submissionId: submission.submissionId,
|
|
1023
|
+
toolCallId: event.toolCallId,
|
|
1024
|
+
toolName: event.toolName,
|
|
1025
|
+
outcome: event.outcome,
|
|
1026
|
+
output: event.output,
|
|
1027
|
+
outputBytes: json(event.output).length,
|
|
1028
|
+
occurredAt: event.occurredAt,
|
|
1029
|
+
});
|
|
1030
|
+
},
|
|
1004
1031
|
...(toolExecutors && Object.keys(toolExecutors).length > 0
|
|
1005
1032
|
? { toolExecutors }
|
|
1006
1033
|
: {}),
|
|
@@ -1107,12 +1134,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
1107
1134
|
async onStart(): Promise<void> {
|
|
1108
1135
|
this.runtimeLoad.reset();
|
|
1109
1136
|
this.telemetry.recover(() => this.ensureRuntimeReady());
|
|
1110
|
-
await this.broadcastApprovals();
|
|
1111
1137
|
if (this.db.runtimeEvents.hasPending()) {
|
|
1112
1138
|
this.ctx.waitUntil(
|
|
1113
1139
|
this.ensureRuntimeReady().then(() => this.drainRuntimeEvents()),
|
|
1114
1140
|
);
|
|
1115
1141
|
}
|
|
1142
|
+
await this.broadcastApprovals();
|
|
1116
1143
|
await this.approvals.dispatchPendingContinuations();
|
|
1117
1144
|
const running = this.db.submissions.findRunning() as StoredSubmission | null;
|
|
1118
1145
|
if (running) {
|
|
@@ -1136,13 +1163,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
1136
1163
|
const payload = JSON.parse(row.body) as RuntimeEventOutboxPayload;
|
|
1137
1164
|
if (payload.type === "model-usage") {
|
|
1138
1165
|
await turnEvents.onModelUsage?.(payload.event);
|
|
1139
|
-
} else if (payload.type === "activity") {
|
|
1140
|
-
if (
|
|
1141
|
-
!this.state.activity ||
|
|
1142
|
-
payload.projection.revision >= this.state.activity.revision
|
|
1143
|
-
) {
|
|
1144
|
-
await turnEvents.onActivityChanged?.(payload.projection);
|
|
1145
|
-
}
|
|
1146
1166
|
} else if (payload.type === "tool-settlement") {
|
|
1147
1167
|
await turnEvents.onToolSettled?.(payload.event);
|
|
1148
1168
|
} else if (payload.type === "subagent-usage") {
|
|
@@ -1671,7 +1691,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1671
1691
|
|
|
1672
1692
|
// 作用:按当前模型输入预算压缩 Pi 上下文。
|
|
1673
1693
|
// 调用:Pi Turn 适配器在发模型请求前通过 `transformContext` 回调。
|
|
1674
|
-
// 原因:模型窗口必须预留最大输出,压缩也必须使用本 Snapshot
|
|
1694
|
+
// 原因:模型窗口必须预留最大输出,压缩也必须使用本 Snapshot 的模型。
|
|
1675
1695
|
private async transformPiContext(
|
|
1676
1696
|
submissionId: string,
|
|
1677
1697
|
messages: Parameters<PiRuntimeTranscript["compactContext"]>[0],
|
|
@@ -1680,13 +1700,12 @@ export abstract class AgentRuntimeKernel<
|
|
|
1680
1700
|
const snapshot = this.assembly();
|
|
1681
1701
|
const inputBudget = snapshot.pi.model.contextWindow -
|
|
1682
1702
|
snapshot.pi.model.maxTokens;
|
|
1703
|
+
const force = this.readSubmission(submissionId)?.recoveryReason ===
|
|
1704
|
+
"context_overflow";
|
|
1683
1705
|
const compacted = await this.transcript.compactContext(messages, {
|
|
1684
1706
|
compactAfterTokens: inputBudget * 0.85,
|
|
1707
|
+
force,
|
|
1685
1708
|
model: snapshot.pi.model,
|
|
1686
|
-
apiKey: this.pi.resolveApiKey(
|
|
1687
|
-
snapshot.bindings.provider,
|
|
1688
|
-
snapshot.pi.model.id,
|
|
1689
|
-
),
|
|
1690
1709
|
signal,
|
|
1691
1710
|
submissionId,
|
|
1692
1711
|
onCompactionPersisted: (event) => {
|
|
@@ -1719,6 +1738,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1719
1738
|
});
|
|
1720
1739
|
},
|
|
1721
1740
|
});
|
|
1741
|
+
if (force) this.db.submissions.clearRecoveryReason(submissionId);
|
|
1722
1742
|
await this.drainRuntimeEvents();
|
|
1723
1743
|
return compacted;
|
|
1724
1744
|
}
|
|
@@ -2849,7 +2869,9 @@ export abstract class AgentRuntimeKernel<
|
|
|
2849
2869
|
reason: submission.recoveryReason ?? "runtime_restart",
|
|
2850
2870
|
attempt: Math.max(1, submission.recoveryErrorCount),
|
|
2851
2871
|
});
|
|
2852
|
-
|
|
2872
|
+
if (submission.recoveryReason !== "context_overflow") {
|
|
2873
|
+
this.db.submissions.clearRecoveryReason(submissionId);
|
|
2874
|
+
}
|
|
2853
2875
|
await this.broadcastApprovals();
|
|
2854
2876
|
}
|
|
2855
2877
|
if (mode !== "fresh") await this.ensureRuntimeReady();
|
|
@@ -3144,15 +3166,25 @@ export abstract class AgentRuntimeKernel<
|
|
|
3144
3166
|
return this.readSubmission(submissionId)!;
|
|
3145
3167
|
}
|
|
3146
3168
|
const stalled = error instanceof ChatStreamStalledError;
|
|
3169
|
+
const contextOverflow = error instanceof ContextOverflowError;
|
|
3147
3170
|
const abortReason = this.readSubmission(submissionId)?.abortReason;
|
|
3148
3171
|
if (!abortReason && (stalled || error instanceof RetryableModelError)) {
|
|
3149
|
-
const
|
|
3150
|
-
|
|
3172
|
+
const recoveryReason: SubmissionRecoveryReason = stalled
|
|
3173
|
+
? "no_meaningful_model_progress"
|
|
3174
|
+
: contextOverflow
|
|
3175
|
+
? "context_overflow"
|
|
3176
|
+
: "transient_model_error";
|
|
3177
|
+
let contextOverflowRecoveryClaimed = false;
|
|
3178
|
+
const recoveryErrorCount = this.db.transaction(() => {
|
|
3179
|
+
if (contextOverflow) {
|
|
3180
|
+
contextOverflowRecoveryClaimed = this.db.submissions
|
|
3181
|
+
.claimContextOverflowRecovery(submissionId);
|
|
3182
|
+
}
|
|
3183
|
+
return this.db.submissions.incrementRecoveryErrorCount(
|
|
3151
3184
|
submissionId,
|
|
3152
|
-
|
|
3153
|
-
? "no_meaningful_model_progress"
|
|
3154
|
-
: "transient_model_error",
|
|
3185
|
+
recoveryReason,
|
|
3155
3186
|
);
|
|
3187
|
+
});
|
|
3156
3188
|
if (stalled) {
|
|
3157
3189
|
const stallDetails = readModelStreamStallDetails(errorText(error)) ?? {
|
|
3158
3190
|
lastMeaningfulActivityAt:
|
|
@@ -3172,10 +3204,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
3172
3204
|
await this.broadcastApprovals();
|
|
3173
3205
|
const stoppedDuringRecovery = this.readSubmission(submissionId)
|
|
3174
3206
|
?.abortReason;
|
|
3207
|
+
const retryAllowed = contextOverflow
|
|
3208
|
+
? contextOverflowRecoveryClaimed &&
|
|
3209
|
+
recoveryErrorCount < CHAT_RECOVERY_MAX_ATTEMPTS
|
|
3210
|
+
: recoveryErrorCount <
|
|
3211
|
+
(stalled ? CHAT_STALL_MAX_ATTEMPTS : CHAT_RECOVERY_MAX_ATTEMPTS);
|
|
3175
3212
|
if (
|
|
3176
3213
|
!stoppedDuringRecovery &&
|
|
3177
|
-
|
|
3178
|
-
(stalled ? CHAT_STALL_MAX_ATTEMPTS : CHAT_RECOVERY_MAX_ATTEMPTS)
|
|
3214
|
+
retryAllowed
|
|
3179
3215
|
) {
|
|
3180
3216
|
const recoveryOutcome = await this.scheduleChatRecoveryRetry(
|
|
3181
3217
|
{
|
|
@@ -3216,7 +3252,9 @@ export abstract class AgentRuntimeKernel<
|
|
|
3216
3252
|
} else if (!stoppedDuringRecovery) {
|
|
3217
3253
|
terminalIntent = {
|
|
3218
3254
|
outcome: "failed",
|
|
3219
|
-
message:
|
|
3255
|
+
message: contextOverflow
|
|
3256
|
+
? CONTEXT_OVERFLOW_TERMINAL_MESSAGE
|
|
3257
|
+
: CHAT_RECOVERY_TERMINAL_MESSAGE,
|
|
3220
3258
|
};
|
|
3221
3259
|
}
|
|
3222
3260
|
}
|
|
@@ -4114,6 +4152,35 @@ export abstract class AgentRuntimeKernel<
|
|
|
4114
4152
|
return result;
|
|
4115
4153
|
}
|
|
4116
4154
|
|
|
4155
|
+
/** Stops every running, queued, or steered user request in this Session. */
|
|
4156
|
+
async stopAllSubmissions(reason?: string): Promise<{ ok: boolean }> {
|
|
4157
|
+
const current = this.db.submissions.findRunning() ??
|
|
4158
|
+
this.db.submissions.findNextPending();
|
|
4159
|
+
if (current) {
|
|
4160
|
+
this.db.transaction(() => {
|
|
4161
|
+
for (const steer of this.db.steers.listForSubmission(
|
|
4162
|
+
current.submissionId,
|
|
4163
|
+
)) {
|
|
4164
|
+
const message = JSON.parse(
|
|
4165
|
+
steer.canonicalJson,
|
|
4166
|
+
) as PiCanonicalUserInput;
|
|
4167
|
+
const userMessage = steer.uiMessageJson
|
|
4168
|
+
? JSON.parse(steer.uiMessageJson) as UIMessage & { role: "user" }
|
|
4169
|
+
: undefined;
|
|
4170
|
+
this.transcript.append(steer.messageId, message, {
|
|
4171
|
+
submissionId: current.submissionId,
|
|
4172
|
+
createdAt: steer.createdAt,
|
|
4173
|
+
...(userMessage ? { userMessage } : {}),
|
|
4174
|
+
});
|
|
4175
|
+
this.db.steers.deleteByMessageId(steer.messageId);
|
|
4176
|
+
}
|
|
4177
|
+
});
|
|
4178
|
+
}
|
|
4179
|
+
const result = await this.submissions.stopAll(reason ?? "Stopped");
|
|
4180
|
+
await this.broadcastApprovals();
|
|
4181
|
+
return result;
|
|
4182
|
+
}
|
|
4183
|
+
|
|
4117
4184
|
/** 返回当前已装配 User Agent 的执行档位,不提供 Session 覆盖。 */
|
|
4118
4185
|
executionLevel(): ExecutionLevel {
|
|
4119
4186
|
return this.assembly().profile.executionLevel;
|
|
@@ -4562,13 +4629,20 @@ export abstract class AgentRuntimeKernel<
|
|
|
4562
4629
|
...(current.recoveryErrorCount === undefined
|
|
4563
4630
|
? {}
|
|
4564
4631
|
: {
|
|
4565
|
-
recoveryAttempt:
|
|
4632
|
+
recoveryAttempt:
|
|
4633
|
+
current.recoveryReason === "context_overflow"
|
|
4634
|
+
? 1
|
|
4635
|
+
: current.recoveryErrorCount,
|
|
4566
4636
|
// 上限按当前恢复原因取,否则 stall 会显示 "2/5" 却在第 3 次就终止。
|
|
4567
4637
|
recoveryMax:
|
|
4568
|
-
current.recoveryReason === "
|
|
4569
|
-
?
|
|
4570
|
-
:
|
|
4571
|
-
|
|
4638
|
+
current.recoveryReason === "context_overflow"
|
|
4639
|
+
? 1
|
|
4640
|
+
: current.recoveryReason ===
|
|
4641
|
+
"no_meaningful_model_progress"
|
|
4642
|
+
? CHAT_STALL_MAX_ATTEMPTS
|
|
4643
|
+
: CHAT_RECOVERY_MAX_ATTEMPTS,
|
|
4644
|
+
...(current.recoveryReason &&
|
|
4645
|
+
current.recoveryReason !== "context_overflow"
|
|
4572
4646
|
? { recoveryReason: current.recoveryReason }
|
|
4573
4647
|
: {}),
|
|
4574
4648
|
}),
|
|
@@ -4607,13 +4681,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
4607
4681
|
backgroundWork,
|
|
4608
4682
|
revision: (currentActivity?.revision ?? 0) + 1,
|
|
4609
4683
|
};
|
|
4610
|
-
this.db.transaction(() => {
|
|
4611
|
-
this.db.runtimeEvents.insert({
|
|
4612
|
-
eventId: `session-activity:${nextActivity.revision}`,
|
|
4613
|
-
body: json({ type: "activity", projection: nextActivity }),
|
|
4614
|
-
createdAt: Date.now(),
|
|
4615
|
-
});
|
|
4616
|
-
});
|
|
4617
4684
|
if (
|
|
4618
4685
|
this.state?.approvals === undefined ||
|
|
4619
4686
|
json(this.state.approvals) !== json(approvals) ||
|
|
@@ -4627,9 +4694,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
4627
4694
|
turn,
|
|
4628
4695
|
});
|
|
4629
4696
|
}
|
|
4630
|
-
this.
|
|
4697
|
+
const projection = this.turnEventsPort()?.onActivityChanged?.(nextActivity);
|
|
4698
|
+
if (projection) {
|
|
4699
|
+
this.ctx.waitUntil(
|
|
4700
|
+
projection.catch(() => undefined),
|
|
4701
|
+
);
|
|
4702
|
+
}
|
|
4631
4703
|
} catch {
|
|
4632
|
-
//
|
|
4704
|
+
// Approval projection is best effort and must not block execution.
|
|
4633
4705
|
}
|
|
4634
4706
|
}
|
|
4635
4707
|
|