@standardagents/builder 0.37.4 → 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 +160 -79
  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 +279 -23
  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 +273 -23
  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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { i as AuthContext, e as ThreadEnv, v as Agent, x as ThreadMetadata, y as ToolResult, M as Message, n as ClientToolRequest, o as ClientToolResponse, p as ClientStreamRequest, q as ClientStreamResponse, G as FileRecord, F as FlowState, K as FileStats, J as GrepResult, H as AttachmentRef, Q as MessageContent, E as Env, w as ThreadInstance } from './index-DceFbLG9.js';
2
- export { A as AuthUser, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, t as ClientDuplex, u as ClientForwarding, B as Controller, a as ControllerContext, z as FlowResult, O as ImageContentPart, I as ImageMetadata, L as LLMResponse, P as MultimodalContent, R as RequestContext, S as StorageBackend, D as TelemetryEvent, N as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as authenticate, j as callClient, k as callClientStream, c as createThreadEndpointHandler, m as decodeStreamChunk, d as defineController, l as encodeStreamChunk, h as requireAdmin, r as requireAuth, s as supportsClientForwarding } from './index-DceFbLG9.js';
1
+ import { i as AuthContext, e as ThreadEnv, v as Agent, x as ThreadMetadata, y as ToolResult, M as Message, n as ClientToolRequest, o as ClientToolResponse, p as ClientStreamRequest, q as ClientStreamResponse, G as FileRecord, F as FlowState, K as FileStats, J as GrepResult, H as AttachmentRef, Q as MessageContent, E as Env, w as ThreadInstance } from './index-Lfan8QMN.js';
2
+ export { A as AuthUser, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, t as ClientDuplex, u as ClientForwarding, B as Controller, a as ControllerContext, z as FlowResult, O as ImageContentPart, I as ImageMetadata, L as LLMResponse, P as MultimodalContent, R as RequestContext, S as StorageBackend, D as TelemetryEvent, N as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as authenticate, j as callClient, k as callClientStream, c as createThreadEndpointHandler, m as decodeStreamChunk, d as defineController, l as encodeStreamChunk, h as requireAdmin, r as requireAuth, s as supportsClientForwarding } from './index-Lfan8QMN.js';
3
3
  import { CodeExecutionOptions, CodeExecutionResult, SubagentRegistryEntry, InjectMessageInput, QueueMessageInput, CredentialGrant, SkillMetadata, SkillFileInput, ModelDefinition as ModelDefinition$1, ToolArgs, PromptTextPart, PromptEnvPart, VariableDefinition, SubpromptConfig as SubpromptConfig$1, PromptToolConfig as PromptToolConfig$1, SubagentToolConfig as SubagentToolConfig$1, ReasoningConfig, SideConfig as SideConfig$1, LLMProviderInterface, ContentPart, TextPart, ImagePart, FilePart, ThreadState, NamespaceContext, DefinitionLoader } from '@standardagents/spec';
4
4
  export { AgentType, DefinitionLoader, GlobalNamespaceContext, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ModelCapabilities as ProviderModelCapabilities, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHook, defineModel, definePrompt, defineTool, isPacked, isVisibleInNamespace, mapReasoningLevel } from '@standardagents/spec';
5
5
  import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
@@ -224,6 +224,15 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
224
224
  private alarmQueueRearmed;
225
225
  private currentAbortController;
226
226
  private isExecuting;
227
+ /**
228
+ * In-memory waiters parked until the running flow finishes (AB-09): the
229
+ * executeFlow busy-guard awaits this signal instead of re-checking
230
+ * isExecuting every 500ms. Every isExecuting=false transition (execution
231
+ * finallys and the watchdog takeover) notifies. In-memory only — a DO
232
+ * restart drops waiters together with the busy caller.
233
+ */
234
+ private executionIdleWaiters;
235
+ private notifyExecutionIdleWaiters;
227
236
  private lastProgressAt;
228
237
  private watchdogWedgeStrikes;
229
238
  private readonly watchdogIntervalMs;
@@ -1160,6 +1169,25 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1160
1169
  * permission level covers the call) otherwise race the placeholder into
