@standardagents/builder 0.29.0 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { e as ThreadEnv, A as Agent, r as ThreadMetadata, t as ToolResult, M as Message, k as ClientToolRequest, l as ClientToolResponse, m as ClientStreamRequest, n as ClientStreamResponse, w as FileRecord, F as FlowState, y as FileStats, G as GrepResult, x as AttachmentRef, J as MessageContent, E as Env, q as ThreadInstance } from './index-Cp_Zcrhy.js';
2
- export { f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, B as Controller, a as ControllerContext, u as FlowResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as callClient, h as callClientStream, c as createThreadEndpointHandler, j as decodeStreamChunk, d as defineController, i as encodeStreamChunk, s as supportsClientForwarding } from './index-Cp_Zcrhy.js';
1
+ import { e as ThreadEnv, A as Agent, r as ThreadMetadata, t as ToolResult, M as Message, k as ClientToolRequest, l as ClientToolResponse, m as ClientStreamRequest, n as ClientStreamResponse, w as FileRecord, F as FlowState, y as FileStats, G as GrepResult, x as AttachmentRef, J as MessageContent, E as Env, q as ThreadInstance } from './index-CcnGEkPh.js';
2
+ export { f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, B as Controller, a as ControllerContext, u as FlowResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, T as ToolCall, g as callClient, h as callClientStream, c as createThreadEndpointHandler, j as decodeStreamChunk, d as defineController, i as encodeStreamChunk, s as supportsClientForwarding } from './index-CcnGEkPh.js';
3
3
  import { CodeExecutionOptions, CodeExecutionResult, SubagentRegistryEntry, InjectMessageInput, QueueMessageInput, 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';
@@ -1043,7 +1043,10 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1043
1043
  * always produce a normal tool error (no client connected, timeout, or user
1044
1044
  * denial) for the model to reason about.
1045
1045
  */
1046
- callClientTool(request: ClientToolRequest): Promise<ClientToolResponse>;
1046
+ callClientTool(request: ClientToolRequest, durability?: {
1047
+ toolCallId: string;
1048
+ side: "a" | "b";
1049
+ }): Promise<ClientToolResponse>;
1047
1050
  /** Settle a pending forwarded call exactly once, clearing its timers. */
1048
1051
  private settleClientCall;
1049
1052
  /** Close and forget a bridge socket we've deemed dead, failing remaining calls. */
@@ -1112,6 +1115,18 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1112
1115
  } | null>;
1113
1116
  /** Default time to wait for the client to accept a stream open. */
1114
1117
  private static readonly CLIENT_STREAM_OPEN_TIMEOUT_MS;
1118
+ /** Poll cadence while waiting for a disconnected owner's bridge to return. */
1119
+ private static readonly OWNER_REATTACH_POLL_MS;
1120
+ /**
1121
+ * The thread HAS an elected execution owner, but its bridge socket is gone
1122
+ * (daemon restart, network blip, DO handoff). Summon the owning machine
1123
+ * through the per-user hub and wait for a bridge to re-attach instead of
1124
+ * failing the stream open instantly — that instant failure is what ended
1125
+ * Sama One turns with "workspace unavailable" whenever a slow tool/approval
1126
+ * outlived the socket. Threads with NO recorded owner (subagent children)
1127
+ * still fail fast so ancestor walk-up routing stays immediate.
1128
+ */
1129
+ private awaitOwnerBridgeSocket;
1115
1130
  /** Open a bidirectional byte stream to the connected owner client. */
1116
1131
  openClientStream(request: ClientStreamRequest): Promise<ClientStreamResponse>;
1117
1132
  private proxyStreams;
@@ -1147,6 +1162,9 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1147
1162
  * instead of a silent hang.
1148
1163
  */
1149
1164
  notifyClientWaiting(toolCallId: string, threadId: string, userId: string | null): Promise<void>;
