@springbrand/agent-runtime 0.3.0-alpha.20 → 0.3.0-alpha.22

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/agent-runtime",
3
- "version": "0.3.0-alpha.20",
3
+ "version": "0.3.0-alpha.22",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -17,10 +17,10 @@
17
17
  "./models": "./src/lib/model-catalog.ts"
18
18
  },
19
19
  "dependencies": {
20
- "@cloudflare/codemode": "0.5.2",
20
+ "@cloudflare/codemode": "0.5.4",
21
21
  "@cloudflare/sandbox": "0.12.4",
22
22
  "@cloudflare/shell": "0.4.3",
23
- "@cloudflare/think": "0.18.0",
23
+ "@cloudflare/think": "0.20.2",
24
24
  "@earendil-works/pi-agent-core": "1.1.0",
25
25
  "@earendil-works/pi-ai": "1.1.0",
26
26
  "@types/lodash-es": "^4.17.12",
@@ -32,11 +32,11 @@
32
32
  "@earendil-works/chord": "1.1.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "agents": "^0.23.0",
35
+ "agents": ">=0.28.1 <0.29.0",
36
36
  "ai": "^7.0.0"
37
37
  },
38
38
  "devDependencies": {
39
- "agents": "0.23.0",
39
+ "agents": "0.28.1",
40
40
  "ai": "7.0.56"
41
41
  }
42
42
  }
@@ -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,8 @@ import {
33
34
  type SubmissionReceipt,
34
35
  type SubmissionExecutionMode,
35
36
  type SubmissionHandle,
37
+ type StoredRuntimeKey,
38
+ type RuntimeKeyRewrite,
36
39
  type RuntimeUnattendedExecutionGrant,
37
40
  } from "../../submission";
38
41
  import type { ExecutionLevel } from "../../lib/execution-level";
@@ -43,7 +46,6 @@ import {
43
46
  RuntimeLoadTracker,
44
47
  prepareRuntimeCandidate,
45
48
  projectRuntimeAssembly,
46
- type PreparedPiRuntime,
47
49
  type RuntimeAssemblyInput,
48
50
  type RuntimeAssemblyView,
49
51
  type RuntimeCandidate,
@@ -88,7 +90,10 @@ import {
88
90
  type TemporaryAgentRequest,
89
91
  type TemporaryAgentRunContext,
90
92
  } from "../../tools";
91
- import { sha256Hex } from "../../lib/artifacts";
93
+ import {
94
+ ASK_USER_TOOL_NAME,
95
+ askUserPiToolCandidate,
96
+ } from "../../tools/assistant/ask-user";
92
97
  import { RuntimeDatabase } from "./database";
93
98
 
94
99
  /**
@@ -156,17 +161,22 @@ export abstract class AgentRuntimeKernel<
156
161
  };
157
162
 
158
163
  protected abstract ensureRuntimeReady(): Promise<void>;
159
- /** Prepare per-Submission Host state before the first assembly pin. */
164
+ /** Prepare per-Submission Host state before first execution. */
160
165
  protected prepareRuntimeForSubmission(_submissionId: string): Promise<void> {
161
166
  return this.ensureRuntimeReady();
162
167
  }
163
168
 
164
169
  /** Runs before a new Submission records its Runtime key. */
165
- protected prepareRuntimeForAdmission(): Promise<void> {
166
- return this.ensureRuntimeReady();
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 };
167
177
  }
168
178
 
169
- protected ensureRuntimeVersion(_runtimeKey: string): Promise<void> {
179
+ protected ensureRuntimeVersion(_runtimeKey: string, _force = false): Promise<void> {
170
180
  return this.ensureRuntimeReady();
171
181
  }
172
182
  protected get submissionContext() {
@@ -176,6 +186,37 @@ export abstract class AgentRuntimeKernel<
176
186
  };
177
187
  }
178
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
+
179
220
  /** Let the Host prepare a validated client response before Tool settlement. */
