@standardagents/builder 0.37.4 → 0.37.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.
Files changed (53) hide show
  1. package/dist/built-in-routes.js +184 -83
  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 +303 -27
  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 +297 -27
  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/index.js CHANGED
@@ -13738,8 +13738,25 @@ var init_ThreadStateImpl = __esm({
13738
13738
  const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
13739
13739
  if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
13740
13740
  await instance.dispatchClientTool(request, toolCallId, side);
13741
- const getDelivered = instance.getDeliveredClientToolResult;
13742
- if (typeof getDelivered === "function") {
13741
+ const withDelivery = instance;
13742
+ if (typeof withDelivery.awaitDeliveredClientToolResult === "function") {
13743
+ try {
13744
+ const delivered = await withDelivery.awaitDeliveredClientToolResult.call(
13745
+ instance,
13746
+ toolCallId,
13747
+ 4e3
13748
+ );
13749
+ if (delivered) {
13750
+ return {
13751
+ ok: delivered.toolStatus !== "error",
13752
+ result: delivered.content,
13753
+ data: { toolCallId }
13754
+ };
13755
+ }
13756
+ } catch {
13757
+ }
13758
+ } else if (typeof withDelivery.getDeliveredClientToolResult === "function") {
13759
+ const getDelivered = withDelivery.getDeliveredClientToolResult;
13743
13760
  const graceDeadline = Date.now() + 4e3;
13744
13761
  while (Date.now() < graceDeadline) {
13745
13762
  await new Promise((r) => setTimeout(r, 150));
@@ -13829,7 +13846,14 @@ var init_ThreadStateImpl = __esm({
13829
13846
  if (typeof stub.proxyStreamOpen === "function") {
13830
13847
  const opened = await stub.proxyStreamOpen(withWake(parentId));
13831
13848
  if (opened.ok && opened.id) {
13832
- return { ok: true, duplex: this.buildProxyDuplex(stub, opened.id) };
13849
+ return {
13850
+ ok: true,
13851
+ duplex: this.buildProxyDuplex(
13852
+ stub,
13853
+ opened.id,
13854
+ opened.parkedPull === true
13855
+ )
13856
+ };
13833
13857
  }
13834
13858
  if (opened.error && !opened.noClient) {
13835
13859
  return {
@@ -13847,11 +13871,15 @@ var init_ThreadStateImpl = __esm({
13847
13871
  }
13848
13872
  /**
13849
13873
  * Build a client duplex that bridges to a stream opened on an ancestor DO
13850
- * (the bridge holder) via chunk-pull RPCs. `readable` polls `proxyStreamPull`;
13851
- * `writable` forwards each chunk with `proxyStreamWrite`; teardown calls
13874
+ * (the bridge holder) via chunk-pull RPCs. `readable` issues PARKED pulls —
13875
+ * a current ancestor holds one `proxyStreamPull` until bytes, completion,
13876
+ * error, or abort, so an idle open stream performs zero repeated pulls.
13877
+ * Older ancestors use the bounded long-pull compatibility path. `writable`
13878
+ * forwards each chunk with `proxyStreamWrite`; teardown calls
13852
13879
  * `proxyStreamAbort`.
13853
13880
  */
13854
- buildProxyDuplex(stub, id) {
13881
+ static PROXY_PULL_MAX_WAIT_MS = 2e4;
13882
+ buildProxyDuplex(stub, id, supportsParkedPull = false) {
13855
13883
  let settleDone;
13856
13884
  const done = new Promise((r) => settleDone = r);
13857
13885
  let closed = false;
@@ -13864,9 +13892,13 @@ var init_ThreadStateImpl = __esm({
13864
13892
  const readable = new ReadableStream({
13865
13893
  async pull(controller) {
13866
13894
  while (!closed) {
13895
+ const pullStartedAt = Date.now();
13867
13896
  let res;
13868
13897
  try {
13869
- res = await stub.proxyStreamPull(id);
13898
+ res = await stub.proxyStreamPull(
13899
+ id,
13900
+ supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
13901
+ );
13870
13902
  } catch (error) {
13871
13903
  controller.error(error instanceof Error ? error : new Error(String(error)));
13872
13904
  settle({ ok: false, error: "proxy stream pull failed" });
@@ -13881,7 +13913,14 @@ var init_ThreadStateImpl = __esm({
13881
13913
  return;
13882
13914
  }
13883
13915
  if (res.chunks.length > 0) return;
13884
- await new Promise((r) => setTimeout(r, 25));
13916
+ if (supportsParkedPull) {
13917
+ controller.error(new Error("parked proxy pull returned without data"));
13918
+ settle({ ok: false, error: "parked proxy pull returned without data" });
13919
+ return;
13920
+ }
13921
+ if (Date.now() - pullStartedAt < 250) {
13922
+ await new Promise((r) => setTimeout(r, 250));
13923
+ }
13885
13924
  }
13886
13925
  },
13887
13926
  cancel() {
@@ -14056,7 +14095,7 @@ var init_ThreadStateImpl = __esm({
14056
14095
  return bytes.buffer;
14057
14096
  }
14058
14097
  async readFileStream(path21, options) {
14059
- const fileInfo = await this._threadInstance.statFile(path21);
14098
+ const fileInfo = this._unwrapStatRecord(await this._threadInstance.statFile(path21));
14060
14099
  if (!fileInfo) {
14061
14100
  return null;
14062
14101
  }
@@ -14111,12 +14150,32 @@ var init_ThreadStateImpl = __esm({
14111
14150
  }
14112
14151
  };
14113
14152
  }
14153
+ /**
14154
+ * Unwrap the thread DO's `{ success, file }` stat envelope (older shapes
14155
+ * returned the record directly). A failed or absent lookup is NULL — the
14156
+ * old code mapped the envelope itself, producing a truthy record with
14157
+ * every field defaulted (size 0, application/octet-stream) even for
14158
+ * NONEXISTENT paths, which let a phantom asset pass an existence check and
14159
+ * a real 1.2MB image read as "0 bytes" (2026-08-11).
14160
+ */
14161
+ _unwrapStatRecord(result) {
14162
+ const record = result?.file ?? result;
14163
+ if (!record || typeof record !== "object") return null;
14164
+ if (!("path" in record) && !("name" in record)) return null;
14165
+ return record;
14166
+ }
14114
14167
  async statFile(path21) {
14115
- const result = await this._threadInstance.statFile(path21);
14116
- return result ? this._mapFileRecord(result) : null;
14168
+ const record = this._unwrapStatRecord(await this._threadInstance.statFile(path21));
14169
+ return record ? this._mapFileRecord(record) : null;
14117
14170
  }
14118
14171
  async readdirFile(path21) {
14119
- const entries = await this._threadInstance.readdirFile(path21);
14172
+ const result = await this._threadInstance.readdirFile(path21);
14173
+ const entries = Array.isArray(result) ? result : result?.files;
14174
+ if (!Array.isArray(entries)) {
14175
+ throw new Error(
14176
+ result?.error || `Could not list directory: ${path21}`
14177
+ );
14178
+ }
14120
14179
  return {
14121
14180
  entries: entries.map((e) => this._mapFileRecord(e))
14122
14181
  };
@@ -22245,6 +22304,10 @@ ${packedThreadRouteCode}
22245
22304
  const isApiRoute = routePath.startsWith('/api/');
22246
22305
 
22247
22306
  let authContext = null;
22307
+ // The router's thread-authorization result, bound to the exact matched
22308
+ // thread id, for same-request reuse by controllers (AB-05). Never cached
22309
+ // across requests.
22310
+ let threadAccess = null;
22248
22311
 
22249
22312
  // Require authentication for all API routes except public ones
22250
22313
  if (isApiRoute && !publicRoute) {
@@ -22277,6 +22340,7 @@ ${packedThreadRouteCode}
22277
22340
  if (!canAccessThread(authContext, thread)) {
22278
22341
  return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
22279
22342
  }
22343
+ threadAccess = { threadId, thread };
22280
22344
  }
22281
22345
  }
22282
22346
  }
@@ -22297,6 +22361,7 @@ ${packedThreadRouteCode}
22297
22361
  config,
22298
22362
  tools,
22299
22363
  auth: authContext, // Add auth context to controller context
22364
+ threadAccess, // Router's thread authorization, bound to the exact id
22300
22365
  };
22301
22366
  const result = await controller(context);
22302
22367
 
@@ -24400,6 +24465,9 @@ var AlarmQueue = class {
24400
24465
  options.sourceLogId ?? null
24401
24466
  );
24402
24467
  await this.rescheduleAlarm();
24468
+ if (method === "runEffect") {
24469
+ await this.emitEffectRunChange(id);
24470
+ }
24403
24471
  return id;
24404
24472
  }
24405
24473
  /**
@@ -24438,6 +24506,9 @@ var AlarmQueue = class {
24438
24506
  `UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
24439
24507
  item.id
24440
24508
  );
24509
+ if (item.method === "runEffect") {
24510
+ await this.emitEffectRunChange(item.id);
24511
+ }
24441
24512
  try {
24442
24513
  const args = JSON.parse(item.args);
24443
24514
  await this.executeMethod(item.method, args, item.id);
@@ -24491,6 +24562,7 @@ var AlarmQueue = class {
24491
24562
  now,
24492
24563
  id
24493
24564
  );
24565
+ await this.emitEffectRunChange(id);
24494
24566
  }
24495
24567
  /**
24496
24568
  * Mark an item as failed but keep it for 24 hours for debugging
@@ -24510,6 +24582,7 @@ var AlarmQueue = class {
24510
24582
  now,
24511
24583
  id
24512
24584
  );
24585
+ await this.emitEffectRunChange(id);
24513
24586
  }
24514
24587
  /**
24515
24588
  * Clean up failed items older than 24 hours
@@ -24599,6 +24672,10 @@ var AlarmQueue = class {
24599
24672
  */
24600
24673
  async recoverOrphanedProcessing(maxAttempts = 3) {
24601
24674
  const now = Date.now() * 1e3;
24675
+ const orphanedEffectsCursor = await this.sql.exec(
24676
+ `SELECT id FROM alarm_queue WHERE status = 'processing' AND method = 'runEffect'`
24677
+ );
24678
+ const orphanedEffectIds = orphanedEffectsCursor.toArray().map((row) => row.id);
24602
24679
  await this.sql.exec(
24603
24680
  `UPDATE alarm_queue
24604
24681
  SET status = 'failed',
@@ -24611,6 +24688,9 @@ var AlarmQueue = class {
24611
24688
  await this.sql.exec(
24612
24689
  `UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
24613
24690
  );
24691
+ for (const id of orphanedEffectIds) {
24692
+ await this.emitEffectRunChange(id);
24693
+ }
24614
24694
  }
24615
24695
  // ============================================================
24616
24696
  // Effect-specific Methods
@@ -24789,6 +24869,7 @@ var AlarmQueue = class {
24789
24869
  const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
24790
24870
  if (canceled) {
24791
24871
  await this.rescheduleAlarm();
24872
+ await this.emitEffectRunChange(id);
24792
24873
  }
24793
24874
  return canceled;
24794
24875
  }
@@ -24863,6 +24944,7 @@ var AlarmQueue = class {
24863
24944
  if (!changed) {
24864
24945
  throw new Error(`Effect run ${item.id} is no longer pending`);
24865
24946
  }
24947
+ await this.emitEffectRunChange(item.id);
24866
24948
  try {
24867
24949
  await this.executeMethod(item.method, JSON.parse(item.args), item.id);
24868
24950
  await this.complete(item.id, item.method);
@@ -24874,6 +24956,45 @@ var AlarmQueue = class {
24874
24956
  await this.rescheduleAlarm();
24875
24957
  }
24876
24958
  }
24959
+ /**
24960
+ * Read one effect row and hand its bounded projection (no args payload) to
24961
+ * the change listener. Self-filtering: operational (non-effect) rows and
24962
+ * already-deleted rows emit nothing, and a listener failure must never
24963
+ * disturb queue processing.
24964
+ */
24965
+ async emitEffectRunChange(id) {
24966
+ const listener = this.options.onEffectRunChanged;
24967
+ if (!listener) return;
24968
+ try {
24969
+ const cursor = await this.sql.exec(
24970
+ `
24971
+ SELECT id, args, scheduled_at, created_at, status, attempts, error, completed_at, manual_run, manual_run_at, replayed_from
24972
+ FROM alarm_queue
24973
+ WHERE id = ? AND method = 'runEffect'
24974
+ LIMIT 1
24975
+ `,
24976
+ id
24977
+ );
24978
+ const row = cursor.toArray()[0];
24979
+ if (!row) return;
24980
+ const { effectName } = this.parseEffectPayload(row.args);
24981
+ listener({
24982
+ id: row.id,
24983
+ name: effectName,
24984
+ scheduledAt: row.scheduled_at,
24985
+ createdAt: row.created_at,
24986
+ status: row.status,
24987
+ attempts: row.attempts,
24988
+ error: row.error,
24989
+ completedAt: row.completed_at,
24990
+ manualRun: row.manual_run === 1,
24991
+ manualRunAt: row.manual_run_at,
24992
+ replayedFrom: row.replayed_from
24993
+ });
24994
+ } catch (error) {
24995
+ console.error(`[AlarmQueue] Failed to emit effect run change for ${id}:`, error);
24996
+ }
24997
+ }
24877
24998
  normalizeOffset(value) {
24878
24999
  const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
24879
25000
  if (!Number.isFinite(parsed)) return 0;
@@ -29344,6 +29465,25 @@ var DurableThread = class _DurableThread extends DurableObject {
29344
29465
  alarmQueueRearmed = false;
29345
29466
  currentAbortController = null;
29346
29467
  isExecuting = false;
29468
+ /**
29469
+ * In-memory waiters parked until the running flow finishes (AB-09): the
29470
+ * executeFlow busy-guard awaits this signal instead of re-checking
29471
+ * isExecuting every 500ms. Every isExecuting=false transition (execution
29472
+ * finallys and the watchdog takeover) notifies. In-memory only — a DO
29473
+ * restart drops waiters together with the busy caller.
29474
+ */
29475
+ executionIdleWaiters = /* @__PURE__ */ new Set();
29476
+ notifyExecutionIdleWaiters() {
29477
+ if (!this.executionIdleWaiters || this.executionIdleWaiters.size === 0) return;
29478
+ const waiters = Array.from(this.executionIdleWaiters);
29479
+ this.executionIdleWaiters.clear();
29480
+ for (const wake of waiters) {
29481
+ try {
29482
+ wake();
29483
+ } catch {
29484
+ }
29485
+ }
29486
+ }
29347
29487
  // ── Execution watchdog state (see watchdogCheck) ─────────────────────────
29348
29488
  // In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
29349
29489
  // chunk and request attempt. Deliberately NOT persisted — after a DO restart
@@ -29368,7 +29508,11 @@ var DurableThread = class _DurableThread extends DurableObject {
29368
29508
  this.alarmQueue = new AlarmQueue(ctx, {
29369
29509
  retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
29370
29510
  isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
29371
- invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 }
29511
+ invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 },
29512
+ // Effect lifecycle transitions broadcast on the thread event channel so
29513
+ // inspectors stay live from the already-open message socket instead of
29514
+ // polling the effects list.
29515
+ onEffectRunChanged: (change) => this.broadcastEvent("effect_changed", change)
29372
29516
  });
29373
29517
  this.alarmQueue.setExecutor(
29374
29518
  async (method, args, itemId) => {
@@ -29927,6 +30071,7 @@ var DurableThread = class _DurableThread extends DurableObject {
29927
30071
  if (value === null || value === void 0) {
29928
30072
  await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
29929
30073
  this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
30074
+ this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
29930
30075
  return;
29931
30076
  }
29932
30077
  const serialized = this.serializeThreadValue(value);
@@ -29943,6 +30088,7 @@ var DurableThread = class _DurableThread extends DurableObject {
29943
30088
  updatedAt
29944
30089
  );
29945
30090
  this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
30091
+ this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
29946
30092
  }
29947
30093
  async listValues(options = {}) {
29948
30094
  await this.ensureMigrated();
@@ -32963,6 +33109,7 @@ ${resultContent}${attachmentSummary}`;
32963
33109
  if (isOwner) {
32964
33110
  void this.resendPendingClientCalls(server);
32965
33111
  }
33112
+ this.notifyOwnerBridgeWaiters();
32966
33113
  return new Response(null, {
32967
33114
  status: 101,
32968
33115
  webSocket: client
@@ -32998,6 +33145,7 @@ ${resultContent}${attachmentSummary}`;
32998
33145
  /** Persist + cache the execution-owner record (null releases ownership). */
32999
33146
  async setExecutionOwner(owner) {
33000
33147
  await this.setValue(EXECUTION_OWNER_KEY, owner);
33148
+ this.notifyOwnerBridgeWaiters();
33001
33149
  }
33002
33150
  /** Whether the given client id has an open bridge socket (excluding `except`).
33003
33151
  * Idle bridge heartbeats are handled by Cloudflare without waking this object,
@@ -33355,6 +33503,55 @@ ${resultContent}${attachmentSummary}`;
33355
33503
  * permission level covers the call) otherwise race the placeholder into
33356
33504
  * stored history, leaving the model blind to its own tool outcomes.
33357
33505
  */
33506
+ /**
33507
+ * In-memory waiters parked on a forwarded call's result delivery (AB-09).
33508
+ * `deliverClientToolResult` resolves them so the approval grace-wait reads
33509
+ * delivered state exactly once instead of re-polling it every 150ms.
33510
+ * In-memory only: a DO restart drops waiters along with the caller that
33511
+ * registered them, and the durable park path still resumes the turn.
33512
+ */
33513
+ deliveredResultWaiters = /* @__PURE__ */ new Map();
33514
+ notifyDeliveredResultWaiters(toolCallId) {
33515
+ const waiters = this.deliveredResultWaiters?.get(toolCallId);
33516
+ if (!waiters) return;
33517
+ this.deliveredResultWaiters.delete(toolCallId);
33518
+ for (const wake of waiters) {
33519
+ try {
33520
+ wake();
33521
+ } catch {
33522
+ }
33523
+ }
33524
+ }
33525
+ /**
33526
+ * Await a parked forwarded call's delivered result: one immediate read,
33527
+ * then a single parked wait resolved by result delivery, bounded by one
33528
+ * hard deadline, then one final read. Replaces the caller-side 150ms
33529
+ * re-poll of {@link getDeliveredClientToolResult}.
33530
+ */
33531
+ async awaitDeliveredClientToolResult(toolCallId, maxWaitMs) {
33532
+ const immediate = await this.getDeliveredClientToolResult(toolCallId);
33533
+ if (immediate) return immediate;
33534
+ if (!(maxWaitMs > 0)) return null;
33535
+ await new Promise((resolve3) => {
33536
+ let waiters = this.deliveredResultWaiters.get(toolCallId);
33537
+ if (!waiters) {
33538
+ waiters = /* @__PURE__ */ new Set();
33539
+ this.deliveredResultWaiters.set(toolCallId, waiters);
33540
+ }
33541
+ const registered = waiters;
33542
+ const deadline = setTimeout(() => {
33543
+ registered.delete(wake);
33544
+ if (registered.size === 0) this.deliveredResultWaiters.delete(toolCallId);
33545
+ resolve3();
33546
+ }, maxWaitMs);
33547
+ const wake = () => {
33548
+ clearTimeout(deadline);
33549
+ resolve3();
33550
+ };
33551
+ registered.add(wake);
33552
+ });
33553
+ return this.getDeliveredClientToolResult(toolCallId);
33554
+ }
33358
33555
  async getDeliveredClientToolResult(toolCallId) {
33359
33556
  await this.ensureMigrated();
33360
33557
  await this.ensurePendingClientTable();
@@ -33490,6 +33687,7 @@ ${resultContent}${attachmentSummary}`;
33490
33687
  `UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
33491
33688
  toolCallId
33492
33689
  );
33690
+ this.notifyDeliveredResultWaiters(toolCallId);
33493
33691
  try {
33494
33692
  const msgCursor = await this.ctx.storage.sql.exec(
33495
33693
  `SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
@@ -33508,8 +33706,24 @@ ${resultContent}${attachmentSummary}`;
33508
33706
  // they are bound to one bridge socket and never park.
33509
33707
  /** Default time to wait for the client to accept a stream open. */
33510
33708
  static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
33511
- /** Poll cadence while waiting for a disconnected owner's bridge to return. */
33512
- static OWNER_REATTACH_POLL_MS = 250;
33709
+ /**
33710
+ * In-memory waiters parked on the owner's bridge socket returning (AB-09).
33711
+ * Resolved by bridge attach and by execution-owner changes; each waiter
33712
+ * re-evaluates against the CURRENT owner on wake. In-memory only: a DO
33713
+ * restart drops waiters with the stream-open call that registered them.
33714
+ */
33715
+ ownerBridgeWaiters = /* @__PURE__ */ new Set();
33716
+ notifyOwnerBridgeWaiters() {
33717
+ if (!this.ownerBridgeWaiters || this.ownerBridgeWaiters.size === 0) return;
33718
+ const waiters = Array.from(this.ownerBridgeWaiters);
33719
+ this.ownerBridgeWaiters.clear();
33720
+ for (const wake of waiters) {
33721
+ try {
33722
+ wake();
33723
+ } catch {
33724
+ }
33725
+ }
33726
+ }
33513
33727
  /**
33514
33728
  * The thread HAS an elected execution owner, but its bridge socket is gone
33515
33729
  * (daemon restart, network blip, DO handoff). Summon the owning machine
@@ -33518,6 +33732,11 @@ ${resultContent}${attachmentSummary}`;
33518
33732
  * Sama One turns with "workspace unavailable" whenever a slow tool/approval
33519
33733
  * outlived the socket. Threads with NO recorded owner (subagent children)
33520
33734
  * still fail fast so ancestor walk-up routing stays immediate.
33735
+ *
33736
+ * The wait parks on bridge-attach/owner-change signals under ONE hard
33737
+ * deadline — no re-poll cadence. Each wake re-reads the owner because the
33738
+ * returning daemon may reclaim with a new generation, and the socket must
33739
+ * match the CURRENT owner.
33521
33740
  */
33522
33741
  async awaitOwnerBridgeSocket(request) {
33523
33742
  if (request.wake?.userId) {
@@ -33525,15 +33744,25 @@ ${resultContent}${attachmentSummary}`;
33525
33744
  }
33526
33745
  const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
33527
33746
  const deadline = Date.now() + timeoutMs;
33528
- while (Date.now() < deadline) {
33529
- await new Promise(
33530
- (resolve3) => setTimeout(resolve3, _DurableThread.OWNER_REATTACH_POLL_MS)
33531
- );
33747
+ this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
33748
+ while (true) {
33532
33749
  const owner = await this.getExecutionOwner();
33533
33750
  const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
33534
33751
  if (socket) return socket;
33752
+ const remainingMs = deadline - Date.now();
33753
+ if (remainingMs <= 0) return null;
33754
+ await new Promise((resolve3) => {
33755
+ const timer = setTimeout(() => {
33756
+ this.ownerBridgeWaiters.delete(wake);
33757
+ resolve3();
33758
+ }, remainingMs);
33759
+ const wake = () => {
33760
+ clearTimeout(timer);
33761
+ resolve3();
33762
+ };
33763
+ this.ownerBridgeWaiters.add(wake);
33764
+ });
33535
33765
  }
33536
- return null;
33537
33766
  }
33538
33767
  /** Open a bidirectional byte stream to the connected owner client. */
33539
33768
  async openClientStream(request) {
@@ -33677,27 +33906,38 @@ ${resultContent}${attachmentSummary}`;
33677
33906
  done: false,
33678
33907
  error: void 0,
33679
33908
  pump: Promise.resolve(),
33680
- close: (error) => duplex.close(error)
33909
+ close: (error) => duplex.close(error),
33910
+ waiter: null,
33911
+ notify: () => {
33912
+ const waiter = entry.waiter;
33913
+ entry.waiter = null;
33914
+ waiter?.();
33915
+ }
33681
33916
  };
33682
33917
  entry.pump = (async () => {
33683
33918
  try {
33684
33919
  while (true) {
33685
33920
  const { value, done } = await reader.read();
33686
33921
  if (done) break;
33687
- if (value) entry.buffer.push(value);
33922
+ if (value) {
33923
+ entry.buffer.push(value);
33924
+ entry.notify();
33925
+ }
33688
33926
  }
33689
33927
  } catch (error) {
33690
33928
  entry.error = error instanceof Error ? error.message : String(error);
33691
33929
  } finally {
33692
33930
  entry.done = true;
33931
+ entry.notify();
33693
33932
  }
33694
33933
  })();
33695
33934
  void duplex.done.then((d) => {
33696
33935
  entry.done = true;
33697
33936
  if (!d.ok && !entry.error) entry.error = d.error;
33937
+ entry.notify();
33698
33938
  });
33699
33939
  this.proxyStreams.set(id, entry);
33700
- return { ok: true, id };
33940
+ return { ok: true, id, parkedPull: true };
33701
33941
  }
33702
33942
  async proxyStreamWrite(id, dataB64) {
33703
33943
  const entry = this.proxyStreams.get(id);
@@ -33707,9 +33947,22 @@ ${resultContent}${attachmentSummary}`;
33707
33947
  } catch {
33708
33948
  }
33709
33949
  }
33710
- async proxyStreamPull(id) {
33950
+ async proxyStreamPull(id, maxWaitMs) {
33711
33951
  const entry = this.proxyStreams.get(id);
33712
33952
  if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
33953
+ if (entry.buffer.length === 0 && !entry.done) {
33954
+ await new Promise((resolve3) => {
33955
+ const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
33956
+ if (entry.waiter === wake) entry.waiter = null;
33957
+ resolve3();
33958
+ }, maxWaitMs) : null;
33959
+ const wake = () => {
33960
+ if (deadline) clearTimeout(deadline);
33961
+ resolve3();
33962
+ };
33963
+ entry.waiter = wake;
33964
+ });
33965
+ }
33713
33966
  const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
33714
33967
  if (entry.done && entry.buffer.length === 0) {
33715
33968
  this.proxyStreams.delete(id);
@@ -33729,6 +33982,9 @@ ${resultContent}${attachmentSummary}`;
33729
33982
  const entry = this.proxyStreams.get(id);
33730
33983
  if (!entry) return;
33731
33984
  this.proxyStreams.delete(id);
33985
+ entry.done = true;
33986
+ if (!entry.error) entry.error = "aborted";
33987
+ entry.notify();
33732
33988
  try {
33733
33989
  entry.close("aborted");
33734
33990
  } catch {
@@ -34084,6 +34340,7 @@ ${resultContent}${attachmentSummary}`;
34084
34340
  this.broadcastEvent(type, data);
34085
34341
  }
34086
34342
  broadcastEvent(type, data) {
34343
+ if (!this.messageSockets || this.messageSockets.size === 0) return;
34087
34344
  const payload = JSON.stringify({
34088
34345
  type: "event",
34089
34346
  eventType: type,
@@ -34510,6 +34767,7 @@ ${resultContent}${attachmentSummary}`;
34510
34767
  } catch {
34511
34768
  }
34512
34769
  this.isExecuting = false;
34770
+ this.notifyExecutionIdleWaiters();
34513
34771
  this.currentAbortController = null;
34514
34772
  await this.recordRegistryActivity(threadId, false);
34515
34773
  tookOver = true;
@@ -34702,9 +34960,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
34702
34960
  await this.assertNotTerminated();
34703
34961
  if (this.isExecuting) {
34704
34962
  const busyWaitBudgetMs = 3e4;
34705
- const waitStartedAt = Date.now();
34706
- while (this.isExecuting && Date.now() - waitStartedAt < busyWaitBudgetMs) {
34707
- await new Promise((resolve3) => setTimeout(resolve3, 500));
34963
+ const deadline = Date.now() + busyWaitBudgetMs;
34964
+ this.executionIdleWaiters ??= /* @__PURE__ */ new Set();
34965
+ while (this.isExecuting) {
34966
+ const remainingMs = deadline - Date.now();
34967
+ if (remainingMs <= 0) break;
34968
+ await new Promise((resolve3) => {
34969
+ const timer = setTimeout(() => {
34970
+ this.executionIdleWaiters.delete(wake);
34971
+ resolve3();
34972
+ }, remainingMs);
34973
+ const wake = () => {
34974
+ clearTimeout(timer);
34975
+ resolve3();
34976
+ };
34977
+ this.executionIdleWaiters.add(wake);
34978
+ });
34708
34979
  }
34709
34980
  if (this.isExecuting) {
34710
34981
  throw new Error(
@@ -34843,6 +35114,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
34843
35114
  return flowResult;
34844
35115
  } finally {
34845
35116
  this.isExecuting = false;
35117
+ this.notifyExecutionIdleWaiters();
34846
35118
  this.currentAbortController = null;
34847
35119
  await this.disarmExecutionWatchdog(threadId);
34848
35120
  await this.recordRegistryActivity(threadId, false);
@@ -35056,6 +35328,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35056
35328
  }, true);
35057
35329
  } finally {
35058
35330
  this.isExecuting = false;
35331
+ this.notifyExecutionIdleWaiters();
35059
35332
  this.currentAbortController = null;
35060
35333
  await this.disarmExecutionWatchdog(threadId);
35061
35334
  await this.recordRegistryActivity(threadId, false);
@@ -35182,6 +35455,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35182
35455
  );
35183
35456
  } finally {
35184
35457
  this.isExecuting = false;
35458
+ this.notifyExecutionIdleWaiters();
35185
35459
  this.currentAbortController = null;
35186
35460
  await this.disarmExecutionWatchdog(threadId);
35187
35461
  await this.recordRegistryActivity(threadId, false);
@@ -35608,6 +35882,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35608
35882
  const fs19 = this.getFileStorage();
35609
35883
  const from = normalizePath2(sourcePath);
35610
35884
  const record = await fs19.move(sourcePath, destinationPath);
35885
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
35611
35886
  this.broadcastEvent("file_deleted", { path: from });
35612
35887
  this.broadcastEvent("file_created", { path: record.path, file: record });
35613
35888
  return { success: true, file: record };
@@ -35626,6 +35901,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35626
35901
  const fs19 = this.getFileStorage();
35627
35902
  const from = normalizePath2(path21);
35628
35903
  const record = await fs19.rename(path21, newName);
35904
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
35629
35905
  this.broadcastEvent("file_deleted", { path: from });
35630
35906
  this.broadcastEvent("file_created", { path: record.path, file: record });
35631
35907
  return { success: true, file: record };