@springbrand/agent-runtime 0.3.0-alpha.2 → 0.3.0-alpha.20
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 +177 -122
- package/src/adapter/cloudflare/define-agent.ts +47 -26
- 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 +15 -0
- 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/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 +51 -13
- package/src/config/model-guidance.ts +20 -0
- package/src/config/pinning.ts +39 -5
- package/src/config/ports.ts +57 -7
- package/src/config/public-contracts.ts +2 -2
- 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 +12 -0
- package/src/conversation/message/conversion.ts +3 -3
- 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 +255 -446
- 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 +12 -1
- package/src/submission/contracts.ts +42 -2
- package/src/submission/delivery.ts +1 -1
- package/src/submission/finalization.ts +36 -15
- package/src/submission/repository.ts +64 -25
- package/src/submission/steer-delivery.ts +32 -76
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/coordinator.ts +1 -1
- 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 +317 -0
- package/src/tools/assistant/candidate.ts +15 -0
- package/src/tools/assistant/followups.ts +34 -0
- package/src/tools/assistant/index.ts +19 -0
- package/src/tools/assistant/memory.ts +73 -0
- package/src/tools/assistant/plan.ts +98 -0
- package/src/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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@springbrand/agent-runtime",
|
|
3
|
-
"version": "0.3.0-alpha.
|
|
3
|
+
"version": "0.3.0-alpha.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -13,28 +13,30 @@
|
|
|
13
13
|
".": "./src/index.ts",
|
|
14
14
|
"./adapter/cloudflare": "./src/adapter/cloudflare/index.ts",
|
|
15
15
|
"./contracts": "./src/config/public-contracts.ts",
|
|
16
|
-
"./declared-tool": "./src/tools/declared.ts",
|
|
16
|
+
"./declared-tool": "./src/tools/adapters/declared.ts",
|
|
17
17
|
"./models": "./src/lib/model-catalog.ts"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@cloudflare/codemode": "0.5.
|
|
20
|
+
"@cloudflare/codemode": "0.5.2",
|
|
21
21
|
"@cloudflare/sandbox": "0.12.4",
|
|
22
22
|
"@cloudflare/shell": "0.4.3",
|
|
23
|
-
"@cloudflare/think": "0.
|
|
24
|
-
"@earendil-works/pi-agent-core": "
|
|
25
|
-
"@earendil-works/pi-ai": "
|
|
23
|
+
"@cloudflare/think": "0.18.0",
|
|
24
|
+
"@earendil-works/pi-agent-core": "1.1.0",
|
|
25
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
26
26
|
"@types/lodash-es": "^4.17.12",
|
|
27
27
|
"cron-schedule": "6.0.0",
|
|
28
28
|
"isomorphic-git": "1.38.6",
|
|
29
29
|
"lodash-es": "^4.18.1",
|
|
30
|
-
"zod": "^4.4.3"
|
|
30
|
+
"zod": "^4.4.3",
|
|
31
|
+
"@earendil-works/pi-durable": "1.1.0",
|
|
32
|
+
"@earendil-works/chord": "1.1.0"
|
|
31
33
|
},
|
|
32
34
|
"peerDependencies": {
|
|
33
|
-
"agents": "^0.
|
|
35
|
+
"agents": "^0.23.0",
|
|
34
36
|
"ai": "^7.0.0"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
37
|
-
"agents": "0.
|
|
39
|
+
"agents": "0.23.0",
|
|
38
40
|
"ai": "7.0.56"
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
type SubmissionReceipt,
|
|
34
34
|
type SubmissionExecutionMode,
|
|
35
35
|
type SubmissionHandle,
|
|
36
|
+
type RuntimeUnattendedExecutionGrant,
|
|
36
37
|
} from "../../submission";
|
|
37
38
|
import type { ExecutionLevel } from "../../lib/execution-level";
|
|
38
39
|
import {
|
|
@@ -66,13 +67,15 @@ import {
|
|
|
66
67
|
import type { UIMessage, UIMessageChunk } from "ai";
|
|
67
68
|
import type {
|
|
68
69
|
AssistantMessage,
|
|
70
|
+
JsonObject,
|
|
69
71
|
} from "@earendil-works/pi-ai";
|
|
70
|
-
import { BACKGROUND_CONTEXT } from "@earendil-works/
|
|
72
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
71
73
|
import { openCloudflareSession } from "../../session";
|
|
72
|
-
import { openSessionHarness, prepareSubmissionOperation } from "../../harness";
|
|
74
|
+
import { ModelRecovery, openSessionHarness, prepareSubmissionOperation } from "../../harness";
|
|
73
75
|
import type { SessionHarness } from "../../harness";
|
|
74
76
|
import {
|
|
75
77
|
compileHarnessTools,
|
|
78
|
+
harnessToolActivation,
|
|
76
79
|
decodeSubagentCompletion,
|
|
77
80
|
HarnessDecisionGate,
|
|
78
81
|
HarnessHumanDecisions,
|
|
@@ -98,6 +101,10 @@ import { RuntimeDatabase } from "./database";
|
|
|
98
101
|
// #region Runtime 内部数据与基础工具
|
|
99
102
|
|
|
100
103
|
const SCHEDULED_STABLE_TIMEOUT_MS = 30_000;
|
|
104
|
+
/** 定时 Run 未结束时拒绝普通输入的统一原因,服务端与客户端状态共用同一事实。 */
|
|
105
|
+
const SCHEDULED_INPUT_LOCKED_MESSAGE =
|
|
106
|
+
"This scheduled run is still in progress. Wait for it to finish before sending a message.";
|
|
107
|
+
|
|
101
108
|
interface ActiveTurn {
|
|
102
109
|
submissionId: string;
|
|
103
110
|
requestId: string;
|
|
@@ -149,11 +156,16 @@ export abstract class AgentRuntimeKernel<
|
|
|
149
156
|
};
|
|
150
157
|
|
|
151
158
|
protected abstract ensureRuntimeReady(): Promise<void>;
|
|
152
|
-
/**
|
|
159
|
+
/** Prepare per-Submission Host state before the first assembly pin. */
|
|
153
160
|
protected prepareRuntimeForSubmission(_submissionId: string): Promise<void> {
|
|
154
161
|
return this.ensureRuntimeReady();
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
/** Runs before a new Submission records its Runtime key. */
|
|
165
|
+
protected prepareRuntimeForAdmission(): Promise<void> {
|
|
166
|
+
return this.ensureRuntimeReady();
|
|
167
|
+
}
|
|
168
|
+
|
|
157
169
|
protected ensureRuntimeVersion(_runtimeKey: string): Promise<void> {
|
|
158
170
|
return this.ensureRuntimeReady();
|
|
159
171
|
}
|
|
@@ -173,7 +185,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
173
185
|
|
|
174
186
|
private activeRuntime?: ActiveRuntime;
|
|
175
187
|
private telemetryCoordinator?: RuntimeTelemetryCoordinator;
|
|
176
|
-
private activeHarness?: SessionHarness
|
|
188
|
+
private activeHarness?: SessionHarness;
|
|
177
189
|
private piAdapter?: HarnessRuntimeConfig;
|
|
178
190
|
private readonly transcript: ConversationHistory;
|
|
179
191
|
private conversationBridge?: HarnessConversationBridge;
|
|
@@ -358,14 +370,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
358
370
|
runtime: Awaited<ReturnType<typeof openSessionHarness>>,
|
|
359
371
|
submissionId: string,
|
|
360
372
|
): Promise<void> {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
373
|
+
const native = await runtime.submissionFor(submissionId, BACKGROUND_CONTEXT);
|
|
374
|
+
if (!native || native.entry === undefined) return;
|
|
375
|
+
const entries = await runtime.entries();
|
|
376
|
+
const start = entries.findIndex((entry) => entry.id === native.entry);
|
|
377
|
+
if (start < 0) throw new Error("Native Submission entry is missing from its conversation");
|
|
378
|
+
this.db.transaction(() => {
|
|
379
|
+
for (const entry of entries.slice(start)) this.db.submissions.linkSessionEntry(String(entry.id), submissionId);
|
|
380
|
+
});
|
|
369
381
|
}
|
|
370
382
|
|
|
371
383
|
/**
|
|
@@ -390,7 +402,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
390
402
|
}
|
|
391
403
|
await this.broadcastApprovals();
|
|
392
404
|
const running = this.db.submissions.findRunning() as StoredSubmission | null;
|
|
393
|
-
if (running) {
|
|
405
|
+
if (running && !running.unattendedFailure) {
|
|
394
406
|
if (
|
|
395
407
|
this.db.approvals.listPendingForSubmission(running.submissionId).length > 0 ||
|
|
396
408
|
this.db.interactions.listPendingForSubmission(running.submissionId).length > 0
|
|
@@ -506,18 +518,21 @@ export abstract class AgentRuntimeKernel<
|
|
|
506
518
|
|
|
507
519
|
// #region Submission 准入与配置固定
|
|
508
520
|
|
|
509
|
-
/** Pin dynamic Context and execution policy before
|
|
521
|
+
/** Pin dynamic Context and execution policy before the first assembly is used. */
|
|
510
522
|
private async admissionPin(fresh: boolean, submissionId: string): Promise<{
|
|
511
523
|
revision: string;
|
|
512
524
|
descriptor: string;
|
|
513
525
|
}> {
|
|
514
526
|
if (fresh) await this.prepareRuntimeForSubmission(submissionId);
|
|
515
|
-
|
|
516
|
-
|
|
527
|
+
const submission = this.db.submissions.find(submissionId);
|
|
528
|
+
if (!submission) {
|
|
529
|
+
throw new Error(`Unknown SpringBrand submission: ${submissionId}`);
|
|
530
|
+
}
|
|
531
|
+
return this.currentAssemblyPin(submission.origin !== "scheduled");
|
|
517
532
|
}
|
|
518
533
|
|
|
519
534
|
// initConfig can call this inside its serialized load; re-entering ensureRuntimeReady would deadlock.
|
|
520
|
-
private async currentAssemblyPin(): Promise<{
|
|
535
|
+
private async currentAssemblyPin(includeMemory = true): Promise<{
|
|
521
536
|
revision: string;
|
|
522
537
|
descriptor: string;
|
|
523
538
|
}> {
|
|
@@ -526,16 +541,18 @@ export abstract class AgentRuntimeKernel<
|
|
|
526
541
|
if (!runtime) {
|
|
527
542
|
throw new Error("Runtime revision is not initialized");
|
|
528
543
|
}
|
|
529
|
-
return this.pinAssembly(runtime.pi, runtime.revision);
|
|
544
|
+
return this.pinAssembly(runtime.pi, runtime.revision, includeMemory);
|
|
530
545
|
}
|
|
531
546
|
|
|
532
547
|
private async pinAssembly(
|
|
533
548
|
prepared: PreparedPiRuntime,
|
|
534
549
|
baseRevision: string,
|
|
550
|
+
includeMemory: boolean,
|
|
535
551
|
): Promise<{ revision: string; descriptor: string }> {
|
|
536
552
|
const pinned = await this.pi.pin({
|
|
537
553
|
prepared,
|
|
538
554
|
baseRevision,
|
|
555
|
+
includeMemory,
|
|
539
556
|
readExtensionContext: ({ label }) =>
|
|
540
557
|
this._hostGetContext(label),
|
|
541
558
|
});
|
|
@@ -558,7 +575,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
558
575
|
submissions: this.db.submissions,
|
|
559
576
|
transaction: (run) => this.db.transaction(run),
|
|
560
577
|
prepareRuntime: async () => {
|
|
561
|
-
await this.
|
|
578
|
+
await this.prepareRuntimeForAdmission();
|
|
562
579
|
return { runtimeKey: this.activeRuntime!.runtimeKey, admission: this.activeRuntime?.admission };
|
|
563
580
|
},
|
|
564
581
|
findUserMessage: (id) => this.transcript.findUserMessage(id),
|
|
@@ -615,11 +632,16 @@ export abstract class AgentRuntimeKernel<
|
|
|
615
632
|
releaseAbortedHarness: async () => {
|
|
616
633
|
const runtime = this.activeHarness;
|
|
617
634
|
if (!runtime) return;
|
|
618
|
-
await runtime.
|
|
619
|
-
|
|
620
|
-
|
|
635
|
+
await runtime.conversation.waitForIdle(BACKGROUND_CONTEXT);
|
|
636
|
+
// The execution owner closes after transport projections settle.
|
|
637
|
+
|
|
638
|
+
},
|
|
639
|
+
cancelQueued: async (id) => {
|
|
640
|
+
const runtime = this.activeHarness;
|
|
641
|
+
if (!runtime) return undefined;
|
|
642
|
+
const result = await runtime.harness.abortSubmission(Number(id) as import("@earendil-works/pi-durable").SubmissionId, BACKGROUND_CONTEXT, runtime.conversation.id);
|
|
643
|
+
return { kind: result === "already_placed" || result === "settled" ? "already_consumed" as const : "cancelled" as const };
|
|
621
644
|
},
|
|
622
|
-
cancelQueued: async (id) => this.activeHarness?.lane.cancelQueued(id, BACKGROUND_CONTEXT),
|
|
623
645
|
submit: (message, options) => this.submitMessage(message, options),
|
|
624
646
|
waitUntil: (promise) => this.ctx.waitUntil(promise),
|
|
625
647
|
});
|
|
@@ -764,17 +786,35 @@ export abstract class AgentRuntimeKernel<
|
|
|
764
786
|
submission = await this.admission.pin(
|
|
765
787
|
submission,
|
|
766
788
|
() => this.admissionPin(mode === "fresh", submissionId),
|
|
767
|
-
|
|
789
|
+
(key) => this.ensureRuntimeVersion(key),
|
|
768
790
|
);
|
|
769
791
|
const activeRuntime = this.requireRuntime();
|
|
792
|
+
// A fresh provider Session per Submission keeps one failing upstream route from pinning later turns.
|
|
793
|
+
const recovery = new ModelRecovery(submissionId);
|
|
770
794
|
const harnessInput = this.pi.harnessInputs(
|
|
771
795
|
activeRuntime.pi,
|
|
772
796
|
submission.assemblyDescriptor,
|
|
773
797
|
{
|
|
774
|
-
|
|
775
|
-
|
|
798
|
+
modelSessionId: () => recovery.sessionId(),
|
|
799
|
+
assertRequestAllowed: () => {
|
|
800
|
+
const current = this.db.submissions.find(submissionId);
|
|
801
|
+
if (current?.abortReason || current?.unattendedFailure) throw new Error(current.abortReason ?? current.unattendedFailure!);
|
|
802
|
+
},
|
|
803
|
+
onGeneration: (event) => this.telemetry.generation(submissionId, event),
|
|
804
|
+
onCompactionGeneration: (event) => this.runtimeTurnEvents.compactionGeneration(submissionId, event),
|
|
776
805
|
},
|
|
777
806
|
);
|
|
807
|
+
const prepareToolInput = (toolName: string, input: unknown): unknown => {
|
|
808
|
+
const prepare = harnessInput.candidates.find(({ tool }) =>
|
|
809
|
+
tool.name === toolName
|
|
810
|
+
)?.tool.prepareArguments;
|
|
811
|
+
if (!prepare) return input;
|
|
812
|
+
try {
|
|
813
|
+
return prepare(input);
|
|
814
|
+
} catch {
|
|
815
|
+
return input;
|
|
816
|
+
}
|
|
817
|
+
};
|
|
778
818
|
let runtime!: Awaited<ReturnType<typeof openSessionHarness>>;
|
|
779
819
|
let publishApprovalRequest = async (
|
|
780
820
|
_toolCallId: string,
|
|
@@ -785,6 +825,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
785
825
|
submissionId,
|
|
786
826
|
requestId: submission.requestId,
|
|
787
827
|
executionLevel: activeRuntime.snapshot.profile.executionLevel,
|
|
828
|
+
origin: submission.origin,
|
|
829
|
+
scheduledMaxExecutionLevel: submission.scheduledMaxExecutionLevel ?? undefined,
|
|
788
830
|
},
|
|
789
831
|
candidates: harnessInput.candidates,
|
|
790
832
|
telemetry: this.telemetry,
|
|
@@ -812,11 +854,14 @@ export abstract class AgentRuntimeKernel<
|
|
|
812
854
|
}
|
|
813
855
|
},
|
|
814
856
|
gateTool: harnessInput.platform.gateTool,
|
|
857
|
+
onUnattendedFailure: (reason) => {
|
|
858
|
+
this.db.submissions.recordUnattendedFailure(submissionId, reason);
|
|
859
|
+
},
|
|
860
|
+
unattendedFailure: () => this.db.submissions.find(submissionId)?.unattendedFailure,
|
|
815
861
|
});
|
|
816
862
|
runtime = await openSessionHarness({
|
|
817
|
-
|
|
863
|
+
storage: openCloudflareSession(this.ctx.storage),
|
|
818
864
|
models: harnessInput.models,
|
|
819
|
-
modelSessionId: submissionId,
|
|
820
865
|
model: harnessInput.model,
|
|
821
866
|
thinkingLevel: harnessInput.thinkingLevel,
|
|
822
867
|
systemPrompt: harnessInput.systemPrompt,
|
|
@@ -824,30 +869,29 @@ export abstract class AgentRuntimeKernel<
|
|
|
824
869
|
workspace: harnessInput.workspace,
|
|
825
870
|
authorize: gate.authorize.bind(gate),
|
|
826
871
|
resolveReplay: gate.resolveReplay.bind(gate),
|
|
872
|
+
resolveConfirmation: gate.resolveConfirmation.bind(gate),
|
|
827
873
|
}),
|
|
874
|
+
...harnessToolActivation(harnessInput.candidates),
|
|
828
875
|
modelToolResultExemptions: harnessInput.candidates
|
|
829
876
|
.filter(({ outputBudget }) => outputBudget?.kind === "structure")
|
|
830
877
|
.map(({ tool }) => tool.name),
|
|
831
878
|
streamOptions: { maxRetries: 0 },
|
|
832
879
|
compaction: harnessInput.compaction,
|
|
833
880
|
toolExecution: "parallel",
|
|
881
|
+
recovery,
|
|
882
|
+
beforeTool: (call, _api, context) => gate.beforeTool({ toolName: call.name, toolCallId: call.id, args: call.arguments }, context),
|
|
883
|
+
afterResponse: (message) => {
|
|
884
|
+
for (const part of message.content) if (part.type === "toolCall") part.arguments = prepareToolInput(part.name, part.arguments) as JsonObject;
|
|
885
|
+
},
|
|
834
886
|
}, BACKGROUND_CONTEXT);
|
|
835
887
|
this.activeHarness = runtime;
|
|
836
|
-
let stream: ReturnType<typeof bindHarnessStream
|
|
888
|
+
let stream: Awaited<ReturnType<typeof bindHarnessStream>> | undefined;
|
|
837
889
|
let deactivate = () => {};
|
|
890
|
+
let released = false;
|
|
838
891
|
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
892
|
|
|
846
|
-
if (submission.abortReason && runtime.openOperation) {
|
|
847
|
-
await runtime.
|
|
848
|
-
runtime.openOperation.operationId,
|
|
849
|
-
BACKGROUND_CONTEXT,
|
|
850
|
-
);
|
|
893
|
+
if ((submission.abortReason || submission.unattendedFailure) && runtime.openOperation) {
|
|
894
|
+
await runtime.abort(BACKGROUND_CONTEXT);
|
|
851
895
|
}
|
|
852
896
|
|
|
853
897
|
await this.associateHarnessEntries(runtime, submissionId);
|
|
@@ -858,21 +902,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
858
902
|
(id) => this.transcript.regenerationTarget(id),
|
|
859
903
|
BACKGROUND_CONTEXT,
|
|
860
904
|
);
|
|
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
905
|
if (submission.status === "pending") {
|
|
877
906
|
this.db.submissions.transition(submissionId, "running", ["pending"]);
|
|
878
907
|
this.telemetry.capture("turnStarted", {
|
|
@@ -886,70 +915,58 @@ export abstract class AgentRuntimeKernel<
|
|
|
886
915
|
steers: this.db.steers,
|
|
887
916
|
messageUi: this.db.messageUi,
|
|
888
917
|
transaction: (run) => this.db.transaction(run),
|
|
889
|
-
enqueue: async (
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
return queued.
|
|
918
|
+
enqueue: async (steer) => {
|
|
919
|
+
const message = JSON.parse(steer.canonicalJson) as PiCanonicalUserInput;
|
|
920
|
+
const queued = await runtime.conversation.submit({ type: "input", content: message.content, requestId: `steer:${steer.messageId}`, whenBusy: "steer" }, BACKGROUND_CONTEXT);
|
|
921
|
+
return queued.status(BACKGROUND_CONTEXT);
|
|
893
922
|
},
|
|
923
|
+
find: (messageId) => runtime.submissionFor(`steer:${messageId}`, BACKGROUND_CONTEXT),
|
|
894
924
|
});
|
|
895
925
|
const uiMessageBody = submission.queuedUiMessageJson;
|
|
896
926
|
let initialUserSidecarPersisted = (await this.transcript.storedMessages())
|
|
897
927
|
.some((entry) =>
|
|
898
928
|
entry.submissionId === submissionId && entry.message.role === "user"
|
|
899
929
|
);
|
|
900
|
-
runtime.harness.
|
|
901
|
-
const entry = event.entry;
|
|
930
|
+
const unsubscribeEntries = runtime.harness.subscribeCommits((publication) => {
|
|
902
931
|
this.db.transaction(() => {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (
|
|
906
|
-
|
|
932
|
+
for (const change of publication.changes) {
|
|
933
|
+
if (change.type === "submission") steerDelivery.settleRecord(change.value);
|
|
934
|
+
if (change.type !== "entry" || change.value.conversationId !== runtime.conversation.id) continue;
|
|
935
|
+
const entry = change.value;
|
|
936
|
+
this.db.submissions.linkSessionEntry(String(entry.id), submissionId);
|
|
937
|
+
if (entry.kind === "pi.user" && !initialUserSidecarPersisted && uiMessageBody) {
|
|
938
|
+
this.db.messageUi.upsert(String(entry.id), uiMessageBody);
|
|
907
939
|
initialUserSidecarPersisted = true;
|
|
908
940
|
}
|
|
909
941
|
}
|
|
910
942
|
});
|
|
911
|
-
|
|
912
943
|
});
|
|
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, {
|
|
944
|
+
await this.runtimeTurnEvents.observe(runtime, submissionId, harnessInput.model);
|
|
945
|
+
stream = await bindHarnessStream(runtime, {
|
|
922
946
|
messageId: submission.assistantMessageId,
|
|
923
947
|
userMessageId: submission.userMessageId ?? undefined,
|
|
924
948
|
startedAt: submission.createdAt,
|
|
925
949
|
turnId: submissionId,
|
|
950
|
+
prepareToolInput,
|
|
926
951
|
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
|
-
}
|
|
952
|
+
for (const chunk of chunks) this.conversation.broadcastResponse({ id: submission.requestId, body: json(chunk), done: false, continuation });
|
|
935
953
|
},
|
|
936
954
|
});
|
|
955
|
+
deactivate = unsubscribeEntries;
|
|
956
|
+
|
|
937
957
|
publishApprovalRequest = stream.publishApprovalRequest;
|
|
938
|
-
this.conversation.activate(runtime
|
|
958
|
+
this.conversation.activate(runtime, submission);
|
|
939
959
|
|
|
940
960
|
const control = {
|
|
941
961
|
abort: async () => {
|
|
942
962
|
if (runtime.openOperation) {
|
|
943
|
-
await runtime.
|
|
944
|
-
runtime.openOperation.operationId,
|
|
945
|
-
BACKGROUND_CONTEXT,
|
|
946
|
-
);
|
|
963
|
+
await runtime.abort(BACKGROUND_CONTEXT);
|
|
947
964
|
}
|
|
948
965
|
},
|
|
949
966
|
interrupt: () => void runtime.close(BACKGROUND_CONTEXT),
|
|
950
967
|
steer: async (_message: PiCanonicalUserInput, messageId: string) => {
|
|
951
968
|
const steer = this.db.steers.findByMessageId(messageId);
|
|
952
|
-
if (steer) await steerDelivery.queue(steer);
|
|
969
|
+
if (steer && runtime.openOperation) await steerDelivery.queue(steer);
|
|
953
970
|
},
|
|
954
971
|
};
|
|
955
972
|
const turn: ActiveTurn = {
|
|
@@ -960,36 +977,28 @@ export abstract class AgentRuntimeKernel<
|
|
|
960
977
|
continuation,
|
|
961
978
|
agent: control,
|
|
962
979
|
};
|
|
963
|
-
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
980
|
+
const deactivateTurn = this.submissions.activate(submission, turn);
|
|
981
|
+
const unsubscribeEntriesAndTurn = deactivate;
|
|
982
|
+
deactivate = () => { unsubscribeEntriesAndTurn(); deactivateTurn(); };
|
|
983
|
+
await runtime.admit(preparation, BACKGROUND_CONTEXT);
|
|
984
|
+
await steerDelivery.reconcile(submissionId);
|
|
985
|
+
const outcome = await runtime.run(undefined, BACKGROUND_CONTEXT);
|
|
967
986
|
this.db.submissions.clearQueuedPayload(submissionId);
|
|
968
987
|
if (outcome.kind === "released") {
|
|
969
|
-
if (
|
|
970
|
-
outcome.reason === "slice" ||
|
|
971
|
-
outcome.reason === "budget" ||
|
|
972
|
-
outcome.reason === "tool-failure"
|
|
973
|
-
) {
|
|
988
|
+
if (outcome.reason === "slice") {
|
|
974
989
|
await this.schedule(0, "_piHarnessContinuation", { submissionId }, {
|
|
975
990
|
idempotent: true,
|
|
976
991
|
});
|
|
977
992
|
}
|
|
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
|
-
);
|
|
993
|
+
released = true;
|
|
989
994
|
return this.db.submissions.find(submissionId)!;
|
|
990
995
|
}
|
|
991
996
|
const record = outcome.outcome;
|
|
992
|
-
const
|
|
997
|
+
const unattendedFailure = this.db.submissions.find(submissionId)
|
|
998
|
+
?.unattendedFailure;
|
|
999
|
+
const status = unattendedFailure
|
|
1000
|
+
? "failed"
|
|
1001
|
+
: record.status === "completed" || record.status === "declined"
|
|
993
1002
|
? "succeeded"
|
|
994
1003
|
: record.status === "aborted"
|
|
995
1004
|
? "aborted"
|
|
@@ -998,7 +1007,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
998
1007
|
const terminal = await this.submissions.finish(
|
|
999
1008
|
submission,
|
|
1000
1009
|
status,
|
|
1001
|
-
record.error?.message,
|
|
1010
|
+
unattendedFailure ?? record.error?.message,
|
|
1002
1011
|
);
|
|
1003
1012
|
const projectedOutcome = projectSubmissionOutcome(record, terminal);
|
|
1004
1013
|
await stream.flushTerminal(projectedOutcome);
|
|
@@ -1015,13 +1024,21 @@ export abstract class AgentRuntimeKernel<
|
|
|
1015
1024
|
}));
|
|
1016
1025
|
throw error;
|
|
1017
1026
|
} finally {
|
|
1018
|
-
await this.conversation.release(runtime
|
|
1027
|
+
await this.conversation.release(runtime).catch((error) => {
|
|
1019
1028
|
console.warn("[runtime-stream:degraded]", errorText(error));
|
|
1020
1029
|
});
|
|
1021
|
-
stream?.unsubscribe();
|
|
1030
|
+
await stream?.unsubscribe();
|
|
1022
1031
|
deactivate();
|
|
1023
1032
|
if (this.activeHarness === runtime) this.activeHarness = undefined;
|
|
1024
1033
|
await runtime.close(BACKGROUND_CONTEXT).catch(() => undefined);
|
|
1034
|
+
if (released) {
|
|
1035
|
+
// Pi closes the Harness on release and drops undelivered stream frames; the
|
|
1036
|
+
// canonical transcript gives a paused question or approval its complete input.
|
|
1037
|
+
this.broadcast(json({
|
|
1038
|
+
type: MessageType.CF_AGENT_CHAT_MESSAGES,
|
|
1039
|
+
messages: await this.getMessages(),
|
|
1040
|
+
}));
|
|
1041
|
+
}
|
|
1025
1042
|
}
|
|
1026
1043
|
}
|
|
1027
1044
|
|
|
@@ -1055,7 +1072,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1055
1072
|
/** Start the native resume handshake from Pi's current lane snapshot. */
|
|
1056
1073
|
async onConnect(connection: Connection): Promise<void> {
|
|
1057
1074
|
this.conversation.disconnect(connection);
|
|
1058
|
-
await this.conversation.
|
|
1075
|
+
await this.conversation.connect(connection);
|
|
1059
1076
|
}
|
|
1060
1077
|
|
|
1061
1078
|
async onClose(connection: Connection, code: number, reason: string, wasClean: boolean): Promise<void> {
|
|
@@ -1152,10 +1169,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
1152
1169
|
|
|
1153
1170
|
if (event.type === "clear") {
|
|
1154
1171
|
if (this.submissions.isBusy()) return;
|
|
1155
|
-
this.
|
|
1156
|
-
|
|
1157
|
-
this.db.clearAll();
|
|
1158
|
-
});
|
|
1172
|
+
await this.transcript.clear();
|
|
1173
|
+
this.db.transaction(() => this.db.clearAll());
|
|
1159
1174
|
await this.broadcastApprovals();
|
|
1160
1175
|
this.broadcast(json({ type: MessageType.CF_AGENT_CHAT_CLEAR }));
|
|
1161
1176
|
return;
|
|
@@ -1297,7 +1312,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1297
1312
|
await this.broadcastApprovals();
|
|
1298
1313
|
return;
|
|
1299
1314
|
}
|
|
1300
|
-
this.transcript.append(crypto.randomUUID(), message);
|
|
1315
|
+
await this.transcript.append(crypto.randomUUID(), message);
|
|
1301
1316
|
}
|
|
1302
1317
|
|
|
1303
1318
|
async _hostGetSessionInfo(): Promise<{ messageCount: number }> {
|
|
@@ -1320,7 +1335,36 @@ export abstract class AgentRuntimeKernel<
|
|
|
1320
1335
|
async addCanonicalSnapshot(
|
|
1321
1336
|
snapshot: PiCanonicalTranscriptSnapshot,
|
|
1322
1337
|
): Promise<void> {
|
|
1323
|
-
this.transcript.importSnapshot(snapshot);
|
|
1338
|
+
await this.transcript.importSnapshot(snapshot);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* 本 Session 是否有一条未结束的定时 Submission。
|
|
1343
|
+
*
|
|
1344
|
+
* 定时 Run 未结束时独占输入:普通消息在准入前被拒绝,不排队等它结束。
|
|
1345
|
+
* 定时注入本身走 `submitScheduledPrompt`,不经过这道闸。
|
|
1346
|
+
*/
|
|
1347
|
+
private get scheduledInputLocked(): boolean {
|
|
1348
|
+
return this.db.submissions.hasUnfinishedScheduled();
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
private rejectedOrdinaryInputReceipt(): SubmissionReceipt {
|
|
1352
|
+
return {
|
|
1353
|
+
submissionId: crypto.randomUUID(),
|
|
1354
|
+
status: "skipped",
|
|
1355
|
+
accepted: false,
|
|
1356
|
+
error: SCHEDULED_INPUT_LOCKED_MESSAGE,
|
|
1357
|
+
retryable: false,
|
|
1358
|
+
createdAt: Date.now(),
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
private rejectInputLocked(): MessageDispatchReceipt {
|
|
1363
|
+
return {
|
|
1364
|
+
kind: "rejected",
|
|
1365
|
+
code: "input_locked",
|
|
1366
|
+
message: SCHEDULED_INPUT_LOCKED_MESSAGE,
|
|
1367
|
+
};
|
|
1324
1368
|
}
|
|
1325
1369
|
|
|
1326
1370
|
/** Admit text and return immediately; completion continues in the Durable Object. */
|
|
@@ -1328,6 +1372,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1328
1372
|
text: string,
|
|
1329
1373
|
options?: { idempotencyKey?: string },
|
|
1330
1374
|
): Promise<SubmissionReceipt> {
|
|
1375
|
+
if (this.scheduledInputLocked) return this.rejectedOrdinaryInputReceipt();
|
|
1331
1376
|
const submitted = await this.submitMessage({
|
|
1332
1377
|
role: "user",
|
|
1333
1378
|
content: [{ type: "text", text }],
|
|
@@ -1357,17 +1402,24 @@ export abstract class AgentRuntimeKernel<
|
|
|
1357
1402
|
}
|
|
1358
1403
|
|
|
1359
1404
|
async dispatchMessage(message: UIMessage, delivery: MessageDelivery): Promise<MessageDispatchReceipt> {
|
|
1405
|
+
if (this.scheduledInputLocked) return this.rejectInputLocked();
|
|
1360
1406
|
return this.delivery.dispatch(message, delivery);
|
|
1361
1407
|
}
|
|
1362
1408
|
|
|
1363
1409
|
async steerQueuedSubmission(submissionId: string): Promise<MessageDispatchReceipt> {
|
|
1410
|
+
if (this.scheduledInputLocked) return this.rejectInputLocked();
|
|
1364
1411
|
return this.delivery.steerQueued(submissionId);
|
|
1365
1412
|
}
|
|
1366
1413
|
|
|
1367
1414
|
/** Admit scheduled work after a bounded stable wait, otherwise return skipped. */
|
|
1368
1415
|
async submitScheduledPrompt(
|
|
1369
1416
|
prompt: string,
|
|
1370
|
-
options?: {
|
|
1417
|
+
options?: {
|
|
1418
|
+
idempotencyKey?: string;
|
|
1419
|
+
scheduledMaxExecutionLevel?: ExecutionLevel;
|
|
1420
|
+
/** @deprecated Retained for rolling upgrades; ignored by Runtime. */
|
|
1421
|
+
unattendedExecutionGrant?: RuntimeUnattendedExecutionGrant;
|
|
1422
|
+
},
|
|
1371
1423
|
): Promise<SubmissionReceipt> {
|
|
1372
1424
|
const requestId = crypto.randomUUID();
|
|
1373
1425
|
const submitted = await this.submitMessageWhenStable(
|
|
@@ -1379,6 +1431,8 @@ export abstract class AgentRuntimeKernel<
|
|
|
1379
1431
|
{
|
|
1380
1432
|
requestId,
|
|
1381
1433
|
idempotencyKey: options?.idempotencyKey,
|
|
1434
|
+
origin: "scheduled",
|
|
1435
|
+
scheduledMaxExecutionLevel: options?.scheduledMaxExecutionLevel,
|
|
1382
1436
|
},
|
|
1383
1437
|
SCHEDULED_STABLE_TIMEOUT_MS,
|
|
1384
1438
|
);
|
|
@@ -1463,7 +1517,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1463
1517
|
summary: approval.summary,
|
|
1464
1518
|
executionLevel: approval.executionLevel,
|
|
1465
1519
|
requiredExecutionLevel: approval.requiredExecutionLevel,
|
|
1466
|
-
inputJson: approval.inputJson,
|
|
1520
|
+
inputJson: approval.confirmationJson ?? approval.inputJson,
|
|
1467
1521
|
requestId: approval.requestId,
|
|
1468
1522
|
};
|
|
1469
1523
|
}
|
|
@@ -1573,6 +1627,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
1573
1627
|
hasPendingInteraction: (id) => this.humanDecisions.isWaitingForInteraction(id),
|
|
1574
1628
|
hasPendingSteer: (id) => this.db.steers.listForSubmission(id).length > 0,
|
|
1575
1629
|
hasRunningAgentTools: this.db.agentTools.hasRunning(),
|
|
1630
|
+
hasUnfinishedScheduled: this.db.submissions.hasUnfinishedScheduled(),
|
|
1576
1631
|
previousState: this.state,
|
|
1577
1632
|
});
|
|
1578
1633
|
if (changed) this.setState(state);
|