@standardagents/builder 0.37.3 → 0.37.5

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.
Files changed (53) hide show
  1. package/dist/built-in-routes.js +178 -81
  2. package/dist/built-in-routes.js.map +1 -1
  3. package/dist/client/{ApiKeysView-BhuT3pIF.js → ApiKeysView-Pk5oljVU.js} +1 -1
  4. package/dist/client/CenteredContentView-HGpMGAQj.js +1 -0
  5. package/dist/client/CompositionView-DN8dTOCO.js +32 -0
  6. package/dist/client/{ConfirmDialog.vue_vue_type_script_setup_true_lang-BqwwsXWJ.js → ConfirmDialog.vue_vue_type_script_setup_true_lang--j_hnMZ0.js} +1 -1
  7. package/dist/client/{CopyButton.vue_vue_type_script_setup_true_lang-Cqm3Vt3E.js → CopyButton.vue_vue_type_script_setup_true_lang-CpJDcbZx.js} +1 -1
  8. package/dist/client/{DataTable.vue_vue_type_script_setup_true_lang-CA5qgsty.js → DataTable.vue_vue_type_script_setup_true_lang-unLrY-PY.js} +1 -1
  9. package/dist/client/{JsonViewer-ByQKCShF.js → JsonViewer-BYj_yrGB.js} +1 -1
  10. package/dist/client/{LoginView-J4qtHbQK.js → LoginView-CfVqItZV.js} +1 -1
  11. package/dist/client/{MarketplaceView-CshdWDqp.js → MarketplaceView-Cidt0nLf.js} +1 -1
  12. package/dist/client/{Modal.vue_vue_type_script_setup_true_lang-DKdB9A8Z.js → Modal.vue_vue_type_script_setup_true_lang-C1dmm1HX.js} +1 -1
  13. package/dist/client/ModelModal.vue_vue_type_script_setup_true_lang-DIKLuPXT.js +1 -0
  14. package/dist/client/{ModelsView-B_n306yk.js → ModelsView-DH6wNe38.js} +1 -1
  15. package/dist/client/{PromptEditView-C85qRgsE.js → PromptEditView-D_VjoAa-.js} +1 -1
  16. package/dist/client/{PromptModal-PNpoJfqD.js → PromptModal-a9LTOWNt.js} +1 -1
  17. package/dist/client/{PromptsView-BKnfThIF.js → PromptsView-BCCCryKR.js} +1 -1
  18. package/dist/client/{ProvidersView-Df41VhHD.js → ProvidersView-CF4x5F6G.js} +2 -2
  19. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-D5VDoQiK.js +22 -0
  20. package/dist/client/ThreadsView-BzscU1l2.js +1 -0
  21. package/dist/client/{ToolsView-C0MH9x3N.js → ToolsView-CeEtxDZV.js} +1 -1
  22. package/dist/client/{UiCodeBlock.vue_vue_type_script_setup_true_lang-DjDQOYjB.js → UiCodeBlock.vue_vue_type_script_setup_true_lang-BdyzSxZZ.js} +1 -1
  23. package/dist/client/{UsersView--TQiJYP7.js → UsersView-Di5cmUYL.js} +1 -1
  24. package/dist/client/{VariablesView-BO3UljZu.js → VariablesView-CXUr05jD.js} +1 -1
  25. package/dist/client/assets/{CompositionView-nJBKoNx4.css → CompositionView-0qQUWuc1.css} +1 -1
  26. package/dist/client/{check-DeHZQcv4.js → check-YKi1o6p6.js} +1 -1
  27. package/dist/client/{chevron-right-C8Mb0B59.js → chevron-right-T0zzq_GX.js} +1 -1
  28. package/dist/client/{copy-BxJsl-KZ.js → copy-C_gUGAj1.js} +1 -1
  29. package/dist/client/{formkit-0eyyCEP_.js → formkit-CE26dtFp.js} +1 -1
  30. package/dist/client/index-BMqXblce.js +6 -0
  31. package/dist/client/index.html +3 -3
  32. package/dist/client/{package-B9czwyIu.js → package-B70IFSDB.js} +1 -1
  33. package/dist/client/{plus-DqtuqJFm.js → plus-BN9IHYdt.js} +1 -1
  34. package/dist/client/{timeAgo-CDt-vAhI.js → timeAgo-G1ovbWPH.js} +1 -1
  35. package/dist/client/{useActionFeedback-B6UfBJ7l.js → useActionFeedback-7pfPjKMi.js} +1 -1
  36. package/dist/client/{vue-Dw7WLX4c.js → vue-CZ-ORfS3.js} +1 -1
  37. package/dist/{index-DceFbLG9.d.ts → index-Lfan8QMN.d.ts} +46 -0
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.js +297 -25
  40. package/dist/index.js.map +1 -1
  41. package/dist/plugin.js +6 -0
  42. package/dist/plugin.js.map +1 -1
  43. package/dist/runtime.d.ts +45 -5
  44. package/dist/runtime.js +291 -25
  45. package/dist/runtime.js.map +1 -1
  46. package/dist/test.d.ts +1 -1
  47. package/package.json +5 -5
  48. package/dist/client/CenteredContentView-CfgX3Ao3.js +0 -1
  49. package/dist/client/CompositionView-B9FfWMHd.js +0 -32
  50. package/dist/client/ModelModal.vue_vue_type_script_setup_true_lang-BAxaBZsx.js +0 -1
  51. package/dist/client/ThreadInspectorPane.vue_vue_type_script_setup_true_lang-CtIBWHUR.js +0 -22
  52. package/dist/client/ThreadsView-sjo5Rrcm.js +0 -1
  53. package/dist/client/index-BgZnQr8X.js +0 -6