180
221
  protected prepareToolInteractionResponse(
181
222
  input: ToolInteractionResponsePreparation,
@@ -318,7 +359,6 @@ export abstract class AgentRuntimeKernel<
318
359
  createExtensionHostBinding: (permissions, labels) =>
319
360
  this.createExtensionHostBinding(permissions, labels),
320
361
  advance: (phase) => this.runtimeLoad.advance(phase),
321
- isExecuting: () => Boolean(this.submissions.currentActive()),
322
362
  commit: (runtime) => {
323
363
  this.telemetry.configure(candidate.telemetry);
324
364
  this.activeRuntime = runtime;
@@ -516,57 +556,7 @@ export abstract class AgentRuntimeKernel<
516
556
 
517
557
  // #endregion
518
558
 
519
- // #region Submission 准入与配置固定
520
-
521
- /** Pin dynamic Context and execution policy before the first assembly is used. */
522
- private async admissionPin(fresh: boolean, submissionId: string): Promise<{
523
- revision: string;
524
- descriptor: string;
525
- }> {
526
- if (fresh) await this.prepareRuntimeForSubmission(submissionId);
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");
532
- }
533
-
534
- // initConfig can call this inside its serialized load; re-entering ensureRuntimeReady would deadlock.
535
- private async currentAssemblyPin(includeMemory = true): Promise<{
536
- revision: string;
537
- descriptor: string;
538
- }> {
539
- await this.runtimeTurnEvents.drain();
540
- const runtime = this.activeRuntime;
541
- if (!runtime) {
542
- throw new Error("Runtime revision is not initialized");
543
- }
544
- return this.pinAssembly(runtime.pi, runtime.revision, includeMemory);
545
- }
546
-
547
- private async pinAssembly(
548
- prepared: PreparedPiRuntime,
549
- baseRevision: string,
550
- includeMemory: boolean,
551
- ): Promise<{ revision: string; descriptor: string }> {
552
- const pinned = await this.pi.pin({
553
- prepared,
554
- baseRevision,
555
- includeMemory,
556
- readExtensionContext: ({ label }) =>
557
- this._hostGetContext(label),
558
- });
559
- if (pinned.degradations.length > 0) {
560
- console.warn(
561
- "[runtime-context:degraded]",
562
- json({ degradations: pinned.degradations }),
563
- );
564
- }
565
- return {
566
- revision: await sha256Hex(pinned.descriptor),
567
- descriptor: pinned.descriptor,
568
- };
569
- }
559
+ // #region Submission 准入与当前配置
570
560
 
571
561
  private get admission(): SubmissionAdmission {
572
562
  return this.submissionAdmission ??= new SubmissionAdmission({
@@ -574,10 +564,7 @@ export abstract class AgentRuntimeKernel<
574
564
  steers: this.db.steers,
575
565
  submissions: this.db.submissions,
576
566
  transaction: (run) => this.db.transaction(run),
577
- prepareRuntime: async () => {
578
- await this.prepareRuntimeForAdmission();
579
- return { runtimeKey: this.activeRuntime!.runtimeKey, admission: this.activeRuntime?.admission };
580
- },
567
+ prepareRuntime: () => this.prepareRuntimeForAdmission(),
581
568
  findUserMessage: (id) => this.transcript.findUserMessage(id),
582
569
  onAdmitted: (submission, hostAccepted) =>
583
570
  this.runtimeTurnEvents.submissionAdmitted(submission, hostAccepted),
@@ -783,27 +770,32 @@ export abstract class AgentRuntimeKernel<
783
770
  ): Promise<StoredSubmission> {
784
771
  const continuation = mode !== "fresh";
785
772
  const submissionId = submission.submissionId;
786
- submission = await this.admission.pin(
773
+ submission = await this.admission.prepareCurrentRuntime(
787
774
  submission,
788
- () => this.admissionPin(mode === "fresh", submissionId),
789
- (key) => this.ensureRuntimeVersion(key),
775
+ (key) => this.ensureRuntimeVersion(key, true),
790
776
  );
777
+ if (!continuation) await this.prepareRuntimeForSubmission(submissionId);
778
+ await this.runtimeTurnEvents.drain();
791
779
  const activeRuntime = this.requireRuntime();
792
780
  // A fresh provider Session per Submission keeps one failing upstream route from pinning later turns.
793
781
  const recovery = new ModelRecovery(submissionId);
794
- const harnessInput = this.pi.harnessInputs(
782
+ const harnessInput = await this.pi.harnessInputs(
795
783
  activeRuntime.pi,
796
- submission.assemblyDescriptor,
797
784
  {
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),
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
+ },
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),
805
794
  },
806
795
  );
796
+ if (harnessInput.degradations.length > 0) {
797
+ console.warn("[runtime-context:degraded]", json({ degradations: harnessInput.degradations }));
798
+ }
807
799
  const prepareToolInput = (toolName: string, input: unknown): unknown => {
808
800
  const prepare = harnessInput.candidates.find(({ tool }) =>
809
801
  tool.name === toolName
@@ -906,7 +898,6 @@ export abstract class AgentRuntimeKernel<
906
898
  this.db.submissions.transition(submissionId, "running", ["pending"]);
907
899
  this.telemetry.capture("turnStarted", {
908
900
  submissionId,
909
- assemblyRevision: submission.assemblyRevision,
910
901
  });
911
902
  submission = this.db.submissions.find(submissionId)!;
912
903
  }
@@ -1533,11 +1524,19 @@ export abstract class AgentRuntimeKernel<
1533
1524
  if (!submission || isTerminalSubmissionStatus(submission.status)) {
1534
1525
  return { ok: false };
1535
1526
  }
1536
- await this.ensureRuntimeReady();
1537
- const spec = this.pi.harnessInputs(
1538
- this.requireRuntime().pi,
1539
- submission.assemblyDescriptor,
1540
- ).candidates.find(({ tool }) => tool.name === pending.toolName)?.interaction;
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);
1541
1540
  if (!spec) return { ok: false };
1542
1541
 
1543
1542
  const accepted = await this.humanDecisions.respond(