@standardagents/builder 0.25.5 → 0.25.6

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/dist/runtime.d.ts CHANGED
@@ -1098,6 +1098,14 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1098
1098
  private watchdogCheck;
1099
1099
  /** Mark logs the dead executor abandoned, and tell connected clients. */
1100
1100
  private finalizeLostExecutionLogs;
1101
+ /**
1102
+ * A subagent whose execution the watchdog gave up on leaves its parent
1103
+ * waiting forever for a completion message. Deliver the failure the same way
1104
+ * a sessionFail would, so the parent's turn wakes and can route around it.
1105
+ * Explicit-communication children (e.g. the compaction agent) stay quiet by
1106
+ * contract — only the registry status updates for those.
1107
+ */
1108
+ private notifyParentOfLostExecution;
1101
1109
  /** At most N auto-resumes per rolling window, tracked durably. */
1102
1110
  private consumeWatchdogResumeBudget;
1103
1111
  private normalizePlainRecord;
package/dist/runtime.js CHANGED
@@ -2385,8 +2385,11 @@ var init_LLMRequest = __esm({
2385
2385
  */
2386
2386
  static async logError(state, error, errorType, modelId, startTime, existingLogId) {
2387
2387
  try {
2388
- const rawErrorMessage = error instanceof Error ? error.message : String(error);
2388
+ let rawErrorMessage = error instanceof Error ? error.message : String(error);
2389
2389
  const errorStack = error instanceof Error ? error.stack : void 0;
2390
+ if (error instanceof Error && error.name === "AbortError" && /operation was aborted/i.test(rawErrorMessage)) {
2391
+ rawErrorMessage = state.abortController?.signal?.aborted ? "Aborted: the execution this request belonged to was cancelled (user stop, watchdog takeover, or a restart) \u2014 retries and fallbacks of a cancelled execution fail immediately." : "Aborted: the request's connection was cancelled mid-flight (provider disconnect or executor restart).";
2392
+ }
2390
2393
  let errorMessage = rawErrorMessage;
2391
2394
  let parsedProviderBody = null;
2392
2395
  const trimmedRaw = rawErrorMessage.trim();
@@ -15038,6 +15041,38 @@ var AlarmQueue = class {
15038
15041
  async ensureAlarmScheduled() {
15039
15042
  await this.rescheduleAlarm();
15040
15043
  }
15044
+ /**
15045
+ * Recover queue items orphaned in 'processing' by a dead predecessor
15046
+ * instance.
15047
+ *
15048
+ * processNext marks an item 'processing' before executing it. If the DO is
15049
+ * evicted or a deploy restarts it mid-execution, that row used to stay
15050
+ * 'processing' FOREVER — only 'pending' rows are ever picked up again — so
15051
+ * queued subagent executions, queued messages, and scheduled effects that
15052
+ * were mid-flight at a restart were silently lost (a subagent that "just
15053
+ * died half way through" with nothing to revive it).
15054
+ *
15055
+ * MUST ONLY be called from the once-per-instance wake path (DurableThread's
15056
+ * alarmQueueRearmed guard): at that moment this fresh instance is executing
15057
+ * nothing, so ANY 'processing' row provably belongs to a dead predecessor.
15058
+ * Calling it at any other time would re-queue items that are legitimately
15059
+ * mid-execution on this very instance.
15060
+ */
15061
+ async recoverOrphanedProcessing(maxAttempts = 3) {
15062
+ const now = Date.now() * 1e3;
15063
+ await this.sql.exec(
15064
+ `UPDATE alarm_queue
15065
+ SET status = 'failed',
15066
+ error = 'Lost mid-execution (the executor restarted) and retry attempts were exhausted.',
15067
+ completed_at = ?
15068
+ WHERE status = 'processing' AND attempts >= ?`,
15069
+ now,
15070
+ maxAttempts
15071
+ );
15072
+ await this.sql.exec(
15073
+ `UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
15074
+ );
15075
+ }
15041
15076
  // ============================================================
15042
15077
  // Effect-specific Methods
15043
15078
  // ============================================================
@@ -16960,6 +16995,9 @@ var DurableThread = class _DurableThread extends DurableObject {
16960
16995
  }
16961
16996
  this.migratedToVersion = LATEST_SCHEMA_VERSION;
16962
16997
  if (!this.alarmQueueRearmed && typeof this.alarmQueue.ensureAlarmScheduled === "function") {
16998
+ if (typeof this.alarmQueue.recoverOrphanedProcessing === "function") {
16999
+ await this.alarmQueue.recoverOrphanedProcessing();
17000
+ }
16963
17001
  await this.alarmQueue.ensureAlarmScheduled();
16964
17002
  this.alarmQueueRearmed = true;
16965
17003
  }
@@ -19665,14 +19703,14 @@ ${resultContent}${attachmentSummary}`;
19665
19703
  `SELECT id FROM logs WHERE is_complete = 0`
19666
19704
  )).toArray();
19667
19705
  let incomplete = await readIncomplete();
19706
+ const ceiling = resolveWatchdogStaleCeilingMs(this.env);
19707
+ const silenceMs = this.lastProgressAt === 0 ? Number.POSITIVE_INFINITY : Date.now() - this.lastProgressAt;
19668
19708
  if (this.isExecuting) {
19669
19709
  if (incomplete.length === 0) {
19670
19710
  this.watchdogWedgeStrikes = 0;
19671
19711
  await rearm();
19672
19712
  return;
19673
19713
  }
19674
- const ceiling = resolveWatchdogStaleCeilingMs(this.env);
19675
- const silenceMs = Date.now() - this.lastProgressAt;
19676
19714
  if (silenceMs <= ceiling) {
19677
19715
  this.watchdogWedgeStrikes = 0;
19678
19716
  await rearm();
@@ -19681,20 +19719,23 @@ ${resultContent}${attachmentSummary}`;
19681
19719
  this.watchdogWedgeStrikes++;
19682
19720
  if (this.watchdogWedgeStrikes < 2) {
19683
19721
  console.warn(
19684
- `[DurableThread] Watchdog: execution silent for ${Math.round(silenceMs / 1e3)}s (ceiling ${Math.round(ceiling / 1e3)}s) \u2014 aborting in-flight request`
19722
+ `[DurableThread] Watchdog: execution silent for ${Math.round(silenceMs / 1e3)}s (ceiling ${Math.round(ceiling / 1e3)}s) \u2014 observing; takeover next cycle if still silent`
19685
19723
  );
19686
- try {
19687
- this.currentAbortController?.abort();
19688
- } catch {
19689
- }
19690
19724
  await rearm();
19691
19725
  return;
19692
19726
  }
19693
19727
  console.error(
19694
- "[DurableThread] Watchdog: wedged execution did not unwind after abort \u2014 forcing takeover"
19728
+ `[DurableThread] Watchdog: wedged execution (${Math.round(silenceMs / 1e3)}s silent across two cycles) \u2014 forcing takeover`
19695
19729
  );
19730
+ try {
19731
+ this.currentAbortController?.abort();
19732
+ } catch {
19733
+ }
19696
19734
  this.isExecuting = false;
19697
19735
  this.currentAbortController = null;
19736
+ } else if (incomplete.length > 0 && silenceMs <= ceiling) {
19737
+ await rearm();
19738
+ return;
19698
19739
  }
19699
19740
  incomplete = await readIncomplete();
19700
19741
  if (this.isExecuting) {
@@ -19722,6 +19763,7 @@ ${resultContent}${attachmentSummary}`;
19722
19763
  console.error(
19723
19764
  `[DurableThread] Watchdog: resume budget exhausted for thread ${threadId} \u2014 leaving thread stopped`
19724
19765
  );
19766
+ await this.notifyParentOfLostExecution(threadId);
19725
19767
  }
19726
19768
  await this.disarmExecutionWatchdog();
19727
19769
  } catch (error) {
@@ -19749,6 +19791,46 @@ ${resultContent}${attachmentSummary}`;
19749
19791
  });
19750
19792
  }
19751
19793
  }
19794
+ /**
19795
+ * A subagent whose execution the watchdog gave up on leaves its parent
19796
+ * waiting forever for a completion message. Deliver the failure the same way
19797
+ * a sessionFail would, so the parent's turn wakes and can route around it.
19798
+ * Explicit-communication children (e.g. the compaction agent) stay quiet by
19799
+ * contract — only the registry status updates for those.
19800
+ */
19801
+ async notifyParentOfLostExecution(threadId) {
19802
+ try {
19803
+ const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
19804
+ const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
19805
+ const meta = await agentBuilder.getThread(threadId);
19806
+ const parentThreadId = meta?.parent ?? null;
19807
+ if (!parentThreadId) return;
19808
+ const parentId = this.env.AGENT_BUILDER_THREAD.idFromName(parentThreadId);
19809
+ const parentStub = this.env.AGENT_BUILDER_THREAD.get(parentId);
19810
+ const parentCommunication = await this.getParentCommunicationModeForChild(
19811
+ parentStub,
19812
+ parentThreadId,
19813
+ threadId
19814
+ );
19815
+ if (parentCommunication !== "explicit") {
19816
+ await parentStub.queueMessage(parentThreadId, {
19817
+ role: "user",
19818
+ content: `Subagent (reference: ${threadId}) has reported a failure:
19819
+
19820
+ Its execution was lost (the executor restarted or hung) and automatic recovery was exhausted. Treat this subagent's task as failed \u2014 do not wait for it; retry the work another way or report the failure.`,
19821
+ attachments: [],
19822
+ silent: true,
19823
+ metadata: { subagent_id: threadId }
19824
+ });
19825
+ }
19826
+ await parentStub.updateChildRegistryStatus(parentThreadId, threadId, "idle");
19827
+ } catch (error) {
19828
+ console.error(
19829
+ "[DurableThread] Watchdog: failed to notify parent of lost execution (non-fatal):",
19830
+ error
19831
+ );
19832
+ }
19833
+ }
19752
19834
  /** At most N auto-resumes per rolling window, tracked durably. */
19753
19835
  async consumeWatchdogResumeBudget() {
19754
19836
  const now = Date.now();