1161
1170
  * stored history, leaving the model blind to its own tool outcomes.
1162
1171
  */
1172
+ /**
1173
+ * In-memory waiters parked on a forwarded call's result delivery (AB-09).
1174
+ * `deliverClientToolResult` resolves them so the approval grace-wait reads
1175
+ * delivered state exactly once instead of re-polling it every 150ms.
1176
+ * In-memory only: a DO restart drops waiters along with the caller that
1177
+ * registered them, and the durable park path still resumes the turn.
1178
+ */
1179
+ private deliveredResultWaiters;
1180
+ private notifyDeliveredResultWaiters;
1181
+ /**
1182
+ * Await a parked forwarded call's delivered result: one immediate read,
1183
+ * then a single parked wait resolved by result delivery, bounded by one
1184
+ * hard deadline, then one final read. Replaces the caller-side 150ms
1185
+ * re-poll of {@link getDeliveredClientToolResult}.
1186
+ */
1187
+ awaitDeliveredClientToolResult(toolCallId: string, maxWaitMs: number): Promise<{
1188
+ content: string;
1189
+ toolStatus: string | null;
1190
+ } | null>;
1163
1191
  getDeliveredClientToolResult(toolCallId: string): Promise<{
1164
1192
  content: string;
1165
1193
  toolStatus: string | null;
@@ -1185,8 +1213,14 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1185
1213
  } | null>;
1186
1214
  /** Default time to wait for the client to accept a stream open. */
1187
1215
  private static readonly CLIENT_STREAM_OPEN_TIMEOUT_MS;
1188
- /** Poll cadence while waiting for a disconnected owner's bridge to return. */
1189
- private static readonly OWNER_REATTACH_POLL_MS;
1216
+ /**
1217
+ * In-memory waiters parked on the owner's bridge socket returning (AB-09).
1218
+ * Resolved by bridge attach and by execution-owner changes; each waiter
1219
+ * re-evaluates against the CURRENT owner on wake. In-memory only: a DO
1220
+ * restart drops waiters with the stream-open call that registered them.
1221
+ */
1222
+ private ownerBridgeWaiters;
1223
+ private notifyOwnerBridgeWaiters;
1190
1224
  /**
1191
1225
  * The thread HAS an elected execution owner, but its bridge socket is gone
1192
1226
  * (daemon restart, network blip, DO handoff). Summon the owning machine
@@ -1195,6 +1229,11 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1195
1229
  * Sama One turns with "workspace unavailable" whenever a slow tool/approval
1196
1230
  * outlived the socket. Threads with NO recorded owner (subagent children)
1197
1231
  * still fail fast so ancestor walk-up routing stays immediate.
1232
+ *
1233
+ * The wait parks on bridge-attach/owner-change signals under ONE hard
1234
+ * deadline — no re-poll cadence. Each wake re-reads the owner because the
1235
+ * returning daemon may reclaim with a new generation, and the socket must
1236
+ * match the CURRENT owner.
1198
1237
  */
1199
1238
  private awaitOwnerBridgeSocket;
1200
1239
  /** Open a bidirectional byte stream to the connected owner client. */
@@ -1205,10 +1244,11 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1205
1244
  id?: string;
1206
1245
  error?: string;
1207
1246
  noClient?: boolean;
1247
+ parkedPull?: boolean;
1208
1248
  code?: 'unsupported' | 'unavailable' | 'refused' | 'timeout';
1209
1249
  }>;
1210
1250
  proxyStreamWrite(id: string, dataB64: string): Promise<void>;