1165
+ /** Ask the user's hub to summon the owning machine's daemon to a thread.
1166
+ * Best-effort: wake failure must never fail the caller's own path. */
1167
+ private requestHubWake;
1150
1168
  /**
1151
1169
  * Park TTL expired: if the call is still pending, settle it as a durable,
1152
1170
  * VISIBLE failure — rewrite the waiting placeholder, mark the pending row
package/dist/runtime.js CHANGED
@@ -13536,7 +13536,10 @@ var init_ThreadStateImpl = __esm({
13536
13536
  data: { deferred: true, toolCallId }
13537
13537
  };
13538
13538
  }
13539
- const local = await this._threadInstance.callClientTool(request);
13539
+ const local = await this._threadInstance.callClientTool(
13540
+ request,
13541
+ canPark && toolCallId ? { toolCallId, side } : void 0
13542
+ );
13540
13543
  if (local.ok || !this.isNoClient(local)) return local;
13541
13544
  const env = this._threadInstance.env;
13542
13545
  if (env?.AGENT_BUILDER_THREAD) {
@@ -13583,7 +13586,9 @@ var init_ThreadStateImpl = __esm({
13583
13586
  error: "This thread runtime cannot open client streams. Client streaming requires the DurableThread bridge."
13584
13587
  };
13585
13588
  }
13586
- const local = await instance.openClientStream(request);
13589
+ const wakeUserId = this._metadata.user_id ?? null;
13590
+ const withWake = (threadId) => wakeUserId ? { ...request, wake: { threadId, userId: wakeUserId } } : request;
13591
+ const local = await instance.openClientStream(withWake(this._metadata.id));
13587
13592
  if (local.ok || !this.isNoClient(local)) return local;
13588
13593
  const env = this._threadInstance.env;
13589
13594
  if (!env?.AGENT_BUILDER_THREAD) return local;
@@ -13594,7 +13599,7 @@ var init_ThreadStateImpl = __esm({
13594
13599
  try {
13595
13600
  const stub = env.AGENT_BUILDER_THREAD.get(env.AGENT_BUILDER_THREAD.idFromName(parentId));
13596
13601
  if (typeof stub.proxyStreamOpen === "function") {
13597
- const opened = await stub.proxyStreamOpen(request);
13602
+ const opened = await stub.proxyStreamOpen(withWake(parentId));
13598
13603
  if (opened.ok && opened.id) {
13599
13604
  return { ok: true, duplex: this.buildProxyDuplex(stub, opened.id) };
13600
13605
  }
@@ -19936,7 +19941,7 @@ ${resultContent}${attachmentSummary}`;
19936
19941
  }
19937
19942
  /** Whether any forwarded call is mid-flight (blocking await or parked). */
19938
19943
  async hasInFlightClientCalls() {
19939
- if (this.pendingClientCalls.size > 0) return true;
19944
+ if ((this.pendingClientCalls?.size ?? 0) > 0) return true;
19940
19945
  await this.ensureMigrated();
19941
19946
  await this.ensurePendingClientTable();
19942
19947
  const cursor = await this.ctx.storage.sql.exec(
@@ -19995,7 +20000,7 @@ ${resultContent}${attachmentSummary}`;
19995
20000
  * always produce a normal tool error (no client connected, timeout, or user
19996
20001
  * denial) for the model to reason about.
19997
20002
  */
19998
- async callClientTool(request) {
20003
+ async callClientTool(request, durability) {
19999
20004
  const owner = await this.getExecutionOwner();
20000
20005
  const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
20001
20006
  if (!socket) {
@@ -20005,7 +20010,33 @@ ${resultContent}${attachmentSummary}`;
20005
20010
  data: { noClient: true }
20006
20011
  };
20007
20012
  }