package/dist/runtime.js CHANGED
@@ -10103,10 +10103,18 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10103
10103
  const mimeType = match[1];
10104
10104
  const base64Data = match[2];
10105
10105
  const ext = mimeType.split("/")[1] || "png";
10106
+ if (base64Data.length < 64) {
10107
+ console.error("[FlowEngine] Generated image payload is empty/truncated, skipping");
10108
+ continue;
10109
+ }
10106
10110
  const attachmentId = crypto.randomUUID();
10107
10111
  const path = `/attachments/${Date.now()}-${attachmentId.slice(0, 8)}.${ext}`;
10108
10112
  try {
10109
- await state.thread.instance.writeFile(path, base64Data, mimeType);
10113
+ const written = await state.thread.instance.writeFile(path, base64Data, mimeType);
10114
+ if (written && written.success === false) {
10115
+ console.error("[FlowEngine] Generated image write failed:", written.error);
10116
+ continue;
10117
+ }
10110
10118
  if (image.id) {
10111
10119
  imagePathMap.set(image.id, path);
10112
10120
  }
@@ -10173,10 +10181,18 @@ ${msg.content}` : `${imageDescriptions}${nonImageList}`;
10173
10181
  const mimeType = imageUrl.slice(dataPrefix.length, base64Index);
10174
10182
  const base64Data = imageUrl.slice(base64Index + base64Marker.length);
10175
10183
  const ext = mimeType.split("/")[1] || "png";
10184
+ if (base64Data.length < 64) {
10185
+ console.error(`[FlowEngine] ${toolName} image payload is empty/truncated, skipping`);
10186
+ continue;
10187
+ }
10176
10188
  const attachmentId = crypto.randomUUID();
10177
10189
  const path = `/attachments/${Date.now()}-${attachmentId.slice(0, 8)}.${ext}`;
10178
10190
  try {
10179
- await state.thread.instance.writeFile(path, base64Data, mimeType);
10191
+ const written = await state.thread.instance.writeFile(path, base64Data, mimeType);
10192
+ if (written && written.success === false) {
10193
+ console.error(`[FlowEngine] ${toolName} image write failed:`, written.error);
10194
+ continue;
10195
+ }
10180
10196
  const attachmentRef = {
10181
10197
  id: attachmentId,
10182
10198
  type: "file",
@@ -13713,8 +13729,25 @@ var init_ThreadStateImpl = __esm({
13713
13729
  const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
13714
13730
  if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
13715
13731
  await instance.dispatchClientTool(request, toolCallId, side);
13716
- const getDelivered = instance.getDeliveredClientToolResult;
13717
- if (typeof getDelivered === "function") {
13732
+ const withDelivery = instance;
13733
+ if (typeof withDelivery.awaitDeliveredClientToolResult === "function") {
13734
+ try {
13735
+ const delivered = await withDelivery.awaitDeliveredClientToolResult.call(
13736
+ instance,
13737
+ toolCallId,
13738
+ 4e3
13739
+ );
13740
+ if (delivered) {
13741
+ return {
13742
+ ok: delivered.toolStatus !== "error",
13743
+ result: delivered.content,
13744
+ data: { toolCallId }
13745
+ };
13746
+ }
13747
+ } catch {
13748
+ }
13749
+ } else if (typeof withDelivery.getDeliveredClientToolResult === "function") {
13750
+ const getDelivered = withDelivery.getDeliveredClientToolResult;
13718
13751
  const graceDeadline = Date.now() + 4e3;
13719
13752
  while (Date.now() < graceDeadline) {
13720
13753
  await new Promise((r) => setTimeout(r, 150));
@@ -13804,7 +13837,14 @@ var init_ThreadStateImpl = __esm({
13804
13837
  if (typeof stub.proxyStreamOpen === "function") {
13805
13838
  const opened = await stub.proxyStreamOpen(withWake(parentId));
13806
13839
  if (opened.ok && opened.id) {
13807
- return { ok: true, duplex: this.buildProxyDuplex(stub, opened.id) };
13840
+ return {
13841
+ ok: true,
13842
+ duplex: this.buildProxyDuplex(
13843
+ stub,
13844
+ opened.id,
13845
+ opened.parkedPull === true
13846
+ )
13847
+ };
13808
13848
  }
13809
13849
  if (opened.error && !opened.noClient) {
13810
13850
  return {
@@ -13822,11 +13862,15 @@ var init_ThreadStateImpl = __esm({
13822
13862
  }
13823
13863
  /**
13824
13864
  * Build a client duplex that bridges to a stream opened on an ancestor DO
13825
- * (the bridge holder) via chunk-pull RPCs. `readable` polls `proxyStreamPull`;
13826
- * `writable` forwards each chunk with `proxyStreamWrite`; teardown calls
13865
+ * (the bridge holder) via chunk-pull RPCs. `readable` issues PARKED pulls —
13866
+ * a current ancestor holds one `proxyStreamPull` until bytes, completion,
13867
+ * error, or abort, so an idle open stream performs zero repeated pulls.
13868
+ * Older ancestors use the bounded long-pull compatibility path. `writable`
13869
+ * forwards each chunk with `proxyStreamWrite`; teardown calls
13827
13870
  * `proxyStreamAbort`.
13828
13871
  */
13829
- buildProxyDuplex(stub, id) {
13872
+ static PROXY_PULL_MAX_WAIT_MS = 2e4;
13873
+ buildProxyDuplex(stub, id, supportsParkedPull = false) {
13830
13874
  let settleDone;
13831
13875
  const done = new Promise((r) => settleDone = r);
13832
13876
  let closed = false;
@@ -13839,9 +13883,13 @@ var init_ThreadStateImpl = __esm({
13839
13883
  const readable = new ReadableStream({
13840
13884
  async pull(controller) {
13841
13885
  while (!closed) {
13886
+ const pullStartedAt = Date.now();
13842
13887
  let res;
13843
13888
  try {
13844
- res = await stub.proxyStreamPull(id);
13889
+ res = await stub.proxyStreamPull(
13890
+ id,
13891
+ supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
13892
+ );
13845
13893
  } catch (error) {
13846
13894
  controller.error(error instanceof Error ? error : new Error(String(error)));
13847
13895
  settle({ ok: false, error: "proxy stream pull failed" });
@@ -13856,7 +13904,14 @@ var init_ThreadStateImpl = __esm({
13856
13904
  return;
13857
13905
  }
13858
13906
  if (res.chunks.length > 0) return;
13859
- await new Promise((r) => setTimeout(r, 25));
13907
+ if (supportsParkedPull) {
13908
+ controller.error(new Error("parked proxy pull returned without data"));
13909
+ settle({ ok: false, error: "parked proxy pull returned without data" });
13910
+ return;
13911
+ }
13912
+ if (Date.now() - pullStartedAt < 250) {
13913
+ await new Promise((r) => setTimeout(r, 250));
13914
+ }
13860
13915
  }
13861
13916
  },
13862
13917
  cancel() {
@@ -15975,6 +16030,9 @@ var AlarmQueue = class {
15975
16030
  options.sourceLogId ?? null
15976
16031
  );
15977
16032
  await this.rescheduleAlarm();
16033
+ if (method === "runEffect") {
16034
+ await this.emitEffectRunChange(id);
16035
+ }
15978
16036
  return id;
15979
16037
  }
15980
16038
  /**
@@ -16013,6 +16071,9 @@ var AlarmQueue = class {
16013
16071
  `UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
16014
16072
  item.id
16015
16073
  );