1211
- proxyStreamPull(id: string): Promise<{
1251
+ proxyStreamPull(id: string, maxWaitMs?: number): Promise<{
1212
1252
  chunks: string[];
1213
1253
  done: boolean;
1214
1254
  error?: string;
package/dist/runtime.js CHANGED
@@ -13729,8 +13729,25 @@ var init_ThreadStateImpl = __esm({
13729
13729
  const mustParkForApproval = await this.forwardedCallNeedsApproval(request);
13730
13730
  if (mustParkForApproval && canPark && toolCallId && typeof instance.hasBridge === "function" && await instance.hasBridge()) {
13731
13731
  await instance.dispatchClientTool(request, toolCallId, side);
13732
- const getDelivered = instance.getDeliveredClientToolResult;
13733
- 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;
13734
13751
  const graceDeadline = Date.now() + 4e3;
13735
13752
  while (Date.now() < graceDeadline) {
13736
13753
  await new Promise((r) => setTimeout(r, 150));
@@ -13820,7 +13837,14 @@ var init_ThreadStateImpl = __esm({
13820
13837
  if (typeof stub.proxyStreamOpen === "function") {
13821
13838
  const opened = await stub.proxyStreamOpen(withWake(parentId));
13822
13839
  if (opened.ok && opened.id) {
13823
- 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
+ };
13824
13848
  }
13825
13849
  if (opened.error && !opened.noClient) {
13826
13850
  return {
@@ -13838,11 +13862,15 @@ var init_ThreadStateImpl = __esm({
13838
13862
  }
13839
13863
  /**
13840
13864
  * Build a client duplex that bridges to a stream opened on an ancestor DO
13841
- * (the bridge holder) via chunk-pull RPCs. `readable` polls `proxyStreamPull`;
13842
- * `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
13843
13870
  * `proxyStreamAbort`.
13844
13871
  */
13845
- buildProxyDuplex(stub, id) {
13872
+ static PROXY_PULL_MAX_WAIT_MS = 2e4;
13873
+ buildProxyDuplex(stub, id, supportsParkedPull = false) {
13846
13874
  let settleDone;
13847
13875
  const done = new Promise((r) => settleDone = r);
13848
13876
  let closed = false;
@@ -13855,9 +13883,13 @@ var init_ThreadStateImpl = __esm({
13855
13883
  const readable = new ReadableStream({
13856
13884
  async pull(controller) {
13857
13885
  while (!closed) {
13886
+ const pullStartedAt = Date.now();
13858
13887
  let res;
13859
13888
  try {
13860
- res = await stub.proxyStreamPull(id);
13889
+ res = await stub.proxyStreamPull(
13890
+ id,
13891
+ supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
13892
+ );
13861
13893
  } catch (error) {
13862
13894
  controller.error(error instanceof Error ? error : new Error(String(error)));
13863
13895
  settle({ ok: false, error: "proxy stream pull failed" });
@@ -13872,7 +13904,14 @@ var init_ThreadStateImpl = __esm({
13872
13904
  return;
13873
13905
  }
13874
13906
  if (res.chunks.length > 0) return;
13875
- 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
+ }
13876
13915
  }
13877
13916
  },
13878
13917
  cancel() {
@@ -15991,6 +16030,9 @@ var AlarmQueue = class {
15991
16030
  options.sourceLogId ?? null
15992
16031
  );
15993
16032
  await this.rescheduleAlarm();
16033
+ if (method === "runEffect") {
16034
+ await this.emitEffectRunChange(id);
16035
+ }
15994
16036
  return id;
15995
16037
  }
15996
16038
  /**
@@ -16029,6 +16071,9 @@ var AlarmQueue = class {
16029
16071
  `UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
16030
16072
  item.id
16031
16073
  );
16074
+ if (item.method === "runEffect") {
16075
+ await this.emitEffectRunChange(item.id);
16076
+ }
16032
16077
  try {
16033
16078
  const args = JSON.parse(item.args);
16034
16079
  await this.executeMethod(item.method, args, item.id);
@@ -16082,6 +16127,7 @@ var AlarmQueue = class {
16082
16127
  now,
16083
16128
  id
16084
16129
  );
16130
+ await this.emitEffectRunChange(id);
16085
16131
  }
16086
16132
  /**
16087
16133
  * Mark an item as failed but keep it for 24 hours for debugging
@@ -16101,6 +16147,7 @@ var AlarmQueue = class {
16101
16147
  now,
16102
16148
  id
16103
16149
  );
16150
+ await this.emitEffectRunChange(id);
16104
16151
  }
16105
16152
  /**
16106
16153
  * Clean up failed items older than 24 hours
@@ -16190,6 +16237,10 @@ var AlarmQueue = class {
16190
16237
  */
16191
16238
  async recoverOrphanedProcessing(maxAttempts = 3) {
16192
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);
16193
16244
  await this.sql.exec(
16194
16245
  `UPDATE alarm_queue
16195
16246
  SET status = 'failed',
@@ -16202,6 +16253,9 @@ var AlarmQueue = class {
16202
16253
  await this.sql.exec(
16203
16254
  `UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
16204
16255
  );
16256
+ for (const id of orphanedEffectIds) {
16257
+ await this.emitEffectRunChange(id);
16258
+ }
16205
16259
  }
16206
16260
  // ============================================================
16207
16261
  // Effect-specific Methods
@@ -16380,6 +16434,7 @@ var AlarmQueue = class {
16380
16434
  const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
16381
16435
  if (canceled) {
16382
16436
  await this.rescheduleAlarm();
16437
+ await this.emitEffectRunChange(id);
16383
16438
  }
16384
16439
  return canceled;
16385
16440
  }
@@ -16454,6 +16509,7 @@ var AlarmQueue = class {
16454
16509
  if (!changed) {
16455
16510
  throw new Error(`Effect run ${item.id} is no longer pending`);
16456
16511
  }
16512
+ await this.emitEffectRunChange(item.id);
16457
16513
  try {
16458
16514
  await this.executeMethod(item.method, JSON.parse(item.args), item.id);
16459
16515
  await this.complete(item.id, item.method);
@@ -16465,6 +16521,45 @@ var AlarmQueue = class {
16465
16521
  await this.rescheduleAlarm();
16466
16522
  }
16467
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
+ }
16468
16563
  normalizeOffset(value) {
16469
16564
  const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
16470
16565
  if (!Number.isFinite(parsed)) return 0;
@@ -20935,6 +21030,25 @@ var DurableThread = class _DurableThread extends DurableObject {
20935
21030
  alarmQueueRearmed = false;
20936
21031
  currentAbortController = null;
20937
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
+ }
20938
21052
  // ── Execution watchdog state (see watchdogCheck) ─────────────────────────
20939
21053
  // In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
20940
21054
  // chunk and request attempt. Deliberately NOT persisted — after a DO restart
@@ -20959,7 +21073,11 @@ var DurableThread = class _DurableThread extends DurableObject {
20959
21073
  this.alarmQueue = new AlarmQueue(ctx, {
20960
21074
  retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
20961
21075
  isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
20962
- 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)
20963
21081
  });
20964
21082
  this.alarmQueue.setExecutor(
20965
21083
  async (method, args, itemId) => {
@@ -21518,6 +21636,7 @@ var DurableThread = class _DurableThread extends DurableObject {
21518
21636
  if (value === null || value === void 0) {
21519
21637
  await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
21520
21638
  this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
21639
+ this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
21521
21640
  return;
21522
21641
  }
21523
21642
  const serialized = this.serializeThreadValue(value);
@@ -21534,6 +21653,7 @@ var DurableThread = class _DurableThread extends DurableObject {
21534
21653
  updatedAt
21535
21654
  );
21536
21655
  this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
21656
+ this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
21537
21657
  }
21538
21658
  async listValues(options = {}) {
21539
21659
  await this.ensureMigrated();
@@ -24554,6 +24674,7 @@ ${resultContent}${attachmentSummary}`;
24554
24674
  if (isOwner) {
24555
24675
  void this.resendPendingClientCalls(server);
24556
24676
  }
24677
+ this.notifyOwnerBridgeWaiters();
24557
24678
  return new Response(null, {
24558
24679
  status: 101,
24559
24680
  webSocket: client
@@ -24589,6 +24710,7 @@ ${resultContent}${attachmentSummary}`;
24589
24710
  /** Persist + cache the execution-owner record (null releases ownership). */
24590
24711
  async setExecutionOwner(owner) {
24591
24712
  await this.setValue(EXECUTION_OWNER_KEY, owner);
24713
+ this.notifyOwnerBridgeWaiters();
24592
24714
  }
24593
24715
  /** Whether the given client id has an open bridge socket (excluding `except`).
24594
24716
  * Idle bridge heartbeats are handled by Cloudflare without waking this object,
@@ -24946,6 +25068,55 @@ ${resultContent}${attachmentSummary}`;
24946
25068
  * permission level covers the call) otherwise race the placeholder into
24947
25069
  * stored history, leaving the model blind to its own tool outcomes.
24948
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
+ }
24949
25120
  async getDeliveredClientToolResult(toolCallId) {
24950
25121
  await this.ensureMigrated();
24951
25122
  await this.ensurePendingClientTable();
@@ -25081,6 +25252,7 @@ ${resultContent}${attachmentSummary}`;
25081
25252
  `UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
25082
25253
  toolCallId
25083
25254
  );
25255
+ this.notifyDeliveredResultWaiters(toolCallId);
25084
25256
  try {
25085
25257
  const msgCursor = await this.ctx.storage.sql.exec(
25086
25258
  `SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
@@ -25099,8 +25271,24 @@ ${resultContent}${attachmentSummary}`;
25099
25271
  // they are bound to one bridge socket and never park.
25100
25272
  /** Default time to wait for the client to accept a stream open. */
25101
25273
  static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
25102
- /** Poll cadence while waiting for a disconnected owner's bridge to return. */
25103
- 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
+ }
25104
25292
  /**
25105
25293
  * The thread HAS an elected execution owner, but its bridge socket is gone
25106
25294
  * (daemon restart, network blip, DO handoff). Summon the owning machine
@@ -25109,6 +25297,11 @@ ${resultContent}${attachmentSummary}`;
25109
25297
  * Sama One turns with "workspace unavailable" whenever a slow tool/approval
25110
25298
  * outlived the socket. Threads with NO recorded owner (subagent children)
25111
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.
25112
25305
  */
25113
25306
  async awaitOwnerBridgeSocket(request) {
25114
25307
  if (request.wake?.userId) {
@@ -25116,15 +25309,25 @@ ${resultContent}${attachmentSummary}`;
25116
25309
  }
25117
25310
  const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
25118
25311
  const deadline = Date.now() + timeoutMs;
25119
- while (Date.now() < deadline) {
25120
- await new Promise(
25121
- (resolve) => setTimeout(resolve, _DurableThread.OWNER_REATTACH_POLL_MS)
25122
- );
25312
+ this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
25313
+ while (true) {
25123
25314
  const owner = await this.getExecutionOwner();
25124
25315
  const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
25125
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
+ });
25126
25330
  }
25127
- return null;
25128
25331
  }
25129
25332
  /** Open a bidirectional byte stream to the connected owner client. */
25130
25333
  async openClientStream(request) {
@@ -25268,27 +25471,38 @@ ${resultContent}${attachmentSummary}`;
25268
25471
  done: false,
25269
25472
  error: void 0,
25270
25473
  pump: Promise.resolve(),
25271
- 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
+ }
25272
25481
  };
25273
25482
  entry.pump = (async () => {
25274
25483
  try {
25275
25484
  while (true) {
25276
25485
  const { value, done } = await reader.read();
25277
25486
  if (done) break;
25278
- if (value) entry.buffer.push(value);
25487
+ if (value) {
25488
+ entry.buffer.push(value);
25489
+ entry.notify();
25490
+ }
25279
25491
  }
25280
25492
  } catch (error) {
25281
25493
  entry.error = error instanceof Error ? error.message : String(error);
25282
25494
  } finally {
25283
25495
  entry.done = true;
25496
+ entry.notify();
25284
25497
  }
25285
25498
  })();
25286
25499
  void duplex.done.then((d) => {
25287
25500
  entry.done = true;
25288
25501
  if (!d.ok && !entry.error) entry.error = d.error;
25502
+ entry.notify();
25289
25503
  });
25290
25504
  this.proxyStreams.set(id, entry);
25291
- return { ok: true, id };
25505
+ return { ok: true, id, parkedPull: true };
25292
25506
  }
25293
25507
  async proxyStreamWrite(id, dataB64) {
25294
25508
  const entry = this.proxyStreams.get(id);
@@ -25298,9 +25512,22 @@ ${resultContent}${attachmentSummary}`;
25298
25512
  } catch {
25299
25513
  }
25300
25514
  }
25301
- async proxyStreamPull(id) {
25515
+ async proxyStreamPull(id, maxWaitMs) {
25302
25516
  const entry = this.proxyStreams.get(id);
25303
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
+ }
25304
25531
  const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
25305
25532
  if (entry.done && entry.buffer.length === 0) {
25306
25533
  this.proxyStreams.delete(id);
@@ -25320,6 +25547,9 @@ ${resultContent}${attachmentSummary}`;
25320
25547
  const entry = this.proxyStreams.get(id);
25321
25548
  if (!entry) return;
25322
25549
  this.proxyStreams.delete(id);
25550
+ entry.done = true;
25551
+ if (!entry.error) entry.error = "aborted";
25552
+ entry.notify();
25323
25553
  try {
25324
25554
  entry.close("aborted");
25325
25555
  } catch {
@@ -25675,6 +25905,7 @@ ${resultContent}${attachmentSummary}`;
25675
25905
  this.broadcastEvent(type, data);
25676
25906
  }
25677
25907
  broadcastEvent(type, data) {
25908
+ if (!this.messageSockets || this.messageSockets.size === 0) return;
25678
25909
  const payload = JSON.stringify({
25679
25910
  type: "event",
25680
25911
  eventType: type,
@@ -26101,6 +26332,7 @@ ${resultContent}${attachmentSummary}`;
26101
26332
  } catch {
26102
26333
  }
26103
26334
  this.isExecuting = false;
26335
+ this.notifyExecutionIdleWaiters();
26104
26336
  this.currentAbortController = null;
26105
26337
  await this.recordRegistryActivity(threadId, false);
26106
26338
  tookOver = true;
@@ -26293,9 +26525,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26293
26525
  await this.assertNotTerminated();
26294
26526
  if (this.isExecuting) {
26295
26527
  const busyWaitBudgetMs = 3e4;
26296
- const waitStartedAt = Date.now();
26297
- while (this.isExecuting && Date.now() - waitStartedAt < busyWaitBudgetMs) {
26298
- 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
+ });
26299
26544
  }
26300
26545
  if (this.isExecuting) {
26301
26546
  throw new Error(
@@ -26434,6 +26679,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26434
26679
  return flowResult;
26435
26680
  } finally {
26436
26681
  this.isExecuting = false;
26682
+ this.notifyExecutionIdleWaiters();
26437
26683
  this.currentAbortController = null;
26438
26684
  await this.disarmExecutionWatchdog(threadId);
26439
26685
  await this.recordRegistryActivity(threadId, false);
@@ -26647,6 +26893,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26647
26893
  }, true);
26648
26894
  } finally {
26649
26895
  this.isExecuting = false;
26896
+ this.notifyExecutionIdleWaiters();
26650
26897
  this.currentAbortController = null;
26651
26898
  await this.disarmExecutionWatchdog(threadId);
26652
26899
  await this.recordRegistryActivity(threadId, false);
@@ -26773,6 +27020,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
26773
27020
  );
26774
27021
  } finally {
26775
27022
  this.isExecuting = false;
27023
+ this.notifyExecutionIdleWaiters();
26776
27024
  this.currentAbortController = null;
26777
27025
  await this.disarmExecutionWatchdog(threadId);
26778
27026
  await this.recordRegistryActivity(threadId, false);
@@ -27199,6 +27447,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
27199
27447
  const fs = this.getFileStorage();
27200
27448
  const from = normalizePath2(sourcePath);
27201
27449
  const record = await fs.move(sourcePath, destinationPath);
27450
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
27202
27451
  this.broadcastEvent("file_deleted", { path: from });
27203
27452
  this.broadcastEvent("file_created", { path: record.path, file: record });
27204
27453
  return { success: true, file: record };
@@ -27217,6 +27466,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
27217
27466
  const fs = this.getFileStorage();
27218
27467
  const from = normalizePath2(path);
27219
27468
  const record = await fs.rename(path, newName);
27469
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
27220
27470
  this.broadcastEvent("file_deleted", { path: from });
27221
27471
  this.broadcastEvent("file_created", { path: record.path, file: record });
27222
27472
  return { success: true, file: record };