20008
- const id = crypto.randomUUID();
20013
+ if (durability) {
20014
+ try {
20015
+ await this.ensureMigrated();
20016
+ await this.ensurePendingClientTable();
20017
+ const ownerId = owner?.client_id ?? null;
20018
+ const socketClientId = this.bridgeClientInfo.get(socket)?.clientId ?? null;
20019
+ const fenced = owner && socketClientId === owner.client_id;
20020
+ await this.ctx.storage.sql.exec(
20021
+ `INSERT OR REPLACE INTO pending_client_calls
20022
+ (tool_call_id, tool, args, request_permission, risk, side, status, created_at,
20023
+ dispatched_client_id, dispatched_generation)
20024
+ VALUES (?, ?, ?, ?, ?, ?, 'pending', ?, ?, ?)`,
20025
+ durability.toolCallId,
20026
+ request.tool,
20027
+ JSON.stringify(request.args ?? {}),
20028
+ request.requestPermission ?? null,
20029
+ typeof request.risk === "number" ? request.risk : null,
20030
+ durability.side,
20031
+ Date.now(),
20032
+ fenced ? ownerId : null,
20033
+ fenced ? owner.generation : 0
20034
+ );
20035
+ } catch (error) {
20036
+ console.error("[DurableThread] Failed to persist fast-path call (continuing):", error);
20037
+ }
20038
+ }
20039
+ const id = durability?.toolCallId ?? crypto.randomUUID();
20009
20040
  const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : DEFAULT_CLIENT_TOOL_TIMEOUT_MS;
20010
20041
  this.bridgeLastSeen.set(socket, Date.now());
