@standardagents/builder 0.25.5 → 0.25.7

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();
@@ -7804,6 +7807,25 @@ This usually points to something I can't fix from here \u2014 for example the wo
7804
7807
  return null;
7805
7808
  }
7806
7809
  }
7810
+ /**
7811
+ * Registry entries the model may see (and therefore manage). Hidden
7812
+ * subagent relationships (`SubagentToolConfig.hidden` — infrastructure
7813
+ * children like background compaction) are excluded from the injected
7814
+ * "Active subagent instances" message: a listed instance reads as the
7815
+ * model's to clean up, and terminate_subagent on an infrastructure child
7816
+ * kills work the runtime depends on. Static and pure for direct testing.
7817
+ */
7818
+ static filterModelVisibleSubagents(registry, promptTools) {
7819
+ if (registry.length === 0) return registry;
7820
+ const hidden = /* @__PURE__ */ new Set();
7821
+ for (const config of promptTools ?? []) {
7822
+ if (typeof config === "object" && config !== null && "name" in config && config.hidden === true) {
7823
+ hidden.add(config.name);
7824
+ }
7825
+ }
7826
+ if (hidden.size === 0) return registry;
7827
+ return registry.filter((entry) => !hidden.has(entry.name));
7828
+ }
7807
7829
  static async getSubagentRegistry(state) {
7808
7830
  if (typeof state.thread.instance.getChildrenRegistry === "function") {
7809
7831
  try {
@@ -8062,7 +8084,10 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
8062
8084
  });
8063
8085
  }
8064
8086
  }
