@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
@@ -1154,6 +1154,46 @@ declare function requireAuth(request: Request, env: any): Promise<AuthContext |
1154
1154
  */
1155
1155
  declare function requireAdmin(request: Request, env: any): Promise<AuthContext | Response>;
1156
1156
 
1157
+ /**
1158
+ * Same-request reuse of the router's authentication and thread-authorization
1159
+ * work (AB-05, coordinator docs/plans/active/CLOUDFLARE_COST_FINDINGS.md).
1160
+ *
1161
+ * The generated runtime router authenticates every protected API request and,
1162
+ * for thread routes, loads the registry record and checks thread access —
1163
+ * then controllers historically repeated the exact same singleton RPCs
1164
+ * (validateSession/validateApiKey + getUserById, getThread), doubling
1165
+ * singleton traffic on every protected request.
1166
+ *
1167
+ * SECURITY CONTRACT (the AB-05 gate):
1168
+ * - Reuse happens ONLY within one incoming request: the router computes these
1169
+ * values per request and passes them as explicit controller-context data.
1170
+ * There is NO cross-request cache, NO module-global map, NO retained
1171
+ * tokens/cookies/identities — revocation is effective on the next request
1172
+ * because the next request re-authenticates from scratch.
1173
+ * - Thread authorization is bound to the EXACT thread id the router
1174
+ * authorized. A controller asking about any other id (including nested
1175
+ * route params also named `id`) gets null and falls back to its own
1176
+ * lookup + access check.
1177
+ * - Permission-specific checks stay at the operation boundary: reusing the
1178
+ * authenticated identity never implies a permission; helpers re-evaluate
1179
+ * `hasPermission`/role against the reused context on every call.
1180
+ * - Controllers invoked WITHOUT router context (direct tests, standalone
1181
+ * invocation, trusted-local public routes) execute their historical
1182
+ * authentication path unchanged.
1183
+ */
1184
+
1185
+ /** The router's thread-authorization result, bound to one exact thread id. */
1186
+ interface RouterThreadAccess {
1187
+ /** The thread id (as matched from the route) the router authorized. */
1188
+ threadId: string;
1189
+ /**
1190
+ * The registry record the router loaded while authorizing. Same-request
1191
+ * read-only reuse only — a controller that mutates thread metadata must
1192
+ * re-read after its mutation rather than trusting this snapshot.
1193
+ */
1194
+ thread: Record<string, unknown>;
1195
+ }
1196
+
1157
1197
  /**
1158
1198
  * Router and endpoint definition module for AgentBuilder.
1159
1199
  *
@@ -1740,6 +1780,12 @@ interface BuilderControllerContext<Env extends ThreadEnv = ThreadEnv> extends Om
1740
1780
  registry?: NamespacedRegistry;
1741
1781
  /** Authenticated caller, populated by the generated runtime router. */
1742
1782
  auth?: AuthContext | null;
1783
+ /**
1784
+ * The router's thread-authorization result for this request, bound to the
1785
+ * exact matched thread id (AB-05 same-request reuse). Populated only after
1786
+ * the router's own getThread + canAccessThread checks passed.
1787
+ */
1788
+ threadAccess?: RouterThreadAccess | null;
1743
1789
  }