20011
20042
  return new Promise((resolve) => {
@@ -20056,6 +20087,13 @@ ${resultContent}${attachmentSummary}`;
20056
20087
  this.pendingClientCalls.delete(id);
20057
20088
  if (pending.timer) clearTimeout(pending.timer);
20058
20089
  if (pending.liveness) clearInterval(pending.liveness);
20090
+ try {
20091
+ void this.ctx.storage.sql.exec(
20092
+ `UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ? AND status = 'pending'`,
20093
+ id
20094
+ );
20095
+ } catch {
20096
+ }
20059
20097
  pending.resolve(res);
20060
20098
  }
20061
20099
  /** Close and forget a bridge socket we've deemed dead, failing remaining calls. */
@@ -20307,6 +20345,26 @@ ${resultContent}${attachmentSummary}`;
20307
20345
  toolStatus,
20308
20346
  toolCallId
20309
20347
  );
20348
+ const updated = await this.ctx.storage.sql.exec(
20349
+ `SELECT COUNT(*) AS total FROM messages WHERE tool_call_id = ? AND role = 'tool'`,
20350
+ toolCallId
20351
+ );
20352
+ if (Number(updated.toArray()[0]?.total ?? 0) === 0) {
20353
+ const toolRow = await this.ctx.storage.sql.exec(
20354
+ `SELECT tool FROM pending_client_calls WHERE tool_call_id = ? LIMIT 1`,
20355
+ toolCallId
20356
+ );
20357
+ await this.ctx.storage.sql.exec(
20358
+ `INSERT INTO messages (id, role, content, name, tool_call_id, tool_status, created_at, status, prompt)
20359
+ VALUES (?, 'tool', ?, ?, ?, ?, ?, 'completed', NULL)`,
20360
+ crypto.randomUUID(),
20361
+ content,
20362
+ toolRow.toArray()[0]?.tool ?? null,
20363
+ toolCallId,
20364
+ toolStatus,
20365
+ Date.now() * TIMESTAMP_MULTIPLIER
20366
+ );
20367
+ }
20310
20368
  await this.ctx.storage.sql.exec(
20311
20369
  `UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
20312
20370
  toolCallId
@@ -20329,10 +20387,40 @@ ${resultContent}${attachmentSummary}`;
20329
20387
  // they are bound to one bridge socket and never park.
20330
20388
  /** Default time to wait for the client to accept a stream open. */
20331
20389
  static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
20390
+ /** Poll cadence while waiting for a disconnected owner's bridge to return. */
20391
+ static OWNER_REATTACH_POLL_MS = 250;
20392
+ /**
20393
+ * The thread HAS an elected execution owner, but its bridge socket is gone
20394
+ * (daemon restart, network blip, DO handoff). Summon the owning machine
20395
+ * through the per-user hub and wait for a bridge to re-attach instead of
20396
+ * failing the stream open instantly — that instant failure is what ended
20397
+ * Sama One turns with "workspace unavailable" whenever a slow tool/approval
20398
+ * outlived the socket. Threads with NO recorded owner (subagent children)
20399
+ * still fail fast so ancestor walk-up routing stays immediate.
20400
+ */
20401
+ async awaitOwnerBridgeSocket(request) {
20402
+ if (request.wake?.userId) {
20403
+ void this.requestHubWake(request.wake.userId, request.wake.threadId);
20404
+ }
20405
+ const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
20406
+ const deadline = Date.now() + timeoutMs;
20407
+ while (Date.now() < deadline) {
20408
+ await new Promise(
20409
+ (resolve) => setTimeout(resolve, _DurableThread.OWNER_REATTACH_POLL_MS)
20410
+ );
20411
+ const owner = await this.getExecutionOwner();
20412
+ const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
20413
+ if (socket) return socket;
20414
+ }
20415
+ return null;
20416
+ }
20332
20417
  /** Open a bidirectional byte stream to the connected owner client. */
20333
20418
  async openClientStream(request) {
20334
20419
  const owner = await this.getExecutionOwner();
20335
- const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
20420
+ let socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
20421
+ if (!socket && owner) {
20422
+ socket = await this.awaitOwnerBridgeSocket(request);
20423
+ }
20336
20424
  if (!socket) {
20337
20425
  return {
20338
20426
  ok: false,
@@ -20626,6 +20714,11 @@ ${resultContent}${attachmentSummary}`;
20626
20714
  console.error("[DurableThread] Failed to arm park TTL (non-fatal):", error);
20627
20715
  }
20628
20716
  if (!userId) return;
20717
+ await this.requestHubWake(userId, threadId);
20718
+ }
20719
+ /** Ask the user's hub to summon the owning machine's daemon to a thread.
20720
+ * Best-effort: wake failure must never fail the caller's own path. */
20721
+ async requestHubWake(userId, threadId) {
20629
20722
  try {
20630
20723
  const namespace = this.env.AGENT_BUILDER_USER_HUB;
20631
20724
  if (!namespace) return;
@@ -21209,6 +21302,31 @@ ${resultContent}${attachmentSummary}`;
21209
21302
  `SELECT COUNT(*) AS c FROM message_queue`
21210
21303
  )).toArray()[0]?.c ?? 0;
21211
21304
  if (stopped || finalizedCount === 0 && queued === 0) {
21305
+ let orphanedClientCalls = false;
21306
+ if (!stopped) {
21307
+ try {
21308
+ orphanedClientCalls = await this.hasInFlightClientCalls();
21309
+ } catch {
21310
+ orphanedClientCalls = false;
21311
+ }
21312
+ }
21313
+ if (orphanedClientCalls) {
21314
+ const owner = await this.getExecutionOwner();
21315
+ const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
21316
+ if (socket) {
21317
+ void this.resendPendingClientCalls(socket);
21318
+ } else if (threadId) {
21319
+ try {
21320
+ const meta = await this.getThreadMetadata(threadId);
21321
+ if (meta.user_id) await this.requestHubWake(meta.user_id, threadId);
21322
+ } catch {
21323
+ }
21324
+ }
21325
+ console.warn(
21326
+ `[DurableThread] Watchdog: pending forwarded call with no executor on ${threadId} \u2014 ${socket ? "re-dispatched to the connected bridge" : "requested owner wake"}`
21327
+ );
21328
+ return report("observing", "none", { queuedMessages: queued });
21329
+ }
21212
21330
  if (finalizedCount > 0) {
21213
21331
  return report("parked", "finalized_parked", {
21214
21332
  queuedMessages: queued,