@standardagents/builder 0.28.1 → 0.29.1

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-BzXyIJPA.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-BzXyIJPA.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-BNLDsLsL.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-BNLDsLsL.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';
@@ -1112,6 +1112,18 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1112
1112
  } | null>;
1113
1113
  /** Default time to wait for the client to accept a stream open. */
1114
1114
  private static readonly CLIENT_STREAM_OPEN_TIMEOUT_MS;
1115
+ /** Poll cadence while waiting for a disconnected owner's bridge to return. */
1116
+ private static readonly OWNER_REATTACH_POLL_MS;
1117
+ /**
1118
+ * The thread HAS an elected execution owner, but its bridge socket is gone
1119
+ * (daemon restart, network blip, DO handoff). Summon the owning machine
1120
+ * through the per-user hub and wait for a bridge to re-attach instead of
1121
+ * failing the stream open instantly — that instant failure is what ended
1122
+ * Sama One turns with "workspace unavailable" whenever a slow tool/approval
1123
+ * outlived the socket. Threads with NO recorded owner (subagent children)
1124
+ * still fail fast so ancestor walk-up routing stays immediate.
1125
+ */
1126
+ private awaitOwnerBridgeSocket;
1115
1127
  /** Open a bidirectional byte stream to the connected owner client. */
1116
1128
  openClientStream(request: ClientStreamRequest): Promise<ClientStreamResponse>;
1117
1129
  private proxyStreams;
@@ -1120,6 +1132,7 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1120
1132
  id?: string;
1121
1133
  error?: string;
1122
1134
  noClient?: boolean;
1135
+ code?: 'unsupported' | 'unavailable' | 'refused' | 'timeout';
1123
1136
  }>;
1124
1137
  proxyStreamWrite(id: string, dataB64: string): Promise<void>;
