@springbrand/agent-runtime 0.3.0-alpha.2 → 0.3.0-alpha.21
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 +11 -9
- package/src/adapter/cloudflare/agent.ts +233 -179
- package/src/adapter/cloudflare/define-agent.ts +252 -104
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/adapter/cloudflare/schema.ts +21 -2
- package/src/adapter/cloudflare/subagent/definition.ts +25 -13
- package/src/adapter/cloudflare/subagent/runner.ts +43 -50
- package/src/adapter/cloudflare/subagent/tools.ts +4 -4
- package/src/adapter/cloudflare/universal-agent/preparation.ts +3 -3
- package/src/adapter/cloudflare/universal-agent/tools.ts +4 -6
- package/src/adapter/cloudflare/workspace/version-control.ts +1 -1
- package/src/config/activation.ts +13 -15
- package/src/config/agent-context.ts +1 -1
- package/src/config/assembler.ts +48 -31
- package/src/config/assembly-snapshot.ts +1 -1
- package/src/config/definition.ts +1 -1
- package/src/config/harness-runtime.ts +77 -49
- package/src/config/index.ts +1 -1
- package/src/config/model-guidance.ts +20 -0
- package/src/config/ports.ts +57 -7
- package/src/config/{pinning.ts → prepared-runtime.ts} +59 -123
- package/src/config/public-contracts.ts +2 -2
- package/src/config/runtime-config.ts +2 -1
- package/src/context/assembly.ts +1 -1
- package/src/context/extension/assembly.ts +2 -2
- package/src/context/memory/budget/gate.ts +19 -3
- package/src/conversation/harness-resume.ts +89 -238
- package/src/conversation/harness-stream.ts +87 -71
- package/src/conversation/history.ts +154 -152
- package/src/conversation/message/contract.ts +28 -4
- package/src/conversation/message/conversion.ts +46 -5
- package/src/conversation/message/projection.ts +61 -21
- package/src/conversation/session-entry.ts +29 -0
- package/src/conversation/state.ts +23 -2
- package/src/harness/governance.ts +206 -0
- package/src/harness/index.ts +3 -0
- package/src/harness/session-harness.ts +269 -445
- package/src/harness/submission-operation.ts +15 -40
- package/src/index.ts +18 -8
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/prompt.ts +9 -10
- package/src/lib/token-estimate.ts +8 -0
- package/src/model-protocol/openrouter.ts +41 -0
- package/src/models/index.ts +200 -35
- package/src/models/openrouter/messages.ts +13 -8
- package/src/models/openrouter/openai.ts +200 -0
- package/src/models/openrouter/protocol-response.ts +85 -0
- package/src/models/provider-streams.ts +45 -0
- package/src/session/cloudflare/migration.ts +382 -864
- package/src/session/cloudflare/storage.ts +13 -536
- package/src/session/index.ts +22 -35
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +20 -30
- package/src/submission/contracts.ts +42 -2
- package/src/submission/delivery.ts +1 -1
- package/src/submission/finalization.ts +36 -15
- package/src/submission/lifecycle.ts +28 -15
- package/src/submission/repository.ts +97 -49
- package/src/submission/steer-delivery.ts +32 -76
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/contract.ts +0 -1
- package/src/telemetry/coordinator.ts +1 -1
- package/src/telemetry/runtime-telemetry.ts +0 -4
- package/src/telemetry/subagent-lifecycle.ts +1 -1
- package/src/telemetry/turn-events.ts +35 -25
- package/src/tools/README.md +44 -0
- package/src/tools/{ai-adapter.ts → adapters/ai-to-pi.ts} +6 -9
- package/src/tools/{declared.ts → adapters/declared.ts} +6 -2
- package/src/tools/{nested-tools.ts → adapters/pi-to-ai.ts} +4 -4
- package/src/tools/assistant/ask-user.ts +319 -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/tools/{time.ts → assistant/time.ts} +1 -1
- package/src/tools/{core-host.ts → codemode/adapter.ts} +50 -53
- package/src/tools/{core.ts → codemode/index.ts} +36 -92
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +29 -26
- package/src/tools/{gateway.ts → mcp/gateway.ts} +19 -19
- package/src/tools/{mcp.ts → mcp/index.ts} +5 -5
- package/src/tools/runtime/activation.ts +40 -0
- package/src/tools/{approval → runtime/approval}/repository.ts +13 -12
- package/src/tools/runtime/compiler.ts +246 -0
- package/src/tools/runtime/contracts.ts +140 -0
- package/src/tools/{decision-gate.ts → runtime/decision-gate.ts} +149 -39
- package/src/tools/{human-decisions.ts → runtime/human-decisions.ts} +4 -4
- package/src/tools/{interaction → 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/tools/{subagent-authority.ts → subagent/authority.ts} +31 -31
- package/src/tools/{subagent.ts → subagent/index.ts} +34 -37
- package/src/tools/{subagent-run-repository.ts → subagent/repository.ts} +1 -1
- package/src/tools/{subagent-runtime.ts → subagent/runtime.ts} +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/approvals.ts +4 -7
- package/src/tools/{temporary-agent → subagent/temporary}/core.ts +2 -3
- package/src/tools/{temporary-agent → subagent/temporary}/runner.ts +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/workspace.ts +1 -4
- package/src/tools/{web-fetch.ts → web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1322 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/tools/{web-search/web-search.ts → web/search/search.ts} +5 -7
- package/src/tools/{workspace-sandbox.ts → workspace/index.ts} +12 -272
- package/src/tools/{workspace-revision.ts → workspace/revision.ts} +3 -3
- package/src/tools/base.ts +0 -534
- package/src/tools/compiler.ts +0 -327
- package/src/tools/schedule.ts +0 -261
- package/src/tools/skill.ts +0 -565
- package/src/tools/web-search/api.ts +0 -1318
- package/src/tools/web-search/index.ts +0 -2
- package/src/tools/workspace-state-connector.ts +0 -146
- package/src/tools/{approval → runtime/approval}/index.ts +0 -0
- package/src/tools/{interaction → runtime/interaction}/index.ts +0 -0
- package/src/tools/{temporary-agent → subagent/temporary}/coordinator.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/contract.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/extract/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/fanout/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/registry.ts +1 -1
- /package/src/tools/{workspace-versioning.ts → workspace/versioning.ts} +0 -0
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isTerminalSubmissionStatus,
|
|
20
20
|
SubmissionLifecycle,
|
|
21
21
|
SubmissionAdmission,
|
|
22
|
+
ADMISSION_ATTEMPT_WINDOW_MS,
|
|
22
23
|
SubmissionDelivery,
|
|
23
24
|
SubmissionFinalizer,
|
|
24
25
|
projectSubmissionReceipt,
|
|
@@ -33,6 +34,9 @@ import {
|
|
|
33
34
|
type SubmissionReceipt,
|
|
34
35
|
type SubmissionExecutionMode,
|
|
35
36
|
type SubmissionHandle,
|
|
37
|
+
type StoredRuntimeKey,
|
|
38
|
+
type RuntimeKeyRewrite,
|
|
39
|
+
type RuntimeUnattendedExecutionGrant,
|
|
36
40
|
} from "../../submission";
|
|
37
41
|
import type { ExecutionLevel } from "../../lib/execution-level";
|
|
38
42
|
import {
|
|
@@ -42,7 +46,6 @@ import {
|
|
|
42
46
|
RuntimeLoadTracker,
|
|
43
47
|
prepareRuntimeCandidate,
|
|
44
48
|
projectRuntimeAssembly,
|
|
45
|
-
type PreparedPiRuntime,
|
|
46
49
|
type RuntimeAssemblyInput,
|
|
47
50
|
type RuntimeAssemblyView,
|
|
48
51
|
type RuntimeCandidate,
|
|
@@ -66,13 +69,15 @@ import {
|
|
|
66
69
|
import type { UIMessage, UIMessageChunk } from "ai";
|
|
67
70
|
import type {
|
|
68
71
|
AssistantMessage,
|
|
72
|
+
JsonObject,
|
|
69
73
|
} from "@earendil-works/pi-ai";
|
|
70
|
-
import { BACKGROUND_CONTEXT } from "@earendil-works/
|
|
74
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
71
75
|
import { openCloudflareSession } from "../../session";
|
|
72
|
-
import { openSessionHarness, prepareSubmissionOperation } from "../../harness";
|
|
76
|
+
import { ModelRecovery, openSessionHarness, prepareSubmissionOperation } from "../../harness";
|
|
73
77
|
import type { SessionHarness } from "../../harness";
|
|
74
78
|
import {
|
|
75
79
|
compileHarnessTools,
|
|
80
|
+
harnessToolActivation,
|
|
76
81
|
decodeSubagentCompletion,
|
|
77
82
|
HarnessDecisionGate,
|
|
78
83
|
HarnessHumanDecisions,
|
|
@@ -85,7 +90,10 @@ import {
|
|
|
85
90
|
type TemporaryAgentRequest,
|
|
86
91
|
type TemporaryAgentRunContext,
|
|
87
92
|
} from "../../tools";
|
|
88
|
-
import {
|
|
93
|
+
import {
|
|
94
|
+
ASK_USER_TOOL_NAME,
|
|
95
|
+
askUserPiToolCandidate,
|
|
96
|
+
} from "../../tools/assistant/ask-user";
|
|
89
97
|
import { RuntimeDatabase } from "./database";
|
|
90
98
|
|
|
91
99
|
/**
|
|
@@ -98,6 +106,10 @@ import { RuntimeDatabase } from "./database";
|
|
|
98
106
|
// #region Runtime 内部数据与基础工具
|
|
99
107
|
|
|
100
108
|
const SCHEDULED_STABLE_TIMEOUT_MS = 30_000;
|
|
109
|
+
/** 定时 Run 未结束时拒绝普通输入的统一原因,服务端与客户端状态共用同一事实。 */
|
|
110
|
+
const SCHEDULED_INPUT_LOCKED_MESSAGE =
|
|
111
|
+
"This scheduled run is still in progress. Wait for it to finish before sending a message.";
|
|
112
|
+
|
|
101
113
|
interface ActiveTurn {
|
|
102
114
|
submissionId: string;
|
|
103
115
|
requestId: string;
|
|
@@ -149,12 +161,22 @@ export abstract class AgentRuntimeKernel<
|
|
|
149
161
|
};
|
|
150
162
|
|
|
151
163
|
protected abstract ensureRuntimeReady(): Promise<void>;
|
|
152
|
-
/**
|
|
164
|
+
/** Prepare per-Submission Host state before first execution. */
|
|
153
165
|
protected prepareRuntimeForSubmission(_submissionId: string): Promise<void> {
|
|
154
166
|
return this.ensureRuntimeReady();
|
|
155
167
|
}
|
|
156
168
|
|
|
157
|
-
|
|
169
|
+
/** Runs before a new Submission records its Runtime key. */
|
|
170
|
+
protected async prepareRuntimeForAdmission(): Promise<{
|
|
171
|
+
runtimeKey: string;
|
|
172
|
+
admission?: ActiveRuntime["admission"];
|
|
173
|
+
}> {
|
|
174
|
+
await this.ensureRuntimeReady();
|
|
175
|
+
const runtime = this.requireRuntime();
|
|
176
|
+
return { runtimeKey: runtime.runtimeKey, admission: runtime.admission };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected ensureRuntimeVersion(_runtimeKey: string, _force = false): Promise<void> {
|
|
158
180
|
return this.ensureRuntimeReady();
|
|
159
181
|
}
|
|
160
182
|
protected get submissionContext() {
|
|
@@ -164,6 +186,37 @@ export abstract class AgentRuntimeKernel<
|
|
|
164
186
|
};
|
|
165
187
|
}
|
|
166
188
|
|
|
189
|
+
/** Narrow storage seam for Host-validated, one-time Agent identity upgrades. */
|
|
190
|
+
protected savedRuntimeKeys(): readonly StoredRuntimeKey[] {
|
|
191
|
+
return this.db.submissions.listRuntimeKeys();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
protected rewriteSavedRuntimeKeys(rewrites: readonly RuntimeKeyRewrite[]): void {
|
|
195
|
+
this.db.submissions.rewriteRuntimeKeys(rewrites);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** One Runtime-owned fact for Host lifecycle guards; includes async preparation. */
|
|
199
|
+
protected hasUnfinishedRuntimeWork(): boolean {
|
|
200
|
+
return this.submissions.isBusy() ||
|
|
201
|
+
this.db.submissionAdmissions.countPending(Date.now() - ADMISSION_ATTEMPT_WINDOW_MS) > 0 ||
|
|
202
|
+
this.db.agentTools.hasRunning() ||
|
|
203
|
+
Boolean(
|
|
204
|
+
this.state.turn?.activeSubmissionId ||
|
|
205
|
+
this.state.approvals?.length ||
|
|
206
|
+
(this.state.activity?.activity && this.state.activity.activity !== "idle"),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** True while a Submission owns this Chat's active execution slice. */
|
|
211
|
+
protected isSubmissionExecuting(): boolean {
|
|
212
|
+
return this.submissions.currentActive() !== undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** External Runtime changes share the same serial queue as Submission execution. */
|
|
216
|
+
protected runConfigControl<T>(run: () => Promise<T>): Promise<T> {
|
|
217
|
+
return this.submissions.runConfigControl(run);
|
|
218
|
+
}
|
|
219
|
+
|
|
167
220
|
/** Let the Host prepare a validated client response before Tool settlement. */
|
|
168
221
|
protected prepareToolInteractionResponse(
|
|
169
222
|
input: ToolInteractionResponsePreparation,
|
|
@@ -173,7 +226,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
173
226
|
|
|
174
227
|
private activeRuntime?: ActiveRuntime;
|
|
175
228
|
private telemetryCoordinator?: RuntimeTelemetryCoordinator;
|
|
176
|
-
private activeHarness?: SessionHarness
|
|
229
|
+
private activeHarness?: SessionHarness;
|
|
177
230
|
private piAdapter?: HarnessRuntimeConfig;
|
|
178
231
|
private readonly transcript: ConversationHistory;
|
|
179
232
|
private conversationBridge?: HarnessConversationBridge;
|
|
@@ -306,7 +359,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
306
359
|
createExtensionHostBinding: (permissions, labels) =>
|
|
307
360
|
this.createExtensionHostBinding(permissions, labels),
|
|
308
361
|
advance: (phase) => this.runtimeLoad.advance(phase),
|
|
309
|
-
isExecuting: () => Boolean(this.submissions.currentActive()),
|
|
310
362
|
commit: (runtime) => {
|
|
311
363
|
this.telemetry.configure(candidate.telemetry);
|
|
312
364
|
this.activeRuntime = runtime;
|
|
@@ -358,14 +410,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
358
410
|
runtime: Awaited<ReturnType<typeof openSessionHarness>>,
|
|
359
411
|
submissionId: string,
|
|
360
412
|
): Promise<void> {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
413
|
+
const native = await runtime.submissionFor(submissionId, BACKGROUND_CONTEXT);
|
|
414
|
+
if (!native || native.entry === undefined) return;
|
|
415
|
+
const entries = await runtime.entries();
|
|
416
|
+
const start = entries.findIndex((entry) => entry.id === native.entry);
|
|
417
|
+
if (start < 0) throw new Error("Native Submission entry is missing from its conversation");
|
|
418
|
+
this.db.transaction(() => {
|
|
419
|
+
for (const entry of entries.slice(start)) this.db.submissions.linkSessionEntry(String(entry.id), submissionId);
|
|
420
|
+
});
|
|
369
421
|
}
|
|
370
422
|
|
|
371
423
|
/**
|
|
@@ -390,7 +442,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
390
442
|
}
|
|
391
443
|
await this.broadcastApprovals();
|
|
392
444
|
const running = this.db.submissions.findRunning() as StoredSubmission | null;
|
|
393
|
-
if (running) {
|
|
445
|
+
if (running && !running.unattendedFailure) {
|
|
394
446
|
if (
|
|
395
447
|
this.db.approvals.listPendingForSubmission(running.submissionId).length > 0 ||
|
|
396
448
|
this.db.interactions.listPendingForSubmission(running.submissionId).length > 0
|
|
@@ -504,52 +556,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
504
556
|
|
|
505
557
|
// #endregion
|
|
506
558
|
|
|
507
|
-
// #region Submission
|
|
508
|
-
|
|
509
|
-
/** Pin dynamic Context and execution policy before admission. */
|
|
510
|
-
private async admissionPin(fresh: boolean, submissionId: string): Promise<{
|
|
511
|
-
revision: string;
|
|
512
|
-
descriptor: string;
|
|
513
|
-
}> {
|
|
514
|
-
if (fresh) await this.prepareRuntimeForSubmission(submissionId);
|
|
515
|
-
else await this.ensureRuntimeReady();
|
|
516
|
-
return this.currentAssemblyPin();
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// initConfig can call this inside its serialized load; re-entering ensureRuntimeReady would deadlock.
|
|
520
|
-
private async currentAssemblyPin(): Promise<{
|
|
521
|
-
revision: string;
|
|
522
|
-
descriptor: string;
|
|
523
|
-
}> {
|
|
524
|
-
await this.runtimeTurnEvents.drain();
|
|
525
|
-
const runtime = this.activeRuntime;
|
|
526
|
-
if (!runtime) {
|
|
527
|
-
throw new Error("Runtime revision is not initialized");
|
|
528
|
-
}
|
|
529
|
-
return this.pinAssembly(runtime.pi, runtime.revision);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
private async pinAssembly(
|
|
533
|
-
prepared: PreparedPiRuntime,
|
|
534
|
-
baseRevision: string,
|
|
535
|
-
): Promise<{ revision: string; descriptor: string }> {
|
|
536
|
-
const pinned = await this.pi.pin({
|
|
537
|
-
prepared,
|
|
538
|
-
baseRevision,
|
|
539
|
-
readExtensionContext: ({ label }) =>
|
|
540
|
-
this._hostGetContext(label),
|
|
541
|
-
});
|
|
542
|
-
if (pinned.degradations.length > 0) {
|
|
543
|
-
console.warn(
|
|
544
|
-
"[runtime-context:degraded]",
|
|
545
|
-
json({ degradations: pinned.degradations }),
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
return {
|
|
549
|
-
revision: await sha256Hex(pinned.descriptor),
|
|
550
|
-
descriptor: pinned.descriptor,
|
|
551
|
-
};
|
|
552
|
-
}
|
|
559
|
+
// #region Submission 准入与当前配置
|
|
553
560
|
|
|
554
561
|
private get admission(): SubmissionAdmission {
|
|
555
562
|
return this.submissionAdmission ??= new SubmissionAdmission({
|
|
@@ -557,10 +564,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
557
564
|
steers: this.db.steers,
|
|
558
565
|
submissions: this.db.submissions,
|
|
559
566
|
transaction: (run) => this.db.transaction(run),
|
|
560
|
-
prepareRuntime:
|
|
561
|
-
await this.ensureRuntimeReady();
|
|
562
|
-
return { runtimeKey: this.activeRuntime!.runtimeKey, admission: this.activeRuntime?.admission };
|
|
563
|
-
},
|
|
567
|
+
prepareRuntime: () => this.prepareRuntimeForAdmission(),
|
|
564
568
|
findUserMessage: (id) => this.transcript.findUserMessage(id),
|
|
565
569
|
onAdmitted: (submission, hostAccepted) =>
|
|
566
570
|
this.runtimeTurnEvents.submissionAdmitted(submission, hostAccepted),
|
|
@@ -615,11 +619,16 @@ export abstract class AgentRuntimeKernel<
|
|
|
615
619
|
releaseAbortedHarness: async () => {
|
|
616
620
|
const runtime = this.activeHarness;
|
|
617
621
|
if (!runtime) return;
|
|
618
|
-
await runtime.
|
|
619
|
-
|
|
620
|
-
|
|
622
|
+
await runtime.conversation.waitForIdle(BACKGROUND_CONTEXT);
|
|
623
|
+
// The execution owner closes after transport projections settle.
|
|
624
|
+
|
|
625
|
+
},
|
|
626
|
+
cancelQueued: async (id) => {
|
|
627
|
+
const runtime = this.activeHarness;
|
|
628
|
+
if (!runtime) return undefined;
|
|
629
|
+
const result = await runtime.harness.abortSubmission(Number(id) as import("@earendil-works/pi-durable").SubmissionId, BACKGROUND_CONTEXT, runtime.conversation.id);
|
|
630
|
+
return { kind: result === "already_placed" || result === "settled" ? "already_consumed" as const : "cancelled" as const };
|
|
621
631
|
},
|
|
622
|
-
cancelQueued: async (id) => this.activeHarness?.lane.cancelQueued(id, BACKGROUND_CONTEXT),
|
|
623
632
|
submit: (message, options) => this.submitMessage(message, options),
|
|
624
633
|
waitUntil: (promise) => this.ctx.waitUntil(promise),
|
|
625
634
|
});
|
|
@@ -761,20 +770,43 @@ export abstract class AgentRuntimeKernel<
|
|
|
761
770
|
): Promise<StoredSubmission> {
|
|
762
771
|
const continuation = mode !== "fresh";
|
|
763
772
|
const submissionId = submission.submissionId;
|
|
764
|
-
submission = await this.admission.
|
|
773
|
+
submission = await this.admission.prepareCurrentRuntime(
|
|
765
774
|
submission,
|
|
766
|
-
() => this.
|
|
767
|
-
mode === "fresh" ? undefined : (key) => this.ensureRuntimeVersion(key),
|
|
775
|
+
(key) => this.ensureRuntimeVersion(key, true),
|
|
768
776
|
);
|
|
777
|
+
if (!continuation) await this.prepareRuntimeForSubmission(submissionId);
|
|
778
|
+
await this.runtimeTurnEvents.drain();
|
|
769
779
|
const activeRuntime = this.requireRuntime();
|
|
770
|
-
|
|
780
|
+
// A fresh provider Session per Submission keeps one failing upstream route from pinning later turns.
|
|
781
|
+
const recovery = new ModelRecovery(submissionId);
|
|
782
|
+
const harnessInput = await this.pi.harnessInputs(
|
|
771
783
|
activeRuntime.pi,
|
|
772
|
-
submission.assemblyDescriptor,
|
|
773
784
|
{
|
|
774
|
-
modelSessionId:
|
|
785
|
+
modelSessionId: () => recovery.sessionId(),
|
|
786
|
+
assertRequestAllowed: () => {
|
|
787
|
+
const current = this.db.submissions.find(submissionId);
|
|
788
|
+
if (current?.abortReason || current?.unattendedFailure) throw new Error(current.abortReason ?? current.unattendedFailure!);
|
|
789
|
+
},
|
|
775
790
|
onGeneration: (event) => this.telemetry.generation(submissionId, event),
|
|
791
|
+
onCompactionGeneration: (event) => this.runtimeTurnEvents.compactionGeneration(submissionId, event),
|
|
792
|
+
includeMemory: submission.origin !== "scheduled",
|
|
793
|
+
readExtensionContext: ({ label }) => this._hostGetContext(label),
|
|
776
794
|
},
|
|
777
795
|
);
|
|
796
|
+
if (harnessInput.degradations.length > 0) {
|
|
797
|
+
console.warn("[runtime-context:degraded]", json({ degradations: harnessInput.degradations }));
|
|
798
|
+
}
|
|
799
|
+
const prepareToolInput = (toolName: string, input: unknown): unknown => {
|
|
800
|
+
const prepare = harnessInput.candidates.find(({ tool }) =>
|
|
801
|
+
tool.name === toolName
|
|
802
|
+
)?.tool.prepareArguments;
|
|
803
|
+
if (!prepare) return input;
|
|
804
|
+
try {
|
|
805
|
+
return prepare(input);
|
|
806
|
+
} catch {
|
|
807
|
+
return input;
|
|
808
|
+
}
|
|
809
|
+
};
|
|
778
810
|
let runtime!: Awaited<ReturnType<typeof openSessionHarness>>;
|
|
779
811
|
let publishApprovalRequest = async (
|
|
780
812
|
_toolCallId: string,
|
|
@@ -785,6 +817,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
785
817
|
submissionId,
|
|
786
818
|
requestId: submission.requestId,
|
|
787
819
|
executionLevel: activeRuntime.snapshot.profile.executionLevel,
|
|
820
|
+
origin: submission.origin,
|
|
821
|
+
scheduledMaxExecutionLevel: submission.scheduledMaxExecutionLevel ?? undefined,
|
|
788
822
|
},
|
|
789
823
|
candidates: harnessInput.candidates,
|
|
790
824
|
telemetry: this.telemetry,
|
|
@@ -812,11 +846,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
812
846
|
}
|
|
813
847
|
},
|
|
814
848
|
gateTool: harnessInput.platform.gateTool,
|
|
849
|
+
onUnattendedFailure: (reason) => {
|
|
850
|
+
this.db.submissions.recordUnattendedFailure(submissionId, reason);
|
|
851
|
+
},
|
|
852
|
+
unattendedFailure: () => this.db.submissions.find(submissionId)?.unattendedFailure,
|
|
815
853
|
});
|
|
816
854
|
runtime = await openSessionHarness({
|
|
817
|
-
|
|
855
|
+
storage: openCloudflareSession(this.ctx.storage),
|
|
818
856
|
models: harnessInput.models,
|
|
819
|
-
modelSessionId: submissionId,
|
|
820
857
|
model: harnessInput.model,
|
|
821
858
|
thinkingLevel: harnessInput.thinkingLevel,
|
|
822
859
|
systemPrompt: harnessInput.systemPrompt,
|
|
@@ -824,30 +861,29 @@ export abstract class AgentRuntimeKernel<
|
|
|
824
861
|
workspace: harnessInput.workspace,
|
|
825
862
|
authorize: gate.authorize.bind(gate),
|
|
826
863
|
resolveReplay: gate.resolveReplay.bind(gate),
|
|
864
|
+
resolveConfirmation: gate.resolveConfirmation.bind(gate),
|
|
827
865
|
}),
|
|
866
|
+
...harnessToolActivation(harnessInput.candidates),
|
|
828
867
|
modelToolResultExemptions: harnessInput.candidates
|
|
829
868
|
.filter(({ outputBudget }) => outputBudget?.kind === "structure")
|
|
830
869
|
.map(({ tool }) => tool.name),
|
|
831
870
|
streamOptions: { maxRetries: 0 },
|
|
832
871
|
compaction: harnessInput.compaction,
|
|
833
872
|
toolExecution: "parallel",
|
|
873
|
+
recovery,
|
|
874
|
+
beforeTool: (call, _api, context) => gate.beforeTool({ toolName: call.name, toolCallId: call.id, args: call.arguments }, context),
|
|
875
|
+
afterResponse: (message) => {
|
|
876
|
+
for (const part of message.content) if (part.type === "toolCall") part.arguments = prepareToolInput(part.name, part.arguments) as JsonObject;
|
|
877
|
+
},
|
|
834
878
|
}, BACKGROUND_CONTEXT);
|
|
835
879
|
this.activeHarness = runtime;
|
|
836
|
-
let stream: ReturnType<typeof bindHarnessStream
|
|
880
|
+
let stream: Awaited<ReturnType<typeof bindHarnessStream>> | undefined;
|
|
837
881
|
let deactivate = () => {};
|
|
882
|
+
let released = false;
|
|
838
883
|
try {
|
|
839
|
-
runtime.harness.hooks.on("before_tool", gate.beforeTool);
|
|
840
|
-
runtime.harness.hooks.on("before_drive", async (event, context) => {
|
|
841
|
-
if (this.db.submissions.find(submissionId)?.abortReason) {
|
|
842
|
-
await runtime.requestAbort(event.runId, context);
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
884
|
|
|
846
|
-
if (submission.abortReason && runtime.openOperation) {
|
|
847
|
-
await runtime.
|
|
848
|
-
runtime.openOperation.operationId,
|
|
849
|
-
BACKGROUND_CONTEXT,
|
|
850
|
-
);
|
|
885
|
+
if ((submission.abortReason || submission.unattendedFailure) && runtime.openOperation) {
|
|
886
|
+
await runtime.abort(BACKGROUND_CONTEXT);
|
|
851
887
|
}
|
|
852
888
|
|
|
853
889
|
await this.associateHarnessEntries(runtime, submissionId);
|
|
@@ -858,26 +894,10 @@ export abstract class AgentRuntimeKernel<
|
|
|
858
894
|
(id) => this.transcript.regenerationTarget(id),
|
|
859
895
|
BACKGROUND_CONTEXT,
|
|
860
896
|
);
|
|
861
|
-
if (preparation.kind === "settled") {
|
|
862
|
-
const record = preparation.outcome;
|
|
863
|
-
const terminal = await this.submissions.finish(
|
|
864
|
-
submission,
|
|
865
|
-
record.status === "completed" || record.status === "declined"
|
|
866
|
-
? "succeeded" : record.status === "aborted" ? "aborted" : "failed",
|
|
867
|
-
record.error?.message,
|
|
868
|
-
);
|
|
869
|
-
this.broadcast(json({
|
|
870
|
-
type: MessageType.CF_AGENT_CHAT_MESSAGES,
|
|
871
|
-
messages: await this.getMessages(),
|
|
872
|
-
}));
|
|
873
|
-
return terminal;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
897
|
if (submission.status === "pending") {
|
|
877
898
|
this.db.submissions.transition(submissionId, "running", ["pending"]);
|
|
878
899
|
this.telemetry.capture("turnStarted", {
|
|
879
900
|
submissionId,
|
|
880
|
-
assemblyRevision: submission.assemblyRevision,
|
|
881
901
|
});
|
|
882
902
|
submission = this.db.submissions.find(submissionId)!;
|
|
883
903
|
}
|
|
@@ -886,70 +906,58 @@ export abstract class AgentRuntimeKernel<
|
|
|
886
906
|
steers: this.db.steers,
|
|
887
907
|
messageUi: this.db.messageUi,
|
|
888
908
|
transaction: (run) => this.db.transaction(run),
|
|
889
|
-
enqueue: async (
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
return queued.
|
|
909
|
+
enqueue: async (steer) => {
|
|
910
|
+
const message = JSON.parse(steer.canonicalJson) as PiCanonicalUserInput;
|
|
911
|
+
const queued = await runtime.conversation.submit({ type: "input", content: message.content, requestId: `steer:${steer.messageId}`, whenBusy: "steer" }, BACKGROUND_CONTEXT);
|
|
912
|
+
return queued.status(BACKGROUND_CONTEXT);
|
|
893
913
|
},
|
|
914
|
+
find: (messageId) => runtime.submissionFor(`steer:${messageId}`, BACKGROUND_CONTEXT),
|
|
894
915
|
});
|
|
895
916
|
const uiMessageBody = submission.queuedUiMessageJson;
|
|
896
917
|
let initialUserSidecarPersisted = (await this.transcript.storedMessages())
|
|
897
918
|
.some((entry) =>
|
|
898
919
|
entry.submissionId === submissionId && entry.message.role === "user"
|
|
899
920
|
);
|
|
900
|
-
runtime.harness.
|
|
901
|
-
const entry = event.entry;
|
|
921
|
+
const unsubscribeEntries = runtime.harness.subscribeCommits((publication) => {
|
|
902
922
|
this.db.transaction(() => {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (
|
|
906
|
-
|
|
923
|
+
for (const change of publication.changes) {
|
|
924
|
+
if (change.type === "submission") steerDelivery.settleRecord(change.value);
|
|
925
|
+
if (change.type !== "entry" || change.value.conversationId !== runtime.conversation.id) continue;
|
|
926
|
+
const entry = change.value;
|
|
927
|
+
this.db.submissions.linkSessionEntry(String(entry.id), submissionId);
|
|
928
|
+
if (entry.kind === "pi.user" && !initialUserSidecarPersisted && uiMessageBody) {
|
|
929
|
+
this.db.messageUi.upsert(String(entry.id), uiMessageBody);
|
|
907
930
|
initialUserSidecarPersisted = true;
|
|
908
931
|
}
|
|
909
932
|
}
|
|
910
933
|
});
|
|
911
|
-
|
|
912
934
|
});
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
await steerDelivery.reconcile(submissionId, pendingWatch.snapshot);
|
|
916
|
-
} finally {
|
|
917
|
-
pendingWatch.unsubscribe();
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
this.runtimeTurnEvents.observe(runtime.harness, submissionId, harnessInput.model);
|
|
921
|
-
stream = bindHarnessStream(runtime.harness.events, {
|
|
935
|
+
await this.runtimeTurnEvents.observe(runtime, submissionId, harnessInput.model);
|
|
936
|
+
stream = await bindHarnessStream(runtime, {
|
|
922
937
|
messageId: submission.assistantMessageId,
|
|
923
938
|
userMessageId: submission.userMessageId ?? undefined,
|
|
924
939
|
startedAt: submission.createdAt,
|
|
925
940
|
turnId: submissionId,
|
|
941
|
+
prepareToolInput,
|
|
926
942
|
publish: async (chunks) => {
|
|
927
|
-
for (const chunk of chunks) {
|
|
928
|
-
this.conversation.broadcastResponse({
|
|
929
|
-
id: submission.requestId,
|
|
930
|
-
body: json(chunk),
|
|
931
|
-
done: false,
|
|
932
|
-
continuation,
|
|
933
|
-
});
|
|
934
|
-
}
|
|
943
|
+
for (const chunk of chunks) this.conversation.broadcastResponse({ id: submission.requestId, body: json(chunk), done: false, continuation });
|
|
935
944
|
},
|
|
936
945
|
});
|
|
946
|
+
deactivate = unsubscribeEntries;
|
|
947
|
+
|
|
937
948
|
publishApprovalRequest = stream.publishApprovalRequest;
|
|
938
|
-
this.conversation.activate(runtime
|
|
949
|
+
this.conversation.activate(runtime, submission);
|
|
939
950
|
|
|
940
951
|
const control = {
|
|
941
952
|
abort: async () => {
|
|
942
953
|
if (runtime.openOperation) {
|
|
943
|
-
await runtime.
|
|
944
|
-
runtime.openOperation.operationId,
|
|
945
|
-
BACKGROUND_CONTEXT,
|
|
946
|
-
);
|
|
954
|
+
await runtime.abort(BACKGROUND_CONTEXT);
|
|
947
955
|
}
|
|
948
956
|
},
|
|
949
957
|
interrupt: () => void runtime.close(BACKGROUND_CONTEXT),
|
|
950
958
|
steer: async (_message: PiCanonicalUserInput, messageId: string) => {
|
|
951
959
|
const steer = this.db.steers.findByMessageId(messageId);
|
|
952
|
-
if (steer) await steerDelivery.queue(steer);
|
|
960
|
+
if (steer && runtime.openOperation) await steerDelivery.queue(steer);
|
|
953
961
|
},
|
|
954
962
|
};
|
|
955
963
|
const turn: ActiveTurn = {
|
|
@@ -960,36 +968,28 @@ export abstract class AgentRuntimeKernel<
|
|
|
960
968
|
continuation,
|
|
961
969
|
agent: control,
|
|
962
970
|
};
|
|
963
|
-
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
971
|
+
const deactivateTurn = this.submissions.activate(submission, turn);
|
|
972
|
+
const unsubscribeEntriesAndTurn = deactivate;
|
|
973
|
+
deactivate = () => { unsubscribeEntriesAndTurn(); deactivateTurn(); };
|
|
974
|
+
await runtime.admit(preparation, BACKGROUND_CONTEXT);
|
|
975
|
+
await steerDelivery.reconcile(submissionId);
|
|
976
|
+
const outcome = await runtime.run(undefined, BACKGROUND_CONTEXT);
|
|
967
977
|
this.db.submissions.clearQueuedPayload(submissionId);
|
|
968
978
|
if (outcome.kind === "released") {
|
|
969
|
-
if (
|
|
970
|
-
outcome.reason === "slice" ||
|
|
971
|
-
outcome.reason === "budget" ||
|
|
972
|
-
outcome.reason === "tool-failure"
|
|
973
|
-
) {
|
|
979
|
+
if (outcome.reason === "slice") {
|
|
974
980
|
await this.schedule(0, "_piHarnessContinuation", { submissionId }, {
|
|
975
981
|
idempotent: true,
|
|
976
982
|
});
|
|
977
983
|
}
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
if (outcome.kind === "waiting") {
|
|
981
|
-
await this.schedule(
|
|
982
|
-
outcome.reason === "retry"
|
|
983
|
-
? Math.max(0, Math.ceil((outcome.notBefore - Date.now()) / 1000))
|
|
984
|
-
: Math.max(0, Math.ceil((outcome.deferred.pollAfterMs ?? 1_000) / 1000)),
|
|
985
|
-
"_piHarnessContinuation",
|
|
986
|
-
{ submissionId },
|
|
987
|
-
{ idempotent: true },
|
|
988
|
-
);
|
|
984
|
+
released = true;
|
|
989
985
|
return this.db.submissions.find(submissionId)!;
|
|
990
986
|
}
|
|
991
987
|
const record = outcome.outcome;
|
|
992
|
-
const
|
|
988
|
+
const unattendedFailure = this.db.submissions.find(submissionId)
|
|
989
|
+
?.unattendedFailure;
|
|
990
|
+
const status = unattendedFailure
|
|
991
|
+
? "failed"
|
|
992
|
+
: record.status === "completed" || record.status === "declined"
|
|
993
993
|
? "succeeded"
|
|
994
994
|
: record.status === "aborted"
|
|
995
995
|
? "aborted"
|
|
@@ -998,7 +998,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
998
998
|
const terminal = await this.submissions.finish(
|
|
999
999
|
submission,
|
|
1000
1000
|
status,
|
|
1001
|
-
record.error?.message,
|
|
1001
|
+
unattendedFailure ?? record.error?.message,
|
|
1002
1002
|
);
|
|
1003
1003
|
const projectedOutcome = projectSubmissionOutcome(record, terminal);
|
|
1004
1004
|
await stream.flushTerminal(projectedOutcome);
|
|
@@ -1015,13 +1015,21 @@ export abstract class AgentRuntimeKernel<
|
|
|
1015
1015
|
}));
|
|
1016
1016
|
throw error;
|
|
1017
1017
|
} finally {
|
|
1018
|
-
await this.conversation.release(runtime
|
|
1018
|
+
await this.conversation.release(runtime).catch((error) => {
|
|
1019
1019
|
console.warn("[runtime-stream:degraded]", errorText(error));
|
|
1020
1020
|
});
|
|
1021
|
-
stream?.unsubscribe();
|
|
1021
|
+
await stream?.unsubscribe();
|
|
1022
1022
|
deactivate();
|
|
1023
1023
|
if (this.activeHarness === runtime) this.activeHarness = undefined;
|
|
1024
1024
|
await runtime.close(BACKGROUND_CONTEXT).catch(() => undefined);
|
|
1025
|
+
if (released) {
|
|
1026
|
+
// Pi closes the Harness on release and drops undelivered stream frames; the
|
|
1027
|
+
// canonical transcript gives a paused question or approval its complete input.
|
|
1028
|
+
this.broadcast(json({
|
|
1029
|
+
type: MessageType.CF_AGENT_CHAT_MESSAGES,
|
|
1030
|
+
messages: await this.getMessages(),
|
|
1031
|
+
}));
|
|
1032
|
+
}
|
|
1025
1033
|
}
|
|
1026
1034
|
}
|
|
1027
1035
|
|
|
@@ -1055,7 +1063,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1055
1063
|
/** Start the native resume handshake from Pi's current lane snapshot. */
|
|
1056
1064
|
async onConnect(connection: Connection): Promise<void> {
|
|
1057
1065
|
this.conversation.disconnect(connection);
|
|
1058
|
-
await this.conversation.
|
|
1066
|
+
await this.conversation.connect(connection);
|
|
1059
1067
|
}
|
|
1060
1068
|
|
|
1061
1069
|
async onClose(connection: Connection, code: number, reason: string, wasClean: boolean): Promise<void> {
|
|
@@ -1152,10 +1160,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
1152
1160
|
|
|
1153
1161
|
if (event.type === "clear") {
|
|
1154
1162
|
if (this.submissions.isBusy()) return;
|
|
1155
|
-
this.
|
|
1156
|
-
|
|
1157
|
-
this.db.clearAll();
|
|
1158
|
-
});
|
|
1163
|
+
await this.transcript.clear();
|
|
1164
|
+
this.db.transaction(() => this.db.clearAll());
|
|
1159
1165
|
await this.broadcastApprovals();
|
|
1160
1166
|
this.broadcast(json({ type: MessageType.CF_AGENT_CHAT_CLEAR }));
|
|
1161
1167
|
return;
|
|
@@ -1297,7 +1303,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1297
1303
|
await this.broadcastApprovals();
|
|
1298
1304
|
return;
|
|
1299
1305
|
}
|
|
1300
|
-
this.transcript.append(crypto.randomUUID(), message);
|
|
1306
|
+
await this.transcript.append(crypto.randomUUID(), message);
|
|
1301
1307
|
}
|
|
1302
1308
|
|
|
1303
1309
|
async _hostGetSessionInfo(): Promise<{ messageCount: number }> {
|
|
@@ -1320,7 +1326,36 @@ export abstract class AgentRuntimeKernel<
|
|
|
1320
1326
|
async addCanonicalSnapshot(
|
|
1321
1327
|
snapshot: PiCanonicalTranscriptSnapshot,
|
|
1322
1328
|
): Promise<void> {
|
|
1323
|
-
this.transcript.importSnapshot(snapshot);
|
|
1329
|
+
await this.transcript.importSnapshot(snapshot);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
/**
|
|
1333
|
+
* 本 Session 是否有一条未结束的定时 Submission。
|
|
1334
|
+
*
|
|
1335
|
+
* 定时 Run 未结束时独占输入:普通消息在准入前被拒绝,不排队等它结束。
|
|
1336
|
+
* 定时注入本身走 `submitScheduledPrompt`,不经过这道闸。
|
|
1337
|
+
*/
|
|
1338
|
+
private get scheduledInputLocked(): boolean {
|
|
1339
|
+
return this.db.submissions.hasUnfinishedScheduled();
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
private rejectedOrdinaryInputReceipt(): SubmissionReceipt {
|
|
1343
|
+
return {
|
|
1344
|
+
submissionId: crypto.randomUUID(),
|
|
1345
|
+
status: "skipped",
|
|
1346
|
+
accepted: false,
|
|
1347
|
+
error: SCHEDULED_INPUT_LOCKED_MESSAGE,
|
|
1348
|
+
retryable: false,
|
|
1349
|
+
createdAt: Date.now(),
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
private rejectInputLocked(): MessageDispatchReceipt {
|
|
1354
|
+
return {
|
|
1355
|
+
kind: "rejected",
|
|
1356
|
+
code: "input_locked",
|
|
1357
|
+
message: SCHEDULED_INPUT_LOCKED_MESSAGE,
|
|
1358
|
+
};
|
|
1324
1359
|
}
|
|
1325
1360
|
|
|
1326
1361
|
/** Admit text and return immediately; completion continues in the Durable Object. */
|
|
@@ -1328,6 +1363,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1328
1363
|
text: string,
|
|
1329
1364
|
options?: { idempotencyKey?: string },
|
|
1330
1365
|
): Promise<SubmissionReceipt> {
|
|
1366
|
+
if (this.scheduledInputLocked) return this.rejectedOrdinaryInputReceipt();
|
|
1331
1367
|
const submitted = await this.submitMessage({
|
|
1332
1368
|
role: "user",
|
|
1333
1369
|
content: [{ type: "text", text }],
|
|
@@ -1357,17 +1393,24 @@ export abstract class AgentRuntimeKernel<
|
|
|
1357
1393
|
}
|
|
1358
1394
|
|
|
1359
1395
|
async dispatchMessage(message: UIMessage, delivery: MessageDelivery): Promise<MessageDispatchReceipt> {
|
|
1396
|
+
if (this.scheduledInputLocked) return this.rejectInputLocked();
|
|
1360
1397
|
return this.delivery.dispatch(message, delivery);
|
|
1361
1398
|
}
|
|
1362
1399
|
|
|
1363
1400
|
async steerQueuedSubmission(submissionId: string): Promise<MessageDispatchReceipt> {
|
|
1401
|
+
if (this.scheduledInputLocked) return this.rejectInputLocked();
|
|
1364
1402
|
return this.delivery.steerQueued(submissionId);
|
|
1365
1403
|
}
|
|
1366
1404
|
|
|
1367
1405
|
/** Admit scheduled work after a bounded stable wait, otherwise return skipped. */
|
|
1368
1406
|
async submitScheduledPrompt(
|
|
1369
1407
|
prompt: string,
|
|
1370
|
-
options?: {
|
|
1408
|
+
options?: {
|
|
1409
|
+
idempotencyKey?: string;
|
|
1410
|
+
scheduledMaxExecutionLevel?: ExecutionLevel;
|
|
1411
|
+
/** @deprecated Retained for rolling upgrades; ignored by Runtime. */
|
|
1412
|
+
unattendedExecutionGrant?: RuntimeUnattendedExecutionGrant;
|
|
1413
|
+
},
|
|
1371
1414
|
): Promise<SubmissionReceipt> {
|
|
1372
1415
|
const requestId = crypto.randomUUID();
|
|
1373
1416
|
const submitted = await this.submitMessageWhenStable(
|
|
@@ -1379,6 +1422,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
1379
1422
|
{
|
|
1380
1423
|
requestId,
|
|
1381
1424
|
idempotencyKey: options?.idempotencyKey,
|
|
1425
|
+
origin: "scheduled",
|
|
1426
|
+
scheduledMaxExecutionLevel: options?.scheduledMaxExecutionLevel,
|
|
1382
1427
|
},
|
|
1383
1428
|
SCHEDULED_STABLE_TIMEOUT_MS,
|
|
1384
1429
|
);
|
|
@@ -1463,7 +1508,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1463
1508
|
summary: approval.summary,
|
|
1464
1509
|
executionLevel: approval.executionLevel,
|
|
1465
1510
|
requiredExecutionLevel: approval.requiredExecutionLevel,
|
|
1466
|
-
inputJson: approval.inputJson,
|
|
1511
|
+
inputJson: approval.confirmationJson ?? approval.inputJson,
|
|
1467
1512
|
requestId: approval.requestId,
|
|
1468
1513
|
};
|
|
1469
1514
|
}
|
|
@@ -1479,11 +1524,19 @@ export abstract class AgentRuntimeKernel<
|
|
|
1479
1524
|
if (!submission || isTerminalSubmissionStatus(submission.status)) {
|
|
1480
1525
|
return { ok: false };
|
|
1481
1526
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1527
|
+
const active = this.submissions.currentActive();
|
|
1528
|
+
if (active && active.submissionId !== submission.submissionId) {
|
|
1529
|
+
return { ok: false };
|
|
1530
|
+
}
|
|
1531
|
+
// ask_user has a Config-independent validator. A parked answer must not
|
|
1532
|
+
// replace the Gateway; the continuation loads current Config instead.
|
|
1533
|
+
const askUser = pending.toolName === ASK_USER_TOOL_NAME;
|
|
1534
|
+
if (!askUser && !active) {
|
|
1535
|
+
await this.ensureRuntimeVersion(submission.runtimeKey, true);
|
|
1536
|
+
}
|
|
1537
|
+
const spec = askUser
|
|
1538
|
+
? askUserPiToolCandidate().interaction
|
|
1539
|
+
: this.pi.interaction(this.requireRuntime().pi, pending.toolName);
|
|
1487
1540
|
if (!spec) return { ok: false };
|
|
1488
1541
|
|
|
1489
1542
|
const accepted = await this.humanDecisions.respond(
|
|
@@ -1573,6 +1626,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1573
1626
|
hasPendingInteraction: (id) => this.humanDecisions.isWaitingForInteraction(id),
|
|
1574
1627
|
hasPendingSteer: (id) => this.db.steers.listForSubmission(id).length > 0,
|
|
1575
1628
|
hasRunningAgentTools: this.db.agentTools.hasRunning(),
|
|
1629
|
+
hasUnfinishedScheduled: this.db.submissions.hasUnfinishedScheduled(),
|
|
1576
1630
|
previousState: this.state,
|
|
1577
1631
|
});
|
|
1578
1632
|
if (changed) this.setState(state);
|