16074
+ if (item.method === "runEffect") {
16075
+ await this.emitEffectRunChange(item.id);
16076
+ }
16016
16077
  try {
16017
16078
  const args = JSON.parse(item.args);
16018
16079
  await this.executeMethod(item.method, args, item.id);
@@ -16066,6 +16127,7 @@ var AlarmQueue = class {
16066
16127
  now,
16067
16128
  id
16068
16129
  );
16130
+ await this.emitEffectRunChange(id);
16069
16131
  }
16070
16132
  /**
16071
16133
  * Mark an item as failed but keep it for 24 hours for debugging
@@ -16085,6 +16147,7 @@ var AlarmQueue = class {
16085
16147
  now,
16086
16148
  id
16087
16149
  );
16150
+ await this.emitEffectRunChange(id);
16088
16151
  }
16089
16152
  /**
16090
16153
  * Clean up failed items older than 24 hours
@@ -16174,6 +16237,10 @@ var AlarmQueue = class {
16174
16237
  */
16175
16238
  async recoverOrphanedProcessing(maxAttempts = 3) {
16176
16239
  const now = Date.now() * 1e3;
16240
+ const orphanedEffectsCursor = await this.sql.exec(
16241
+ `SELECT id FROM alarm_queue WHERE status = 'processing' AND method = 'runEffect'`
16242
+ );
16243
+ const orphanedEffectIds = orphanedEffectsCursor.toArray().map((row) => row.id);
16177
16244
  await this.sql.exec(
16178
16245
  `UPDATE alarm_queue
16179
16246
  SET status = 'failed',
@@ -16186,6 +16253,9 @@ var AlarmQueue = class {
16186
16253
  await this.sql.exec(
16187
16254
  `UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
16188
16255
  );
16256
+ for (const id of orphanedEffectIds) {
16257
+ await this.emitEffectRunChange(id);
16258
+ }
16189
16259
  }