8065
- const subagentRegistry = await this.getSubagentRegistry(state);
8087
+ const subagentRegistry = _FlowEngine.filterModelVisibleSubagents(
8088
+ await this.getSubagentRegistry(state),
8089
+ state.prompt?._tools
8090
+ );
8066
8091
  if (subagentRegistry.length > 0) {
8067
8092
  const fallbackNameMap = await this.resolveRegistryThreadNameMap(state, subagentRegistry);
8068
8093
  const lines = subagentRegistry.map((entry) => {
@@ -15038,6 +15063,38 @@ var AlarmQueue = class {
15038
15063
  async ensureAlarmScheduled() {
15039
15064
  await this.rescheduleAlarm();
15040
15065
  }
15066
+ /**
15067
+ * Recover queue items orphaned in 'processing' by a dead predecessor
15068
+ * instance.
15069
+ *
15070
+ * processNext marks an item 'processing' before executing it. If the DO is
15071
+ * evicted or a deploy restarts it mid-execution, that row used to stay
15072
+ * 'processing' FOREVER — only 'pending' rows are ever picked up again — so
15073
+ * queued subagent executions, queued messages, and scheduled effects that
15074
+ * were mid-flight at a restart were silently lost (a subagent that "just
15075
+ * died half way through" with nothing to revive it).
15076
+ *
15077
+ * MUST ONLY be called from the once-per-instance wake path (DurableThread's
15078
+ * alarmQueueRearmed guard): at that moment this fresh instance is executing
15079
+ * nothing, so ANY 'processing' row provably belongs to a dead predecessor.
15080
+ * Calling it at any other time would re-queue items that are legitimately
15081
+ * mid-execution on this very instance.
15082
+ */
15083
+ async recoverOrphanedProcessing(maxAttempts = 3) {
15084
+ const now = Date.now() * 1e3;
15085
+ await this.sql.exec(
15086
+ `UPDATE alarm_queue
15087
+ SET status = 'failed',
15088
+ error = 'Lost mid-execution (the executor restarted) and retry attempts were exhausted.',
15089
+ completed_at = ?
15090
+ WHERE status = 'processing' AND attempts >= ?`,
15091
+ now,
15092
+ maxAttempts
15093
+ );
15094
+ await this.sql.exec(
15095
+ `UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
15096
+ );
15097
+ }
15041
15098
  // ============================================================
15042
15099
  // Effect-specific Methods
15043
15100
  // ============================================================
@@ -16960,6 +17017,9 @@ var DurableThread = class _DurableThread extends DurableObject {
16960
17017
  }
16961
17018
  this.migratedToVersion = LATEST_SCHEMA_VERSION;
16962
17019
  if (!this.alarmQueueRearmed && typeof this.alarmQueue.ensureAlarmScheduled === "function") {
17020
+ if (typeof this.alarmQueue.recoverOrphanedProcessing === "function") {
17021
+ await this.alarmQueue.recoverOrphanedProcessing();
17022
+ }
16963
17023
  await this.alarmQueue.ensureAlarmScheduled();
16964
17024
  this.alarmQueueRearmed = true;
16965
17025
  }
@@ -19665,14 +19725,14 @@ ${resultContent}${attachmentSummary}`;
19665
19725
  `SELECT id FROM logs WHERE is_complete = 0`
19666
19726
  )).toArray();
19667
19727
  let incomplete = await readIncomplete();
19728
+ const ceiling = resolveWatchdogStaleCeilingMs(this.env);
19729
+ const silenceMs = this.lastProgressAt === 0 ? Number.POSITIVE_INFINITY : Date.now() - this.lastProgressAt;
19668
19730
  if (this.isExecuting) {
19669
19731
  if (incomplete.length === 0) {
19670
19732
  this.watchdogWedgeStrikes = 0;
19671
19733
  await rearm();
19672
19734
  return;
19673
19735
  }
19674
- const ceiling = resolveWatchdogStaleCeilingMs(this.env);
19675
- const silenceMs = Date.now() - this.lastProgressAt;
19676
19736
  if (silenceMs <= ceiling) {
19677
19737
  this.watchdogWedgeStrikes = 0;
19678
19738
  await rearm();
@@ -19681,20 +19741,23 @@ ${resultContent}${attachmentSummary}`;
19681
19741
  this.watchdogWedgeStrikes++;
19682
19742
  if (this.watchdogWedgeStrikes < 2) {
19683
19743
  console.warn(
19684
- `[DurableThread] Watchdog: execution silent for ${Math.round(silenceMs / 1e3)}s (ceiling ${Math.round(ceiling / 1e3)}s) \u2014 aborting in-flight request`
19744
+ `[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
19745
  );
19686
- try {
19687
- this.currentAbortController?.abort();
19688
- } catch {
19689
- }
19690
19746
  await rearm();
19691
19747
  return;
19692
19748
  }
19693
19749
  console.error(
19694
- "[DurableThread] Watchdog: wedged execution did not unwind after abort \u2014 forcing takeover"
19750
+ `[DurableThread] Watchdog: wedged execution (${Math.round(silenceMs / 1e3)}s silent across two cycles) \u2014 forcing takeover`
19695
19751
  );
19752
+ try {
19753
+ this.currentAbortController?.abort();
19754
+ } catch {
19755
+ }
19696
19756
  this.isExecuting = false;
19697
19757
  this.currentAbortController = null;
19758
+ } else if (incomplete.length > 0 && silenceMs <= ceiling) {
19759
+ await rearm();
19760
+ return;
19698
19761
  }
19699
19762
  incomplete = await readIncomplete();
19700
19763
  if (this.isExecuting) {
@@ -19722,6 +19785,7 @@ ${resultContent}${attachmentSummary}`;
19722
19785
  console.error(
19723
19786
  `[DurableThread] Watchdog: resume budget exhausted for thread ${threadId} \u2014 leaving thread stopped`
19724
19787
  );
19788
+ await this.notifyParentOfLostExecution(threadId);
19725
19789
  }
19726
19790
  await this.disarmExecutionWatchdog();
19727
19791
  } catch (error) {
@@ -19749,6 +19813,46 @@ ${resultContent}${attachmentSummary}`;
19749
19813
  });
19750
19814
  }
19751
19815
  }
19816
+ /**
19817
+ * A subagent whose execution the watchdog gave up on leaves its parent
19818
+ * waiting forever for a completion message. Deliver the failure the same way
19819
+ * a sessionFail would, so the parent's turn wakes and can route around it.
19820
+ * Explicit-communication children (e.g. the compaction agent) stay quiet by
19821
+ * contract — only the registry status updates for those.
19822
+ */
19823
+ async notifyParentOfLostExecution(threadId) {
19824
+ try {
19825
+ const agentBuilderId = this.env.AGENT_BUILDER.idFromName("singleton");
19826
+ const agentBuilder = this.env.AGENT_BUILDER.get(agentBuilderId);
19827
+ const meta = await agentBuilder.getThread(threadId);
19828
+ const parentThreadId = meta?.parent ?? null;
19829
+ if (!parentThreadId) return;
19830
+ const parentId = this.env.AGENT_BUILDER_THREAD.idFromName(parentThreadId);
19831
+ const parentStub = this.env.AGENT_BUILDER_THREAD.get(parentId);
19832
+ const parentCommunication = await this.getParentCommunicationModeForChild(
19833
+ parentStub,
19834
+ parentThreadId,
19835
+ threadId
19836
+ );
19837
+ if (parentCommunication !== "explicit") {
19838
+ await parentStub.queueMessage(parentThreadId, {
19839
+ role: "user",
19840
+ content: `Subagent (reference: ${threadId}) has reported a failure:
19841
+
19842
+ 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.`,
19843
+ attachments: [],
19844
+ silent: true,
19845
+ metadata: { subagent_id: threadId }
19846
+ });
19847
+ }
19848
+ await parentStub.updateChildRegistryStatus(parentThreadId, threadId, "idle");
19849
+ } catch (error) {
19850
+ console.error(
19851
+ "[DurableThread] Watchdog: failed to notify parent of lost execution (non-fatal):",
19852
+ error
19853
+ );
19854
+ }
19855
+ }
19752
19856
  /** At most N auto-resumes per rolling window, tracked durably. */
19753
19857
  async consumeWatchdogResumeBudget() {
19754
19858
  const now = Date.now();