@springbrand/agent-runtime 0.3.0-alpha.27 → 0.3.0-alpha.29

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.27",
3
+ "version": "0.3.0-alpha.29",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -766,8 +766,56 @@ export abstract class AgentRuntimeKernel<
766
766
  }
767
767
  }
768
768
 
769
+ /**
770
+ * Run or recover the Submission while an alarm heartbeat keeps the DO resident.
771
+ *
772
+ * A Turn makes only outbound model and Tool calls, which do not count as DO
773
+ * activity. Once every client disconnects, an unheld DO is evicted after
774
+ * 70-140s, taking the in-memory Turn and its stall watchdog with it while the
775
+ * row stays `running`. If the DO is still evicted (deploy, runtime restart),
776
+ * the pending heartbeat alarm wakes it and `onStart` recovers the Submission.
777
+ * A Turn parked for approval or input returns `released`, which drops the hold.
778
+ */
779
+ private executeHarnessSubmission(
780
+ submission: StoredSubmission,
781
+ mode: SubmissionExecutionMode,
782
+ ): Promise<StoredSubmission> {
783
+ return this.holdResidentWhile(() => this.runHarnessSubmission(submission, mode));
784
+ }
785
+
786
+ /**
787
+ * `keepAliveWhile` whose release finishes before `run`'s result settles.
788
+ *
789
+ * The SDK disposer hands the alarm re-sync to `ctx.waitUntil`. The Turn's last
790
+ * await can be resumed by another Durable Object in the same isolate, and that
791
+ * registration then fails as cross-DO I/O (`RefcountedFulfiller`) after the Turn
792
+ * has already finished. Awaiting the re-sync inline needs no waitUntil. A facet
793
+ * borrows its root's heartbeat over RPC, so it keeps the SDK path.
794
+ * Relies on agents 0.28 internals; the peer range pins that minor.
795
+ */
796
+ private async holdResidentWhile<T>(run: () => Promise<T>): Promise<T> {
797
+ const host = this as unknown as {
798
+ _isFacet: boolean;
799
+ _keepAliveRefs: number;
800
+ _syncHostJobs(): Promise<void>;
801
+ };
802
+ if (host._isFacet) return this.keepAliveWhile(run);
803
+ host._keepAliveRefs += 1;
804
+ if (host._keepAliveRefs === 1) await host._syncHostJobs();
805
+ try {
806
+ return await run();
807
+ } finally {
808
+ host._keepAliveRefs = Math.max(0, host._keepAliveRefs - 1);
809
+ if (host._keepAliveRefs === 0) {
810
+ await host._syncHostJobs().catch((error: unknown) => {
811
+ console.error("[keep-alive] heartbeat release failed", error);
812
+ });
813
+ }
814
+ }
815
+ }
816
+
769
817
  /** Run or recover the Submission through Pi's single durable AgentHarness. */
770
- private async executeHarnessSubmission(
818
+ private async runHarnessSubmission(
771
819
  submission: StoredSubmission,
772
820
  mode: SubmissionExecutionMode,
773
821
  ): Promise<StoredSubmission> {