16190
16260
  // ============================================================
16191
16261
  // Effect-specific Methods
@@ -16364,6 +16434,7 @@ var AlarmQueue = class {
16364
16434
  const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
16365
16435
  if (canceled) {
16366
16436
  await this.rescheduleAlarm();
16437
+ await this.emitEffectRunChange(id);
16367
16438
  }
16368
16439
  return canceled;
16369
16440
  }
@@ -16438,6 +16509,7 @@ var AlarmQueue = class {
16438
16509
  if (!changed) {
16439
16510
  throw new Error(`Effect run ${item.id} is no longer pending`);
16440
16511
  }
16512
+ await this.emitEffectRunChange(item.id);
16441
16513
  try {
16442
16514
  await this.executeMethod(item.method, JSON.parse(item.args), item.id);
16443
16515
  await this.complete(item.id, item.method);
@@ -16449,6 +16521,45 @@ var AlarmQueue = class {
16449
16521
  await this.rescheduleAlarm();
16450
16522
  }
16451
16523
  }
16524
+ /**
16525
+ * Read one effect row and hand its bounded projection (no args payload) to
16526
+ * the change listener. Self-filtering: operational (non-effect) rows and
16527
+ * already-deleted rows emit nothing, and a listener failure must never
16528
+ * disturb queue processing.
16529
+ */
16530
+ async emitEffectRunChange(id) {
16531
+ const listener = this.options.onEffectRunChanged;
16532
+ if (!listener) return;
16533
+ try {
16534
+ const cursor = await this.sql.exec(
16535
+ `
16536
+ SELECT id, args, scheduled_at, created_at, status, attempts, error, completed_at, manual_run, manual_run_at, replayed_from
16537
+ FROM alarm_queue
16538
+ WHERE id = ? AND method = 'runEffect'
16539
+ LIMIT 1
16540
+ `,
16541
+ id
16542
+ );
16543
+ const row = cursor.toArray()[0];
16544
+ if (!row) return;
16545
+ const { effectName } = this.parseEffectPayload(row.args);
16546
+ listener({
16547
+ id: row.id,
16548
+ name: effectName,
16549
+ scheduledAt: row.scheduled_at,
16550
+ createdAt: row.created_at,
16551
+ status: row.status,
16552
+ attempts: row.attempts,
16553
+ error: row.error,
16554
+ completedAt: row.completed_at,
16555
+ manualRun: row.manual_run === 1,
16556
+ manualRunAt: row.manual_run_at,
16557
+ replayedFrom: row.replayed_from
16558
+ });
16559
+ } catch (error) {
16560
+ console.error(`[AlarmQueue] Failed to emit effect run change for ${id}:`, error);
16561
+ }
16562
+ }
16452
16563
  normalizeOffset(value) {
16453
16564
  const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
16454
16565
  if (!Number.isFinite(parsed)) return 0;
@@ -20919,6 +21030,25 @@ var DurableThread = class _DurableThread extends DurableObject {
20919
21030
  alarmQueueRearmed = false;
20920
21031
  currentAbortController = null;
20921
21032
  isExecuting = false;
21033
+ /**
21034
+ * In-memory waiters parked until the running flow finishes (AB-09): the
21035
+ * executeFlow busy-guard awaits this signal instead of re-checking
21036
+ * isExecuting every 500ms. Every isExecuting=false transition (execution
21037
+ * finallys and the watchdog takeover) notifies. In-memory only — a DO
21038
+ * restart drops waiters together with the busy caller.
21039
+ */
21040
+ executionIdleWaiters = /* @__PURE__ */ new Set();
21041
+ notifyExecutionIdleWaiters() {
21042
+ if (!this.executionIdleWaiters || this.executionIdleWaiters.size === 0) return;
21043
+ const waiters = Array.from(this.executionIdleWaiters);
21044
+ this.executionIdleWaiters.clear();
21045
+ for (const wake of waiters) {
21046
+ try {
21047
+ wake();
21048
+ } catch {
21049
+ }
21050
+ }
21051
+ }
20922
21052
  // ── Execution watchdog state (see watchdogCheck) ─────────────────────────
20923
21053
  // In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
20924
21054
  // chunk and request attempt. Deliberately NOT persisted — after a DO restart
@@ -20943,7 +21073,11 @@ var DurableThread = class _DurableThread extends DurableObject {
20943
21073
  this.alarmQueue = new AlarmQueue(ctx, {
20944
21074
  retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
20945
21075
  isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
20946
- invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 }
21076
+ invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 },
21077
+ // Effect lifecycle transitions broadcast on the thread event channel so
21078
+ // inspectors stay live from the already-open message socket instead of
21079
+ // polling the effects list.
21080
+ onEffectRunChanged: (change) => this.broadcastEvent("effect_changed", change)
20947
21081
  });
20948
21082
  this.alarmQueue.setExecutor(
20949
21083
  async (method, args, itemId) => {
@@ -21502,6 +21636,7 @@ var DurableThread = class _DurableThread extends DurableObject {
21502
21636
  if (value === null || value === void 0) {
21503
21637
  await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
21504
21638
  this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
21639
+ this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
21505
21640
  return;
21506
21641
  }
21507
21642
  const serialized = this.serializeThreadValue(value);
@@ -21518,6 +21653,7 @@ var DurableThread = class _DurableThread extends DurableObject {
21518
21653
  updatedAt
21519
21654
  );
21520
21655
  this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
21656
+ this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
21521
21657
  }
21522
21658
  async listValues(options = {}) {
21523
21659
  await this.ensureMigrated();
@@ -24538,6 +24674,7 @@ ${resultContent}${attachmentSummary}`;
24538
24674
  if (isOwner) {
24539
24675
  void this.resendPendingClientCalls(server);
24540
24676
  }
24677
+ this.notifyOwnerBridgeWaiters();
24541
24678
  return new Response(null, {
24542
24679
  status: 101,
24543
24680
  webSocket: client
@@ -24573,6 +24710,7 @@ ${resultContent}${attachmentSummary}`;
24573
24710
  /** Persist + cache the execution-owner record (null releases ownership). */
24574
24711
  async setExecutionOwner(owner) {
24575
24712
  await this.setValue(EXECUTION_OWNER_KEY, owner);
24713
+ this.notifyOwnerBridgeWaiters();
24576
24714
  }
24577
24715
  /** Whether the given client id has an open bridge socket (excluding `except`).
24578
24716
  * Idle bridge heartbeats are handled by Cloudflare without waking this object,
@@ -24930,6 +25068,55 @@ ${resultContent}${attachmentSummary}`;
24930
25068
  * permission level covers the call) otherwise race the placeholder into
24931
25069
  * stored history, leaving the model blind to its own tool outcomes.
24932
25070
  */
25071
+ /**
25072
+ * In-memory waiters parked on a forwarded call's result delivery (AB-09).
25073
+ * `deliverClientToolResult` resolves them so the approval grace-wait reads
25074
+ * delivered state exactly once instead of re-polling it every 150ms.
25075
+ * In-memory only: a DO restart drops waiters along with the caller that
25076
+ * registered them, and the durable park path still resumes the turn.
25077
+ */
25078
+ deliveredResultWaiters = /* @__PURE__ */ new Map();
25079
+ notifyDeliveredResultWaiters(toolCallId) {
25080
+ const waiters = this.deliveredResultWaiters?.get(toolCallId);
25081
+ if (!waiters) return;
25082
+ this.deliveredResultWaiters.delete(toolCallId);
25083
+ for (const wake of waiters) {
25084
+ try {
25085
+ wake();
25086
+ } catch {
25087
+ }
25088
+ }
25089
+ }
25090
+ /**
25091
+ * Await a parked forwarded call's delivered result: one immediate read,
25092
+ * then a single parked wait resolved by result delivery, bounded by one
25093
+ * hard deadline, then one final read. Replaces the caller-side 150ms
25094
+ * re-poll of {@link getDeliveredClientToolResult}.
25095
+ */
25096
+ async awaitDeliveredClientToolResult(toolCallId, maxWaitMs) {
25097
+ const immediate = await this.getDeliveredClientToolResult(toolCallId);
25098
+ if (immediate) return immediate;
25099
+ if (!(maxWaitMs > 0)) return null;
25100
+ await new Promise((resolve) => {
25101
+ let waiters = this.deliveredResultWaiters.get(toolCallId);
25102
+ if (!waiters) {
25103
+ waiters = /* @__PURE__ */ new Set();
25104
+ this.deliveredResultWaiters.set(toolCallId, waiters);
25105
+ }
25106
+ const registered = waiters;
25107
+ const deadline = setTimeout(() => {
25108
+ registered.delete(wake);
25109
+ if (registered.size === 0) this.deliveredResultWaiters.delete(toolCallId);
25110
+ resolve();
25111
+ }, maxWaitMs);
25112
+ const wake = () => {
25113
+ clearTimeout(deadline);
25114
+ resolve();
25115
+ };
25116
+ registered.add(wake);
25117
+ });
25118
+ return this.getDeliveredClientToolResult(toolCallId);
25119
+ }
24933
25120
  async getDeliveredClientToolResult(toolCallId) {
24934
25121
  await this.ensureMigrated();
24935
25122
  await this.ensurePendingClientTable();
@@ -25065,6 +25252,7 @@ ${resultContent}${attachmentSummary}`;
25065
25252
  `UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
25066
25253
  toolCallId
25067
25254
  );
25255
+ this.notifyDeliveredResultWaiters(toolCallId);
25068
25256
  try {
25069
25257
  const msgCursor = await this.ctx.storage.sql.exec(
25070
25258
  `SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
@@ -25083,8 +25271,24 @@ ${resultContent}${attachmentSummary}`;
25083
25271
  // they are bound to one bridge socket and never park.
25084
25272
  /** Default time to wait for the client to accept a stream open. */
25085
25273
  static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
25086
- /** Poll cadence while waiting for a disconnected owner's bridge to return. */
25087
- static OWNER_REATTACH_POLL_MS = 250;
25274
+ /**
25275
+ * In-memory waiters parked on the owner's bridge socket returning (AB-09).
25276
+ * Resolved by bridge attach and by execution-owner changes; each waiter
25277
+ * re-evaluates against the CURRENT owner on wake. In-memory only: a DO
25278
+ * restart drops waiters with the stream-open call that registered them.
25279
+ */
25280
+ ownerBridgeWaiters = /* @__PURE__ */ new Set();
25281
+ notifyOwnerBridgeWaiters() {
25282
+ if (!this.ownerBridgeWaiters || this.ownerBridgeWaiters.size === 0) return;
25283
+ const waiters = Array.from(this.ownerBridgeWaiters);
25284
+ this.ownerBridgeWaiters.clear();
25285
+ for (const wake of waiters) {
25286
+ try {
25287
+ wake();
25288
+ } catch {
25289
+ }
25290
+ }
25291
+ }
25088
25292
  /**
25089
25293
  * The thread HAS an elected execution owner, but its bridge socket is gone
25090
25294
  * (daemon restart, network blip, DO handoff). Summon the owning machine
@@ -25093,6 +25297,11 @@ ${resultContent}${attachmentSummary}`;
25093
25297
  * Sama One turns with "workspace unavailable" whenever a slow tool/approval
25094
25298
  * outlived the socket. Threads with NO recorded owner (subagent children)
25095
25299
  * still fail fast so ancestor walk-up routing stays immediate.
25300
+ *
25301
+ * The wait parks on bridge-attach/owner-change signals under ONE hard
25302
+ * deadline — no re-poll cadence. Each wake re-reads the owner because the
25303
+ * returning daemon may reclaim with a new generation, and the socket must
25304
+ * match the CURRENT owner.
25096
25305
  */
25097
25306
  async awaitOwnerBridgeSocket(request) {
25098
25307
  if (request.wake?.userId) {
@@ -25100,15 +25309,25 @@ ${resultContent}${attachmentSummary}`;
25100
25309
  }
25101
25310
  const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
25102
25311
  const deadline = Date.now() + timeoutMs;
25103
- while (Date.now() < deadline) {
25104
- await new Promise(
25105
- (resolve) => setTimeout(resolve, _DurableThread.OWNER_REATTACH_POLL_MS)
25106
- );
25312
+ this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
25313
+ while (true) {
25107
25314
  const owner = await this.getExecutionOwner();
25108
25315
  const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
25109
25316
  if (socket) return socket;
25317
+ const remainingMs = deadline - Date.now();
25318
+ if (remainingMs <= 0) return null;
25319
+ await new Promise((resolve) => {
25320
+ const timer = setTimeout(() => {
25321
+ this.ownerBridgeWaiters.delete(wake);
25322
+ resolve();
25323
+ }, remainingMs);
25324
+ const wake = () => {
25325
+ clearTimeout(timer);
25326
+ resolve();
25327
+ };
25328
+ this.ownerBridgeWaiters.add(wake);
25329
+ });
25110
25330
  }
25111
- return null;
25112
25331
  }
25113
25332
  /** Open a bidirectional byte stream to the connected owner client. */
25114
25333
  async openClientStream(request) {
@@ -25252,27 +25471,38 @@ ${resultContent}${attachmentSummary}`;
25252
25471
  done: false,
25253
25472
  error: void 0,
25254
25473
  pump: Promise.resolve(),
25255
- close: (error) => duplex.close(error)
25474
+ close: (error) => duplex.close(error),
25475
+ waiter: null,
25476
+ notify: () => {
25477
+ const waiter = entry.waiter;
25478
+ entry.waiter = null;
25479
+ waiter?.();
25480
+ }
25256
25481
  };
25257
25482
  entry.pump = (async () => {
25258
25483
  try {
25259
25484
  while (true) {
25260
25485
  const { value, done } = await reader.read();
25261
25486
  if (done) break;
25262
- if (value) entry.buffer.push(value);
25487
+ if (value) {
25488
+ entry.buffer.push(value);
25489
+ entry.notify();
25490
+ }
25263
25491
  }
25264
25492
  } catch (error) {
25265
25493
  entry.error = error instanceof Error ? error.message : String(error);
25266
25494
  } finally {
25267
25495
  entry.done = true;
25496
+ entry.notify();
25268
25497
  }
25269
25498
  })();
25270
25499
  void duplex.done.then((d) => {
25271
25500
  entry.done = true;
25272
25501
  if (!d.ok && !entry.error) entry.error = d.error;
25502
+ entry.notify();
25273
25503
  });
25274
25504
  this.proxyStreams.set(id, entry);
25275
- return { ok: true, id };
25505
+ return { ok: true, id, parkedPull: true };
25276
25506
  }
25277
25507
  async proxyStreamWrite(id, dataB64) {
25278
25508
  const entry = this.proxyStreams.get(id);
@@ -25282,9 +25512,22 @@ ${resultContent}${attachmentSummary}`;
25282
25512
  } catch {
25283
25513
  }
25284
25514
  }
25285
- async proxyStreamPull(id) {
25515
+ async proxyStreamPull(id, maxWaitMs) {
25286
25516
  const entry = this.proxyStreams.get(id);
25287
25517
  if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
25518
+ if (entry.buffer.length === 0 && !entry.done) {
25519
+ await new Promise((resolve) => {
25520
+ const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
25521
+ if (entry.waiter === wake) entry.waiter = null;
25522
+ resolve();
25523
+ }, maxWaitMs) : null;
25524
+ const wake = () => {
25525
+ if (deadline) clearTimeout(deadline);
25526
+ resolve();
25527
+ };
25528
+ entry.waiter = wake;
25529
+ });
25530
+ }
25288
25531
  const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
25289
25532
  if (entry.done && entry.buffer.length === 0) {
25290
25533
  this.proxyStreams.delete(id);
@@ -25304,6 +25547,9 @@ ${resultContent}${attachmentSummary}`;
25304
25547
  const entry = this.proxyStreams.get(id);
25305
25548
  if (!entry) return;
25306
25549
  this.proxyStreams.delete(id);
25550
+ entry.done = true;
25551
+ if (!entry.error) entry.error = "aborted";
25552
+ entry.notify();
25307
25553
  try {
25308
25554
  entry.close("aborted");
25309
25555
  } catch {
@@ -25659,6 +25905,7 @@ ${resultContent}${attachmentSummary}`;
25659
25905
  this.broadcastEvent(type, data);
25660
25906
  }
25661
25907
  broadcastEvent(type, data) {
25908
+ if (!this.messageSockets || this.messageSockets.size === 0) return;
25662
25909
  const payload = JSON.stringify({
25663
25910
  type: "event",
25664
25911
  eventType: type,
@@ -26085,6 +26332,7 @@ ${resultContent}${attachmentSummary}`;
26085
26332
  } catch {
26086
26333
  }
26087
26334
  this.isExecuting = false;
26335
+ this.notifyExecutionIdleWaiters();
26088
26336
  this.currentAbortController = null;
26089
26337
  await this.recordRegistryActivity(threadId, false);
26090
26338
  tookOver = true;
@@ -26277,9 +26525,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26277
26525
  await this.assertNotTerminated();
26278
26526
  if (this.isExecuting) {
26279
26527
  const busyWaitBudgetMs = 3e4;
26280
- const waitStartedAt = Date.now();
26281
- while (this.isExecuting && Date.now() - waitStartedAt < busyWaitBudgetMs) {
26282
- await new Promise((resolve) => setTimeout(resolve, 500));
26528
+ const deadline = Date.now() + busyWaitBudgetMs;
26529
+ this.executionIdleWaiters ??= /* @__PURE__ */ new Set();
26530
+ while (this.isExecuting) {
26531
+ const remainingMs = deadline - Date.now();
26532
+ if (remainingMs <= 0) break;
26533
+ await new Promise((resolve) => {
26534
+ const timer = setTimeout(() => {
26535
+ this.executionIdleWaiters.delete(wake);
26536
+ resolve();
26537
+ }, remainingMs);
26538
+ const wake = () => {
26539
+ clearTimeout(timer);
26540
+ resolve();
26541
+ };
26542
+ this.executionIdleWaiters.add(wake);
26543
+ });
26283
26544
  }
26284
26545
  if (this.isExecuting) {
26285
26546
  throw new Error(
@@ -26418,6 +26679,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26418
26679
  return flowResult;
26419
26680
  } finally {
26420
26681
  this.isExecuting = false;
26682
+ this.notifyExecutionIdleWaiters();
26421
26683
  this.currentAbortController = null;
26422
26684
  await this.disarmExecutionWatchdog(threadId);
26423
26685
  await this.recordRegistryActivity(threadId, false);
@@ -26631,6 +26893,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26631
26893
  }, true);
26632
26894
  } finally {
26633
26895
  this.isExecuting = false;
26896
+ this.notifyExecutionIdleWaiters();
26634
26897
  this.currentAbortController = null;
26635
26898
  await this.disarmExecutionWatchdog(threadId);
26636
26899
  await this.recordRegistryActivity(threadId, false);
@@ -26757,6 +27020,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26757
27020
  );
26758
27021
  } finally {
26759
27022
  this.isExecuting = false;
27023
+ this.notifyExecutionIdleWaiters();
26760
27024
  this.currentAbortController = null;
26761
27025
  await this.disarmExecutionWatchdog(threadId);
26762
27026
  await this.recordRegistryActivity(threadId, false);
@@ -27183,6 +27447,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
27183
27447
  const fs = this.getFileStorage();
27184
27448
  const from = normalizePath2(sourcePath);
27185
27449
  const record = await fs.move(sourcePath, destinationPath);
27450
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
27186
27451
  this.broadcastEvent("file_deleted", { path: from });
27187
27452
  this.broadcastEvent("file_created", { path: record.path, file: record });
27188
27453
  return { success: true, file: record };
@@ -27201,6 +27466,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
27201
27466
  const fs = this.getFileStorage();
27202
27467
  const from = normalizePath2(path);
27203
27468
  const record = await fs.rename(path, newName);
27469
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
27204
27470
  this.broadcastEvent("file_deleted", { path: from });
27205
27471
  this.broadcastEvent("file_created", { path: record.path, file: record });
27206
27472
  return { success: true, file: record };