1744
1790
  /**
1745
1791
  * Builder-specific controller type with typed env.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ModelDefinition, AgentDefinition } from './runtime.js';
2
2
  export { AgentBuilderEnv, AgentBuilderEvent, BroadcastOptions, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, FlowStateWithSdk, HubClientInfo, ImageContextConfig, InjectMessageOptions, LLMProviderInterface, NamespaceResolutionError, PromptContent, PromptDefinition, PromptIncludePart, PromptPart, PromptToolConfig, Provider, ProviderContentPart, ProviderFilePart, ProviderImagePart, ProviderRegistry, ProviderTextPart, SessionToolBinding, SessionToolConfig, SideConfig, SubagentToolConfig, SubpromptConfig, ThreadEventPayload, ThreadRegistryEntry, ThreadWatchdog, ThreadWatchdogState, ToolConfig, UpdateThreadParams, User, WatchdogCheckin, WatchdogProbeResult, buildImageDescription, canAccessThread, cat, createNamespaceContext, emitThreadEvent, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isTrustedLocalDevelopmentRequest, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, stat, tail, unlink, updateThread, writeFile, writeImage } from './runtime.js';
3
3
  export { AgentPluginOptions, agentbuilder, builder } from './plugin.js';
4
- export { v as Agent, H as AttachmentRef, i as AuthContext, A as AuthUser, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, t as ClientDuplex, u as ClientForwarding, p as ClientStreamRequest, q as ClientStreamResponse, n as ClientToolRequest, o as ClientToolResponse, B as Controller, a as ControllerContext, E as Env, G as FileRecord, K as FileStats, z as FlowResult, F as FlowState, J as GrepResult, O as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, Q as MessageContent, P as MultimodalContent, R as RequestContext, S as StorageBackend, D as TelemetryEvent, N as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, w as ThreadInstance, x as ThreadMetadata, T as ToolCall, y as ToolResult, 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';
4
+ export { v as Agent, H as AttachmentRef, i as AuthContext, A as AuthUser, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, t as ClientDuplex, u as ClientForwarding, p as ClientStreamRequest, q as ClientStreamResponse, n as ClientToolRequest, o as ClientToolResponse, B as Controller, a as ControllerContext, E as Env, G as FileRecord, K as FileStats, z as FlowResult, F as FlowState, J as GrepResult, O as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, Q as MessageContent, P as MultimodalContent, R as RequestContext, S as StorageBackend, D as TelemetryEvent, N as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, w as ThreadInstance, x as ThreadMetadata, T as ToolCall, y as ToolResult, 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';
5
5
  export { AgentType, DefinitionLoader, GlobalNamespaceContext, HookSignatures, ImageContent, ModelCapabilities, ModelProvider, NamespaceContext, PackageSignature, PackedExports, PackedMeta, PackedMetadata, PackedNamespaceContext, PromptInput, PromptTextPart, ProviderAssistantMessage, ProviderConfigSlot, ProviderConfigSlots, ProviderConfigValueSource, ProviderDefinition, ProviderError, ProviderErrorCode, ProviderFactory, ProviderFactoryConfig, ProviderFinishReason, ProviderGeneratedImage, ProviderMessage, ProviderMessageContent, ProviderMethodOverrides, ModelCapabilities as ProviderModelCapabilities, ProviderOverride, ProviderOverrideContext, ProviderReasoningDetail, ProviderRequest, ProviderResponse, ProviderStreamChunk, ProviderSyncOverride, ProviderSystemMessage, ProviderTool, ProviderToolCallPart, ProviderToolMessage, ProviderToolResultContent, ProviderUsage, ProviderUserMessage, ReasoningConfig, StructuredPrompt, TextContent, Tool, ToolArgs, ToolArgsNode, ToolArgsRawShape, ToolContent, belongsToPackage, defineAgent, defineHook, defineModel, definePrompt, defineProvider, defineTool, isPacked, isVisibleInNamespace, mapReasoningLevel, providerConst, providerEnv, providerValue } from '@standardagents/spec';
6
6
  export { C as ConstituentItem, D as DiscoveredPackage, N as NamespacedRegistry, P as PackingAnalysis, a as PackingOptions, b as PackingResult, c as UnpackOptions, d as UnpackResult } from './types-Bpe7IANZ.js';
7
7
  export { D as DiscoveryConfig, P as PackageDiscoveryService, d as discoverPackages } from './discovery-DiMJWisl.js';
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() {
@@ -22245,6 +22284,10 @@ ${packedThreadRouteCode}
22245
22284
  const isApiRoute = routePath.startsWith('/api/');
22246
22285
 
22247
22286
  let authContext = null;
22287
+ // The router's thread-authorization result, bound to the exact matched
22288
+ // thread id, for same-request reuse by controllers (AB-05). Never cached
22289
+ // across requests.
22290
+ let threadAccess = null;
22248
22291
 
22249
22292
  // Require authentication for all API routes except public ones
22250
22293
  if (isApiRoute && !publicRoute) {
@@ -22277,6 +22320,7 @@ ${packedThreadRouteCode}
22277
22320
  if (!canAccessThread(authContext, thread)) {
22278
22321
  return addCorsHeaders(Response.json({ error: "Forbidden: You don't have access to this thread" }, { status: 403 }));
22279
22322
  }
22323
+ threadAccess = { threadId, thread };
22280
22324
  }
22281
22325
  }
22282
22326
  }
@@ -22297,6 +22341,7 @@ ${packedThreadRouteCode}
22297
22341
  config,
22298
22342
  tools,
22299
22343
  auth: authContext, // Add auth context to controller context
22344
+ threadAccess, // Router's thread authorization, bound to the exact id
22300
22345
  };
22301
22346
  const result = await controller(context);
22302
22347
 
@@ -24400,6 +24445,9 @@ var AlarmQueue = class {
24400
24445
  options.sourceLogId ?? null
24401
24446
  );
24402
24447
  await this.rescheduleAlarm();
24448
+ if (method === "runEffect") {
24449
+ await this.emitEffectRunChange(id);
24450
+ }
24403
24451
  return id;
24404
24452
  }
24405
24453
  /**
@@ -24438,6 +24486,9 @@ var AlarmQueue = class {
24438
24486
  `UPDATE alarm_queue SET status = 'processing', attempts = attempts + 1 WHERE id = ?`,
24439
24487
  item.id
24440
24488
  );
24489
+ if (item.method === "runEffect") {
24490
+ await this.emitEffectRunChange(item.id);
24491
+ }
24441
24492
  try {
24442
24493
  const args = JSON.parse(item.args);
24443
24494
  await this.executeMethod(item.method, args, item.id);
@@ -24491,6 +24542,7 @@ var AlarmQueue = class {
24491
24542
  now,
24492
24543
  id
24493
24544
  );
24545
+ await this.emitEffectRunChange(id);
24494
24546
  }
24495
24547
  /**
24496
24548
  * Mark an item as failed but keep it for 24 hours for debugging
@@ -24510,6 +24562,7 @@ var AlarmQueue = class {
24510
24562
  now,
24511
24563
  id
24512
24564
  );
24565
+ await this.emitEffectRunChange(id);
24513
24566
  }
24514
24567
  /**
24515
24568
  * Clean up failed items older than 24 hours
@@ -24599,6 +24652,10 @@ var AlarmQueue = class {
24599
24652
  */