1125
1138
  proxyStreamPull(id: string): Promise<{
@@ -1146,6 +1159,9 @@ declare class DurableThread<Env extends ThreadEnv = ThreadEnv> extends DurableOb
1146
1159
  * instead of a silent hang.
1147
1160
  */
1148
1161
  notifyClientWaiting(toolCallId: string, threadId: string, userId: string | null): Promise<void>;
1162
+ /** Ask the user's hub to summon the owning machine's daemon to a thread.
1163
+ * Best-effort: wake failure must never fail the caller's own path. */
1164
+ private requestHubWake;
1149
1165
  /**
1150
1166
  * Park TTL expired: if the call is still pending, settle it as a durable,
1151
1167
  * VISIBLE failure — rewrite the waiting placeholder, mark the pending row
package/dist/runtime.js CHANGED
@@ -47,6 +47,30 @@ function callClient(state, request) {
47
47
  return s.callClient(request);
48
48
  }
49
49
  function callClientStream(state, request) {
50
+ if (state.runtimeStreams) {
51
+ return state.runtimeStreams.open({
52
+ capability: request.tool,
53
+ metadata: request.meta,
54
+ timeoutMs: request.timeoutMs
55
+ }).then(
56
+ (opened) => opened.ok ? {
57
+ ok: true,
58
+ duplex: {
59
+ readable: opened.stream.readable,
60
+ writable: opened.stream.writable,
61
+ close: (reason) => opened.stream.close(reason),
62
+ done: opened.stream.closed
63
+ }
64
+ } : {
65
+ ok: false,
66
+ error: opened.error,
67
+ data: opened.code ? {
68
+ code: opened.code,
69
+ noClient: opened.code === "unavailable" ? true : void 0
70
+ } : void 0
71
+ }
72
+ );
73
+ }
50
74
  const s = asForwarding(state);
51
75
  if (typeof s.callClientStream !== "function") {
52
76
  return Promise.resolve({
@@ -56,6 +80,39 @@ function callClientStream(state, request) {
56
80
  }
57
81
  return s.callClientStream(request);
58
82
  }
83
+ function createRuntimeStreams(openClientStream) {
84
+ return {
85
+ async open(request) {
86
+ const opened = await openClientStream({
87
+ tool: request.capability,
88
+ meta: request.metadata,
89
+ timeoutMs: request.timeoutMs
90
+ });
91
+ if (!opened.ok) {
92
+ const failureData = opened.data;
93
+ return {
94
+ ok: false,
95
+ error: opened.error,
96
+ code: failureData?.code
97
+ };
98
+ }
99
+ let closed = false;
100
+ return {
101
+ ok: true,
102
+ stream: {
103
+ readable: opened.duplex.readable,
104
+ writable: opened.duplex.writable,
105
+ closed: opened.duplex.done,
106
+ close: (reason) => {
107
+ if (closed) return;
108
+ closed = true;
109
+ opened.duplex.close(reason);
110
+ }
111
+ }
112
+ };
113
+ }
114
+ };
115
+ }
59
116
  function encodeStreamChunk(bytes) {
60
117
  let binary = "";
61
118
  const step = 32768;
@@ -12930,6 +12987,14 @@ var init_ThreadStateImpl = __esm({
12930
12987
  }
12931
12988
  return this.getMetadataArguments();
12932
12989
  }
12990
+ /**
12991
+ * Optional spec byte streams backed by the execution owner's existing
12992
+ * bridge. Streams are live resources: they proxy through ancestors when a
12993
+ * subagent opens one, but they never park or outlive a bridge disconnect.
12994
+ */
12995
+ get runtimeStreams() {
12996
+ return createRuntimeStreams((request) => this.callClientStream(request));
12997
+ }
12933
12998
  getMetadataArguments() {
12934
12999
  const value = this._metadata.properties?.[SUBAGENT_INITIAL_ARGUMENTS_THREAD_PROPERTY];
12935
13000
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -13506,11 +13571,9 @@ var init_ThreadStateImpl = __esm({
13506
13571
  return local;
13507
13572
  }
13508
13573
  /**
13509
- * Open a bidirectional byte stream to the connected client (e.g. a raw TCP
13510
- * tunnel for Worker-side TLS termination). Streams require a LIVE bridge on
13511
- * this thread and never park — a stream is only meaningful while the client
13512
- * is attached. Subagents must not call this directly; they proxy whole
13513
- * requests up to the root thread instead (request-up / stream-down).
13574
+ * Legacy bridge implementation used by `runtimeStreams`. Streams require a
13575
+ * live execution owner and never park. A child thread proxies the complete
13576
+ * request to the nearest bridge-holding ancestor (request-up/stream-down).
13514
13577
  */
13515
13578
  async callClientStream(request) {
13516
13579
  const instance = this._threadInstance;
@@ -13520,7 +13583,9 @@ var init_ThreadStateImpl = __esm({
13520
13583
  error: "This thread runtime cannot open client streams. Client streaming requires the DurableThread bridge."
13521
13584
  };
13522
13585
  }
13523
- const local = await instance.openClientStream(request);
13586
+ const wakeUserId = this._metadata.user_id ?? null;
13587
+ const withWake = (threadId) => wakeUserId ? { ...request, wake: { threadId, userId: wakeUserId } } : request;
13588
+ const local = await instance.openClientStream(withWake(this._metadata.id));
13524
13589
  if (local.ok || !this.isNoClient(local)) return local;
13525
13590
  const env = this._threadInstance.env;
13526
13591
  if (!env?.AGENT_BUILDER_THREAD) return local;
@@ -13531,12 +13596,16 @@ var init_ThreadStateImpl = __esm({
13531
13596
  try {
13532
13597
  const stub = env.AGENT_BUILDER_THREAD.get(env.AGENT_BUILDER_THREAD.idFromName(parentId));
13533
13598
  if (typeof stub.proxyStreamOpen === "function") {
13534
- const opened = await stub.proxyStreamOpen(request);
13599
+ const opened = await stub.proxyStreamOpen(withWake(parentId));
13535
13600
  if (opened.ok && opened.id) {
13536
13601
  return { ok: true, duplex: this.buildProxyDuplex(stub, opened.id) };
13537
13602
  }
13538
13603
  if (opened.error && !opened.noClient) {
13539
- return { ok: false, error: opened.error };
13604
+ return {
13605
+ ok: false,
13606
+ error: opened.error,
13607
+ data: opened.code ? { code: opened.code } : void 0
13608
+ };
13540
13609
  }
13541
13610
  }
13542
13611
  } catch {
@@ -13555,6 +13624,12 @@ var init_ThreadStateImpl = __esm({
13555
13624
  let settleDone;
13556
13625
  const done = new Promise((r) => settleDone = r);
13557
13626
  let closed = false;
13627
+ let settled = false;
13628
+ const settle = (result) => {
13629
+ if (settled) return;
13630
+ settled = true;
13631
+ settleDone(result);
13632
+ };
13558
13633
  const readable = new ReadableStream({
13559
13634
  async pull(controller) {
13560
13635
  while (!closed) {
@@ -13563,7 +13638,7 @@ var init_ThreadStateImpl = __esm({
13563
13638
  res = await stub.proxyStreamPull(id);
13564
13639
  } catch (error) {
13565
13640
  controller.error(error instanceof Error ? error : new Error(String(error)));
13566
- settleDone({ ok: false, error: "proxy stream pull failed" });
13641
+ settle({ ok: false, error: "proxy stream pull failed" });
13567
13642
  return;
13568
13643
  }
13569
13644
  for (const chunk of res.chunks) {
@@ -13571,7 +13646,7 @@ var init_ThreadStateImpl = __esm({
13571
13646
  }
13572
13647
  if (res.done) {
13573
13648
  controller.close();
13574
- settleDone(res.error ? { ok: false, error: res.error } : { ok: true });
13649
+ settle(res.error ? { ok: false, error: res.error } : { ok: true });
13575
13650
  return;
13576
13651
  }
13577
13652
  if (res.chunks.length > 0) return;
@@ -13582,6 +13657,7 @@ var init_ThreadStateImpl = __esm({
13582
13657
  closed = true;
13583
13658
  void stub.proxyStreamAbort(id).catch(() => {
13584
13659
  });
13660
+ settle({ ok: false, error: "proxy stream reader canceled" });
13585
13661
  }
13586
13662
  });
13587
13663
  const writable = new WritableStream({
@@ -13596,16 +13672,18 @@ var init_ThreadStateImpl = __esm({
13596
13672
  closed = true;
13597
13673
  void stub.proxyStreamAbort(id).catch(() => {
13598
13674
  });
13675
+ settle({ ok: false, error: "proxy stream writer aborted" });
13599
13676
  }
13600
13677
  });
13601
13678
  return {
13602
13679
  readable,
13603
13680
  writable,
13604
13681
  close: (error) => {
13682
+ if (closed) return;
13605
13683
  closed = true;
13606
13684
  void stub.proxyStreamAbort(id).catch(() => {
13607
13685
  });
13608
- settleDone(error ? { ok: false, error } : { ok: true });
13686
+ settle(error ? { ok: false, error } : { ok: true });
13609
13687
  },
13610
13688
  done
13611
13689
  };
@@ -20247,23 +20325,51 @@ ${resultContent}${attachmentSummary}`;
20247
20325
  }
20248
20326
  return { side };
20249
20327
  }
20250
- // ─── Client byte streams (Worker-side TLS tunnels) ─────────────────────────
20251
- // A stream is a bidirectional byte pipe over the bridge WebSocket: the
20252
- // client opens a raw TCP socket per the stream's `meta` and shuttles bytes
20253
- // both ways; the Worker terminates TLS over those bytes (node:tls over the
20254
- // duplex), so the client only ever relays ciphertext. Streams are bound to a
20255
- // live socket and never park.
20328
+ // ─── Runtime byte streams ──────────────────────────────────────────────────
20329
+ // The existing bridge frames carry opaque capability metadata and bytes
20330
+ // between the runtime and its execution owner. Streams are live resources:
20331
+ // they are bound to one bridge socket and never park.
20256
20332
  /** Default time to wait for the client to accept a stream open. */
20257
20333
  static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
20334
+ /** Poll cadence while waiting for a disconnected owner's bridge to return. */
20335
+ static OWNER_REATTACH_POLL_MS = 250;
20336
+ /**
20337
+ * The thread HAS an elected execution owner, but its bridge socket is gone
20338
+ * (daemon restart, network blip, DO handoff). Summon the owning machine
20339
+ * through the per-user hub and wait for a bridge to re-attach instead of
20340
+ * failing the stream open instantly — that instant failure is what ended
20341
+ * Sama One turns with "workspace unavailable" whenever a slow tool/approval
20342
+ * outlived the socket. Threads with NO recorded owner (subagent children)
20343
+ * still fail fast so ancestor walk-up routing stays immediate.
20344
+ */
20345
+ async awaitOwnerBridgeSocket(request) {
20346
+ if (request.wake?.userId) {
20347
+ void this.requestHubWake(request.wake.userId, request.wake.threadId);
20348
+ }
20349
+ const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
20350
+ const deadline = Date.now() + timeoutMs;
20351
+ while (Date.now() < deadline) {
20352
+ await new Promise(
20353
+ (resolve) => setTimeout(resolve, _DurableThread.OWNER_REATTACH_POLL_MS)
20354
+ );
20355
+ const owner = await this.getExecutionOwner();
20356
+ const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
20357
+ if (socket) return socket;
20358
+ }
20359
+ return null;
20360
+ }
20258
20361
  /** Open a bidirectional byte stream to the connected owner client. */
20259
20362
  async openClientStream(request) {
20260
20363
  const owner = await this.getExecutionOwner();
20261
- const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
20364
+ let socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
20365
+ if (!socket && owner) {
20366
+ socket = await this.awaitOwnerBridgeSocket(request);
20367
+ }
20262
20368
  if (!socket) {
20263
20369
  return {
20264
20370
  ok: false,
20265
20371
  error: "The workspace is not available right now. Try again in a moment.",
20266
- data: { noClient: true }
20372
+ data: { noClient: true, code: "unavailable" }
20267
20373
  };
20268
20374
  }
20269
20375
  const id = crypto.randomUUID();
@@ -20296,7 +20402,8 @@ ${resultContent}${attachmentSummary}`;
20296
20402
  state.openTimer = setTimeout(() => {
20297
20403
  resolve({
20298
20404
  ok: false,
20299
- error: `The client did not accept the ${request.tool} stream within ${Math.round(timeoutMs / 1e3)}s.`
20405
+ error: `The client did not accept the ${request.tool} stream within ${Math.round(timeoutMs / 1e3)}s.`,
20406
+ code: "timeout"
20300
20407
  });
20301
20408
  }, timeoutMs);
20302
20409
  try {
@@ -20311,7 +20418,8 @@ ${resultContent}${attachmentSummary}`;
20311
20418
  } catch (error) {
20312
20419
  resolve({
20313
20420
  ok: false,
20314
- error: `Failed to reach the client: ${error instanceof Error ? error.message : String(error)}`
20421
+ error: `Failed to reach the client: ${error instanceof Error ? error.message : String(error)}`,
20422
+ code: "unavailable"
20315
20423
  });
20316
20424
  }
20317
20425
  });
@@ -20324,7 +20432,11 @@ ${resultContent}${attachmentSummary}`;
20324
20432
  this.clientStreams.delete(id);
20325
20433
  state.settled = true;
20326
20434
  settleDone({ ok: false, error: openResult.error });
20327
- return { ok: false, error: openResult.error ?? "Stream open failed." };
20435
+ return {
20436
+ ok: false,
20437
+ error: openResult.error ?? "Stream open failed.",
20438
+ data: openResult.code ? { code: openResult.code } : void 0
20439
+ };
20328
20440
  }
20329
20441
  const sendChunked = (bytes) => {
20330
20442
  for (let i = 0; i < bytes.length; i += CLIENT_STREAM_CHUNK_BYTES) {
@@ -20368,8 +20480,14 @@ ${resultContent}${attachmentSummary}`;
20368
20480
  async proxyStreamOpen(request) {
20369
20481
  const opened = await this.openClientStream(request);
20370
20482
  if (!opened.ok) {
20371
- const noClient = !!(opened.data && opened.data.noClient);
20372
- return { ok: false, error: opened.error, noClient };
20483
+ const failureData = opened.data;
20484
+ const noClient = failureData?.noClient === true;
20485
+ return {
20486
+ ok: false,
20487
+ error: opened.error,
20488
+ noClient,
20489
+ code: failureData?.code
20490
+ };
20373
20491
  }
20374
20492
  const id = crypto.randomUUID();
20375
20493
  const duplex = opened.duplex;
@@ -20440,12 +20558,12 @@ ${resultContent}${attachmentSummary}`;
20440
20558
  }
20441
20559
  }
20442
20560
  /** Settle and remove a client stream exactly once. */
20443
- settleClientStream(id, result) {
20561
+ settleClientStream(id, result, openCode) {
20444
20562
  const state = this.clientStreams.get(id);
20445
20563
  if (!state) return;
20446
20564
  this.clientStreams.delete(id);
20447
20565
  if (state.openTimer) clearTimeout(state.openTimer);
20448
- if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed." });
20566
+ if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed.", code: openCode });
20449
20567
  if (!state.settled) {
20450
20568
  state.settled = true;
20451
20569
  try {
@@ -20489,7 +20607,7 @@ ${resultContent}${attachmentSummary}`;
20489
20607
  }
20490
20608
  case "stream_error": {
20491
20609
  const error = typeof parsed.error === "string" ? parsed.error : "Client stream error.";
20492
- this.settleClientStream(id, { ok: false, error });
20610
+ this.settleClientStream(id, { ok: false, error }, "refused");
20493
20611
  return;
20494
20612
  }
20495
20613
  case "stream_data": {
@@ -20516,7 +20634,7 @@ ${resultContent}${attachmentSummary}`;
20516
20634
  failClientStreamsForSocket(ws) {
20517
20635
  for (const [id, state] of Array.from(this.clientStreams.entries())) {
20518
20636
  if (state.socket === ws) {
20519
- this.settleClientStream(id, { ok: false, error: "The client connection was lost." });
20637
+ this.settleClientStream(id, { ok: false, error: "The client connection was lost." }, "unavailable");
20520
20638
  }
20521
20639
  }
20522
20640
  }
@@ -20540,6 +20658,11 @@ ${resultContent}${attachmentSummary}`;
20540
20658
  console.error("[DurableThread] Failed to arm park TTL (non-fatal):", error);
20541
20659
  }
20542
20660
  if (!userId) return;
20661
+ await this.requestHubWake(userId, threadId);
20662
+ }
20663
+ /** Ask the user's hub to summon the owning machine's daemon to a thread.
20664
+ * Best-effort: wake failure must never fail the caller's own path. */
20665
+ async requestHubWake(userId, threadId) {
20543
20666
  try {
20544
20667
  const namespace = this.env.AGENT_BUILDER_USER_HUB;
20545
20668
  if (!namespace) return;