24600
24653
  async recoverOrphanedProcessing(maxAttempts = 3) {
24601
24654
  const now = Date.now() * 1e3;
24655
+ const orphanedEffectsCursor = await this.sql.exec(
24656
+ `SELECT id FROM alarm_queue WHERE status = 'processing' AND method = 'runEffect'`
24657
+ );
24658
+ const orphanedEffectIds = orphanedEffectsCursor.toArray().map((row) => row.id);
24602
24659
  await this.sql.exec(
24603
24660
  `UPDATE alarm_queue
24604
24661
  SET status = 'failed',
@@ -24611,6 +24668,9 @@ var AlarmQueue = class {
24611
24668
  await this.sql.exec(
24612
24669
  `UPDATE alarm_queue SET status = 'pending' WHERE status = 'processing'`
24613
24670
  );
24671
+ for (const id of orphanedEffectIds) {
24672
+ await this.emitEffectRunChange(id);
24673
+ }
24614
24674
  }
24615
24675
  // ============================================================
24616
24676
  // Effect-specific Methods
@@ -24789,6 +24849,7 @@ var AlarmQueue = class {
24789
24849
  const canceled = changesRows.length > 0 && changesRows[0].changes > 0;
24790
24850
  if (canceled) {
24791
24851
  await this.rescheduleAlarm();
24852
+ await this.emitEffectRunChange(id);
24792
24853
  }
24793
24854
  return canceled;
24794
24855
  }
@@ -24863,6 +24924,7 @@ var AlarmQueue = class {
24863
24924
  if (!changed) {
24864
24925
  throw new Error(`Effect run ${item.id} is no longer pending`);
24865
24926
  }
24927
+ await this.emitEffectRunChange(item.id);
24866
24928
  try {
24867
24929
  await this.executeMethod(item.method, JSON.parse(item.args), item.id);
24868
24930
  await this.complete(item.id, item.method);
@@ -24874,6 +24936,45 @@ var AlarmQueue = class {
24874
24936
  await this.rescheduleAlarm();
24875
24937
  }
24876
24938
  }
24939
+ /**
24940
+ * Read one effect row and hand its bounded projection (no args payload) to
24941
+ * the change listener. Self-filtering: operational (non-effect) rows and
24942
+ * already-deleted rows emit nothing, and a listener failure must never
24943
+ * disturb queue processing.
24944
+ */
24945
+ async emitEffectRunChange(id) {
24946
+ const listener = this.options.onEffectRunChanged;
24947
+ if (!listener) return;
24948
+ try {
24949
+ const cursor = await this.sql.exec(
24950
+ `
24951
+ SELECT id, args, scheduled_at, created_at, status, attempts, error, completed_at, manual_run, manual_run_at, replayed_from
24952
+ FROM alarm_queue
24953
+ WHERE id = ? AND method = 'runEffect'
24954
+ LIMIT 1
24955
+ `,
24956
+ id
24957
+ );
24958
+ const row = cursor.toArray()[0];
24959
+ if (!row) return;
24960
+ const { effectName } = this.parseEffectPayload(row.args);
24961
+ listener({
24962
+ id: row.id,
24963
+ name: effectName,
24964
+ scheduledAt: row.scheduled_at,
24965
+ createdAt: row.created_at,
24966
+ status: row.status,
24967
+ attempts: row.attempts,
24968
+ error: row.error,
24969
+ completedAt: row.completed_at,
24970
+ manualRun: row.manual_run === 1,
24971
+ manualRunAt: row.manual_run_at,
24972
+ replayedFrom: row.replayed_from
24973
+ });
24974
+ } catch (error) {
24975
+ console.error(`[AlarmQueue] Failed to emit effect run change for ${id}:`, error);
24976
+ }
24977
+ }
24877
24978
  normalizeOffset(value) {
24878
24979
  const parsed = typeof value === "number" ? Math.trunc(value) : Number.NaN;
24879
24980
  if (!Number.isFinite(parsed)) return 0;
@@ -29344,6 +29445,25 @@ var DurableThread = class _DurableThread extends DurableObject {
29344
29445
  alarmQueueRearmed = false;
29345
29446
  currentAbortController = null;
29346
29447
  isExecuting = false;
29448
+ /**
29449
+ * In-memory waiters parked until the running flow finishes (AB-09): the
29450
+ * executeFlow busy-guard awaits this signal instead of re-checking
29451
+ * isExecuting every 500ms. Every isExecuting=false transition (execution
29452
+ * finallys and the watchdog takeover) notifies. In-memory only — a DO
29453
+ * restart drops waiters together with the busy caller.
29454
+ */
29455
+ executionIdleWaiters = /* @__PURE__ */ new Set();
29456
+ notifyExecutionIdleWaiters() {
29457
+ if (!this.executionIdleWaiters || this.executionIdleWaiters.size === 0) return;
29458
+ const waiters = Array.from(this.executionIdleWaiters);
29459
+ this.executionIdleWaiters.clear();
29460
+ for (const wake of waiters) {
29461
+ try {
29462
+ wake();
29463
+ } catch {
29464
+ }
29465
+ }
29466
+ }
29347
29467
  // ── Execution watchdog state (see watchdogCheck) ─────────────────────────
29348
29468
  // In-memory heartbeat: bumped by noteExecutionProgress() on every streamed
29349
29469
  // chunk and request attempt. Deliberately NOT persisted — after a DO restart
@@ -29368,7 +29488,11 @@ var DurableThread = class _DurableThread extends DurableObject {
29368
29488
  this.alarmQueue = new AlarmQueue(ctx, {
29369
29489
  retentionEnabled: env.AGENT_BUILDER_ALARM_RETENTION_ENABLED !== "false",
29370
29490
  isForensicHold: async () => (await this.runtimeSafety.getStatus()).forensicHold,
29371
- invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 }
29491
+ invocationSqlLimits: { rowsRead: 1e6, rowsWritten: 1e4 },
29492
+ // Effect lifecycle transitions broadcast on the thread event channel so
29493
+ // inspectors stay live from the already-open message socket instead of
29494
+ // polling the effects list.
29495
+ onEffectRunChanged: (change) => this.broadcastEvent("effect_changed", change)
29372
29496
  });
29373
29497
  this.alarmQueue.setExecutor(
29374
29498
  async (method, args, itemId) => {
@@ -29927,6 +30051,7 @@ var DurableThread = class _DurableThread extends DurableObject {
29927
30051
  if (value === null || value === void 0) {
29928
30052
  await this.ctx.storage.sql.exec(`DELETE FROM thread_values WHERE key = ?`, key);
29929
30053
  this.broadcastLog({ type: "kv_data", key, deleted: true, updated_at: updatedAt });
30054
+ this.broadcastEvent("kv_changed", { key, deleted: true, updated_at: updatedAt });
29930
30055
  return;
29931
30056
  }
29932
30057
  const serialized = this.serializeThreadValue(value);
@@ -29943,6 +30068,7 @@ var DurableThread = class _DurableThread extends DurableObject {
29943
30068
  updatedAt
29944
30069
  );
29945
30070
  this.broadcastLog({ type: "kv_data", key, deleted: false, updated_at: updatedAt });
30071
+ this.broadcastEvent("kv_changed", { key, deleted: false, updated_at: updatedAt });
29946
30072
  }
29947
30073
  async listValues(options = {}) {
29948
30074
  await this.ensureMigrated();
@@ -32963,6 +33089,7 @@ ${resultContent}${attachmentSummary}`;
32963
33089
  if (isOwner) {
32964
33090
  void this.resendPendingClientCalls(server);
32965
33091
  }
33092
+ this.notifyOwnerBridgeWaiters();
32966
33093
  return new Response(null, {
32967
33094
  status: 101,
32968
33095
  webSocket: client
@@ -32998,6 +33125,7 @@ ${resultContent}${attachmentSummary}`;
32998
33125
  /** Persist + cache the execution-owner record (null releases ownership). */
32999
33126
  async setExecutionOwner(owner) {
33000
33127
  await this.setValue(EXECUTION_OWNER_KEY, owner);
33128
+ this.notifyOwnerBridgeWaiters();
33001
33129
  }
33002
33130
  /** Whether the given client id has an open bridge socket (excluding `except`).
33003
33131
  * Idle bridge heartbeats are handled by Cloudflare without waking this object,
@@ -33355,6 +33483,55 @@ ${resultContent}${attachmentSummary}`;
33355
33483
  * permission level covers the call) otherwise race the placeholder into
33356
33484
  * stored history, leaving the model blind to its own tool outcomes.
33357
33485
  */
33486
+ /**
33487
+ * In-memory waiters parked on a forwarded call's result delivery (AB-09).
33488
+ * `deliverClientToolResult` resolves them so the approval grace-wait reads
33489
+ * delivered state exactly once instead of re-polling it every 150ms.
33490
+ * In-memory only: a DO restart drops waiters along with the caller that
33491
+ * registered them, and the durable park path still resumes the turn.
33492
+ */
33493
+ deliveredResultWaiters = /* @__PURE__ */ new Map();
33494
+ notifyDeliveredResultWaiters(toolCallId) {
33495
+ const waiters = this.deliveredResultWaiters?.get(toolCallId);
33496
+ if (!waiters) return;
33497
+ this.deliveredResultWaiters.delete(toolCallId);
33498
+ for (const wake of waiters) {
33499
+ try {
33500
+ wake();
33501
+ } catch {
33502
+ }
33503
+ }
33504
+ }
33505
+ /**
33506
+ * Await a parked forwarded call's delivered result: one immediate read,
33507
+ * then a single parked wait resolved by result delivery, bounded by one
33508
+ * hard deadline, then one final read. Replaces the caller-side 150ms
33509
+ * re-poll of {@link getDeliveredClientToolResult}.
33510
+ */
33511
+ async awaitDeliveredClientToolResult(toolCallId, maxWaitMs) {
33512
+ const immediate = await this.getDeliveredClientToolResult(toolCallId);
33513
+ if (immediate) return immediate;
33514
+ if (!(maxWaitMs > 0)) return null;
33515
+ await new Promise((resolve3) => {
33516
+ let waiters = this.deliveredResultWaiters.get(toolCallId);
33517
+ if (!waiters) {
33518
+ waiters = /* @__PURE__ */ new Set();
33519
+ this.deliveredResultWaiters.set(toolCallId, waiters);
33520
+ }
33521
+ const registered = waiters;
33522
+ const deadline = setTimeout(() => {
33523
+ registered.delete(wake);
33524
+ if (registered.size === 0) this.deliveredResultWaiters.delete(toolCallId);
33525
+ resolve3();
33526
+ }, maxWaitMs);
33527
+ const wake = () => {
33528
+ clearTimeout(deadline);
33529
+ resolve3();
33530
+ };
33531
+ registered.add(wake);
33532
+ });
33533
+ return this.getDeliveredClientToolResult(toolCallId);
33534
+ }
33358
33535
  async getDeliveredClientToolResult(toolCallId) {
33359
33536
  await this.ensureMigrated();
33360
33537
  await this.ensurePendingClientTable();
@@ -33490,6 +33667,7 @@ ${resultContent}${attachmentSummary}`;
33490
33667
  `UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
33491
33668
  toolCallId
33492
33669
  );
33670
+ this.notifyDeliveredResultWaiters(toolCallId);
33493
33671
  try {
33494
33672
  const msgCursor = await this.ctx.storage.sql.exec(
33495
33673
  `SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
@@ -33508,8 +33686,24 @@ ${resultContent}${attachmentSummary}`;
33508
33686
  // they are bound to one bridge socket and never park.
33509
33687
  /** Default time to wait for the client to accept a stream open. */
33510
33688
  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;
33689
+ /**
33690
+ * In-memory waiters parked on the owner's bridge socket returning (AB-09).
33691
+ * Resolved by bridge attach and by execution-owner changes; each waiter
33692
+ * re-evaluates against the CURRENT owner on wake. In-memory only: a DO
33693
+ * restart drops waiters with the stream-open call that registered them.
33694
+ */
33695
+ ownerBridgeWaiters = /* @__PURE__ */ new Set();
33696
+ notifyOwnerBridgeWaiters() {
33697
+ if (!this.ownerBridgeWaiters || this.ownerBridgeWaiters.size === 0) return;
33698
+ const waiters = Array.from(this.ownerBridgeWaiters);
33699
+ this.ownerBridgeWaiters.clear();
33700
+ for (const wake of waiters) {
33701
+ try {
33702
+ wake();
33703
+ } catch {
33704
+ }
33705
+ }
33706
+ }
33513
33707
  /**
33514
33708
  * The thread HAS an elected execution owner, but its bridge socket is gone
33515
33709
  * (daemon restart, network blip, DO handoff). Summon the owning machine
@@ -33518,6 +33712,11 @@ ${resultContent}${attachmentSummary}`;
33518
33712
  * Sama One turns with "workspace unavailable" whenever a slow tool/approval
33519
33713
  * outlived the socket. Threads with NO recorded owner (subagent children)
33520
33714
  * still fail fast so ancestor walk-up routing stays immediate.
33715
+ *
33716
+ * The wait parks on bridge-attach/owner-change signals under ONE hard
33717
+ * deadline — no re-poll cadence. Each wake re-reads the owner because the
33718
+ * returning daemon may reclaim with a new generation, and the socket must
33719
+ * match the CURRENT owner.
33521
33720
  */
33522
33721
  async awaitOwnerBridgeSocket(request) {
33523
33722
  if (request.wake?.userId) {
@@ -33525,15 +33724,25 @@ ${resultContent}${attachmentSummary}`;
33525
33724
  }
33526
33725
  const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
33527
33726
  const deadline = Date.now() + timeoutMs;
33528
- while (Date.now() < deadline) {
33529
- await new Promise(
33530
- (resolve3) => setTimeout(resolve3, _DurableThread.OWNER_REATTACH_POLL_MS)
33531
- );
33727
+ this.ownerBridgeWaiters ??= /* @__PURE__ */ new Set();
33728
+ while (true) {
33532
33729
  const owner = await this.getExecutionOwner();
33533
33730
  const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
33534
33731
  if (socket) return socket;
33732
+ const remainingMs = deadline - Date.now();
33733
+ if (remainingMs <= 0) return null;
33734
+ await new Promise((resolve3) => {
33735
+ const timer = setTimeout(() => {
33736
+ this.ownerBridgeWaiters.delete(wake);
33737
+ resolve3();
33738
+ }, remainingMs);
33739
+ const wake = () => {
33740
+ clearTimeout(timer);
33741
+ resolve3();
33742
+ };
33743
+ this.ownerBridgeWaiters.add(wake);
33744
+ });
33535
33745
  }
33536
- return null;
33537
33746
  }
33538
33747
  /** Open a bidirectional byte stream to the connected owner client. */
33539
33748
  async openClientStream(request) {
@@ -33677,27 +33886,38 @@ ${resultContent}${attachmentSummary}`;
33677
33886
  done: false,
33678
33887
  error: void 0,
33679
33888
  pump: Promise.resolve(),
33680
- close: (error) => duplex.close(error)
33889
+ close: (error) => duplex.close(error),
33890
+ waiter: null,
33891
+ notify: () => {
33892
+ const waiter = entry.waiter;
33893
+ entry.waiter = null;
33894
+ waiter?.();
33895
+ }
33681
33896
  };
33682
33897
  entry.pump = (async () => {
33683
33898
  try {
33684
33899
  while (true) {
33685
33900
  const { value, done } = await reader.read();
33686
33901
  if (done) break;
33687
- if (value) entry.buffer.push(value);
33902
+ if (value) {
33903
+ entry.buffer.push(value);
33904
+ entry.notify();
33905
+ }
33688
33906
  }
33689
33907
  } catch (error) {
33690
33908
  entry.error = error instanceof Error ? error.message : String(error);
33691
33909
  } finally {
33692
33910
  entry.done = true;
33911
+ entry.notify();
33693
33912
  }
33694
33913
  })();
33695
33914
  void duplex.done.then((d) => {
33696
33915
  entry.done = true;
33697
33916
  if (!d.ok && !entry.error) entry.error = d.error;
33917
+ entry.notify();
33698
33918
  });
33699
33919
  this.proxyStreams.set(id, entry);
33700
- return { ok: true, id };
33920
+ return { ok: true, id, parkedPull: true };
33701
33921
  }
33702
33922
  async proxyStreamWrite(id, dataB64) {
33703
33923
  const entry = this.proxyStreams.get(id);
@@ -33707,9 +33927,22 @@ ${resultContent}${attachmentSummary}`;
33707
33927
  } catch {
33708
33928
  }
33709
33929
  }
33710
- async proxyStreamPull(id) {
33930
+ async proxyStreamPull(id, maxWaitMs) {
33711
33931
  const entry = this.proxyStreams.get(id);
33712
33932
  if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
33933
+ if (entry.buffer.length === 0 && !entry.done) {
33934
+ await new Promise((resolve3) => {
33935
+ const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
33936
+ if (entry.waiter === wake) entry.waiter = null;
33937
+ resolve3();
33938
+ }, maxWaitMs) : null;
33939
+ const wake = () => {
33940
+ if (deadline) clearTimeout(deadline);
33941
+ resolve3();
33942
+ };
33943
+ entry.waiter = wake;
33944
+ });
33945
+ }
33713
33946
  const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
33714
33947
  if (entry.done && entry.buffer.length === 0) {
33715
33948
  this.proxyStreams.delete(id);
@@ -33729,6 +33962,9 @@ ${resultContent}${attachmentSummary}`;
33729
33962
  const entry = this.proxyStreams.get(id);
33730
33963
  if (!entry) return;
33731
33964
  this.proxyStreams.delete(id);
33965
+ entry.done = true;
33966
+ if (!entry.error) entry.error = "aborted";
33967
+ entry.notify();
33732
33968
  try {
33733
33969
  entry.close("aborted");
33734
33970
  } catch {
@@ -34084,6 +34320,7 @@ ${resultContent}${attachmentSummary}`;
34084
34320
  this.broadcastEvent(type, data);
34085
34321
  }
34086
34322
  broadcastEvent(type, data) {
34323
+ if (!this.messageSockets || this.messageSockets.size === 0) return;
34087
34324
  const payload = JSON.stringify({
34088
34325
  type: "event",
34089
34326
  eventType: type,
@@ -34510,6 +34747,7 @@ ${resultContent}${attachmentSummary}`;
34510
34747
  } catch {
34511
34748
  }
34512
34749
  this.isExecuting = false;
34750
+ this.notifyExecutionIdleWaiters();
34513
34751
  this.currentAbortController = null;
34514
34752
  await this.recordRegistryActivity(threadId, false);
34515
34753
  tookOver = true;
@@ -34702,9 +34940,22 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
34702
34940
  await this.assertNotTerminated();
34703
34941
  if (this.isExecuting) {
34704
34942
  const busyWaitBudgetMs = 3e4;
34705
- const waitStartedAt = Date.now();
34706
- while (this.isExecuting && Date.now() - waitStartedAt < busyWaitBudgetMs) {
34707
- await new Promise((resolve3) => setTimeout(resolve3, 500));
34943
+ const deadline = Date.now() + busyWaitBudgetMs;
34944
+ this.executionIdleWaiters ??= /* @__PURE__ */ new Set();
34945
+ while (this.isExecuting) {
34946
+ const remainingMs = deadline - Date.now();
34947
+ if (remainingMs <= 0) break;
34948
+ await new Promise((resolve3) => {
34949
+ const timer = setTimeout(() => {
34950
+ this.executionIdleWaiters.delete(wake);
34951
+ resolve3();
34952
+ }, remainingMs);
34953
+ const wake = () => {
34954
+ clearTimeout(timer);
34955
+ resolve3();
34956
+ };
34957
+ this.executionIdleWaiters.add(wake);
34958
+ });
34708
34959
  }
34709
34960
  if (this.isExecuting) {
34710
34961
  throw new Error(
@@ -34843,6 +35094,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
34843
35094
  return flowResult;
34844
35095
  } finally {
34845
35096
  this.isExecuting = false;
35097
+ this.notifyExecutionIdleWaiters();
34846
35098
  this.currentAbortController = null;
34847
35099
  await this.disarmExecutionWatchdog(threadId);
34848
35100
  await this.recordRegistryActivity(threadId, false);
@@ -35056,6 +35308,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35056
35308
  }, true);
35057
35309
  } finally {
35058
35310
  this.isExecuting = false;
35311
+ this.notifyExecutionIdleWaiters();
35059
35312
  this.currentAbortController = null;
35060
35313
  await this.disarmExecutionWatchdog(threadId);
35061
35314
  await this.recordRegistryActivity(threadId, false);
@@ -35182,6 +35435,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35182
35435
  );
35183
35436
  } finally {
35184
35437
  this.isExecuting = false;
35438
+ this.notifyExecutionIdleWaiters();
35185
35439
  this.currentAbortController = null;
35186
35440
  await this.disarmExecutionWatchdog(threadId);
35187
35441
  await this.recordRegistryActivity(threadId, false);
@@ -35608,6 +35862,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35608
35862
  const fs19 = this.getFileStorage();
35609
35863
  const from = normalizePath2(sourcePath);
35610
35864
  const record = await fs19.move(sourcePath, destinationPath);
35865
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
35611
35866
  this.broadcastEvent("file_deleted", { path: from });
35612
35867
  this.broadcastEvent("file_created", { path: record.path, file: record });
35613
35868
  return { success: true, file: record };
@@ -35626,6 +35881,7 @@ Its execution was lost (the executor restarted or hung) and automatic recovery w
35626
35881
  const fs19 = this.getFileStorage();
35627
35882
  const from = normalizePath2(path21);
35628
35883
  const record = await fs19.rename(path21, newName);
35884
+ this.broadcastEvent("file_moved", { from, path: record.path, file: record });
35629
35885
  this.broadcastEvent("file_deleted", { path: from });
35630
35886
  this.broadcastEvent("file_created", { path: record.path, file: record });
35631
35887
  return { success: true, file: record };