@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.
@@ -77,15 +77,11 @@ declare class StreamManager {
77
77
  }
78
78
 
79
79
  /**
80
- * Client-tool forwarding — a BUILDER capability, not a spec surface.
80
+ * Client-tool forwarding and the legacy client-stream adapter.
81
81
  *
82
- * The Standard Agent Specification has no "client" concept: agents, prompts,
83
- * hooks, and tools are defined against spec surfaces only. But tools are
84
- * arbitrary code and may call anything — including this builder API, which
85
- * forwards an operation over the thread's `bridge` WebSocket to a connected
86
- * client (a CLI or machine daemon) for local execution. Products built on
87
- * AgentBuilder (e.g. Standard Code) import these types and entry points from
88
- * `@standardagents/builder`.
82
+ * Tool forwarding remains Builder-specific. Live byte streams are exposed to
83
+ * portable agent code through the optional spec `ThreadState.runtimeStreams`
84
+ * surface, which this module adapts onto the existing bridge frames.
89
85
  */
90
86
  /**
91
87
  * A request forwarded from a server-side tool to a connected client (CLI or
@@ -125,10 +121,8 @@ interface ClientToolResponse {
125
121
  /**
126
122
  * A request to open a bidirectional byte stream to the connected client.
127
123
  *
128
- * The client interprets `tool` + `meta` (e.g. `tcp_tunnel` with
129
- * `{ host, port }` opens a raw TCP socket and shuttles bytes both ways).
130
- * Payload bytes are opaque to the transport — for a TLS tunnel the client
131
- * only ever relays ciphertext.
124
+ * The execution host interprets `tool` + `meta`; both fields and all payload
125
+ * bytes are opaque to the bridge transport.
132
126
  */
133
127
  interface ClientStreamRequest {
134
128
  /** Client stream capability name (e.g. `tcp_tunnel`). */
@@ -137,6 +131,18 @@ interface ClientStreamRequest {
137
131
  meta?: Record<string, unknown>;
138
132
  /** Milliseconds to wait for the client to accept the stream. */
139
133
  timeoutMs?: number;
134
+ /**
135
+ * Builder-internal reconnect hint, never sent to the client: when the
136
+ * thread's elected execution owner has no live socket, ask this user's hub
137
+ * to summon the owning machine's daemon to `threadId` and wait for the
138
+ * bridge to re-attach before failing the open. Without it a transient
139
+ * disconnect (daemon restart, network blip) surfaces instantly as
140
+ * "workspace unavailable" mid-turn.
141
+ */
142
+ wake?: {
143
+ threadId: string;
144
+ userId: string;
145
+ };
140
146
  }
141
147
  /**
142
148
  * A live bidirectional byte pipe between the thread runtime and the client.
@@ -189,9 +195,10 @@ declare function supportsClientForwarding(state: ThreadState): state is ThreadSt
189
195
  */
190
196
  declare function callClient(state: ThreadState, request: ClientToolRequest): Promise<ClientToolResponse>;
191
197
  /**
192
- * Open a bidirectional byte stream to the connected client (e.g. a raw TCP
193
- * tunnel for Worker-side TLS termination). Requires a live bridge socket on
194
- * this thread — streams never park durably.
198
+ * Open a bidirectional byte stream through the legacy Builder-only surface.
199
+ * Requires a live execution-host bridge and never parks durably.
200
+ *
201
+ * @deprecated Portable agent code should use `state.runtimeStreams?.open()`.
195
202
  */
196
203
  declare function callClientStream(state: ThreadState, request: ClientStreamRequest): Promise<ClientStreamResponse>;
197
204
  /** Maximum raw bytes per `stream_data` frame (base64 expands ~4/3 on the wire;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ModelDefinition, AgentDefinition } from './runtime.js';
2
2
  export { AgentBuilderEnv, AuthContext, AuthUser, 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, ThreadRegistryEntry, ThreadWatchdog, ThreadWatchdogState, ToolConfig, UpdateThreadParams, User, WatchdogCheckin, WatchdogProbeResult, authenticate, buildImageDescription, cat, createNamespaceContext, emitThreadEvent, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isQualifiedName, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, 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 { A as Agent, x as AttachmentRef, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, m as ClientStreamRequest, n as ClientStreamResponse, k as ClientToolRequest, l as ClientToolResponse, B as Controller, a as ControllerContext, E as Env, w as FileRecord, y as FileStats, u as FlowResult, F as FlowState, G as GrepResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, J as MessageContent, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, q as ThreadInstance, r as ThreadMetadata, T as ToolCall, t as ToolResult, 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';
4
+ export { A as Agent, x as AttachmentRef, f as BuilderThreadEndpointHandler, C as CLIENT_STREAM_CHUNK_BYTES, o as ClientDuplex, p as ClientForwarding, m as ClientStreamRequest, n as ClientStreamResponse, k as ClientToolRequest, l as ClientToolResponse, B as Controller, a as ControllerContext, E as Env, w as FileRecord, y as FileStats, u as FlowResult, F as FlowState, G as GrepResult, D as ImageContentPart, I as ImageMetadata, L as LLMResponse, M as Message, J as MessageContent, H as MultimodalContent, R as RequestContext, S as StorageBackend, v as TelemetryEvent, z as TextContentPart, b as ThreadEndpointContext, e as ThreadEnv, q as ThreadInstance, r as ThreadMetadata, T as ToolCall, t as ToolResult, 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';
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
@@ -56,6 +56,30 @@ function callClient(state, request) {
56
56
  return s.callClient(request);
57
57
  }
58
58
  function callClientStream(state, request) {
59
+ if (state.runtimeStreams) {
60
+ return state.runtimeStreams.open({
61
+ capability: request.tool,
62
+ metadata: request.meta,
63
+ timeoutMs: request.timeoutMs
64
+ }).then(
65
+ (opened) => opened.ok ? {
66
+ ok: true,
67
+ duplex: {
68
+ readable: opened.stream.readable,
69
+ writable: opened.stream.writable,
70
+ close: (reason) => opened.stream.close(reason),
71
+ done: opened.stream.closed
72
+ }
73
+ } : {
74
+ ok: false,
75
+ error: opened.error,
76
+ data: opened.code ? {
77
+ code: opened.code,
78
+ noClient: opened.code === "unavailable" ? true : void 0
79
+ } : void 0
80
+ }
81
+ );
82
+ }
59
83
  const s = asForwarding(state);
60
84
  if (typeof s.callClientStream !== "function") {
61
85
  return Promise.resolve({
@@ -65,6 +89,39 @@ function callClientStream(state, request) {
65
89
  }
66
90
  return s.callClientStream(request);
67
91
  }
92
+ function createRuntimeStreams(openClientStream) {
93
+ return {
94
+ async open(request) {
95
+ const opened = await openClientStream({
96
+ tool: request.capability,
97
+ meta: request.metadata,
98
+ timeoutMs: request.timeoutMs
99
+ });
100
+ if (!opened.ok) {
101
+ const failureData = opened.data;
102
+ return {
103
+ ok: false,
104
+ error: opened.error,
105
+ code: failureData?.code
106
+ };
107
+ }
108
+ let closed = false;
109
+ return {
110
+ ok: true,
111
+ stream: {
112
+ readable: opened.duplex.readable,
113
+ writable: opened.duplex.writable,
114
+ closed: opened.duplex.done,
115
+ close: (reason) => {
116
+ if (closed) return;
117
+ closed = true;
118
+ opened.duplex.close(reason);
119
+ }
120
+ }
121
+ };
122
+ }
123
+ };
124
+ }
68
125
  function encodeStreamChunk(bytes) {
69
126
  let binary = "";
70
127
  const step = 32768;
@@ -12939,6 +12996,14 @@ var init_ThreadStateImpl = __esm({
12939
12996
  }
12940
12997
  return this.getMetadataArguments();
12941
12998
  }
12999
+ /**
13000
+ * Optional spec byte streams backed by the execution owner's existing
13001
+ * bridge. Streams are live resources: they proxy through ancestors when a
13002
+ * subagent opens one, but they never park or outlive a bridge disconnect.
13003
+ */
13004
+ get runtimeStreams() {
13005
+ return createRuntimeStreams((request) => this.callClientStream(request));
13006
+ }
12942
13007
  getMetadataArguments() {
12943
13008
  const value = this._metadata.properties?.[SUBAGENT_INITIAL_ARGUMENTS_THREAD_PROPERTY];
12944
13009
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -13515,11 +13580,9 @@ var init_ThreadStateImpl = __esm({
13515
13580
  return local;
13516
13581
  }
13517
13582
  /**
13518
- * Open a bidirectional byte stream to the connected client (e.g. a raw TCP
13519
- * tunnel for Worker-side TLS termination). Streams require a LIVE bridge on
13520
- * this thread and never park — a stream is only meaningful while the client
13521
- * is attached. Subagents must not call this directly; they proxy whole
13522
- * requests up to the root thread instead (request-up / stream-down).
13583
+ * Legacy bridge implementation used by `runtimeStreams`. Streams require a
13584
+ * live execution owner and never park. A child thread proxies the complete
13585
+ * request to the nearest bridge-holding ancestor (request-up/stream-down).
13523
13586
  */
13524
13587
  async callClientStream(request) {
13525
13588
  const instance = this._threadInstance;
@@ -13529,7 +13592,9 @@ var init_ThreadStateImpl = __esm({
13529
13592
  error: "This thread runtime cannot open client streams. Client streaming requires the DurableThread bridge."
13530
13593
  };
13531
13594
  }
13532
- const local = await instance.openClientStream(request);
13595
+ const wakeUserId = this._metadata.user_id ?? null;
13596
+ const withWake = (threadId) => wakeUserId ? { ...request, wake: { threadId, userId: wakeUserId } } : request;
13597
+ const local = await instance.openClientStream(withWake(this._metadata.id));
13533
13598
  if (local.ok || !this.isNoClient(local)) return local;
13534
13599
  const env = this._threadInstance.env;
13535
13600
  if (!env?.AGENT_BUILDER_THREAD) return local;
@@ -13540,12 +13605,16 @@ var init_ThreadStateImpl = __esm({
13540
13605
  try {
13541
13606
  const stub = env.AGENT_BUILDER_THREAD.get(env.AGENT_BUILDER_THREAD.idFromName(parentId));
13542
13607
  if (typeof stub.proxyStreamOpen === "function") {
13543
- const opened = await stub.proxyStreamOpen(request);
13608
+ const opened = await stub.proxyStreamOpen(withWake(parentId));
13544
13609
  if (opened.ok && opened.id) {
13545
13610
  return { ok: true, duplex: this.buildProxyDuplex(stub, opened.id) };
13546
13611
  }
13547
13612
  if (opened.error && !opened.noClient) {
13548
- return { ok: false, error: opened.error };
13613
+ return {
13614
+ ok: false,
13615
+ error: opened.error,
13616
+ data: opened.code ? { code: opened.code } : void 0
13617
+ };
13549
13618
  }
13550
13619
  }
13551
13620
  } catch {
@@ -13564,6 +13633,12 @@ var init_ThreadStateImpl = __esm({
13564
13633
  let settleDone;
13565
13634
  const done = new Promise((r) => settleDone = r);
13566
13635
  let closed = false;
13636
+ let settled = false;
13637
+ const settle = (result) => {
13638
+ if (settled) return;
13639
+ settled = true;
13640
+ settleDone(result);
13641
+ };
13567
13642
  const readable = new ReadableStream({
13568
13643
  async pull(controller) {
13569
13644
  while (!closed) {
@@ -13572,7 +13647,7 @@ var init_ThreadStateImpl = __esm({
13572
13647
  res = await stub.proxyStreamPull(id);
13573
13648
  } catch (error) {
13574
13649
  controller.error(error instanceof Error ? error : new Error(String(error)));
13575
- settleDone({ ok: false, error: "proxy stream pull failed" });
13650
+ settle({ ok: false, error: "proxy stream pull failed" });
13576
13651
  return;
13577
13652
  }
13578
13653
  for (const chunk of res.chunks) {
@@ -13580,7 +13655,7 @@ var init_ThreadStateImpl = __esm({
13580
13655
  }
13581
13656
  if (res.done) {
13582
13657
  controller.close();
13583
- settleDone(res.error ? { ok: false, error: res.error } : { ok: true });
13658
+ settle(res.error ? { ok: false, error: res.error } : { ok: true });
13584
13659
  return;
13585
13660
  }
13586
13661
  if (res.chunks.length > 0) return;
@@ -13591,6 +13666,7 @@ var init_ThreadStateImpl = __esm({
13591
13666
  closed = true;
13592
13667
  void stub.proxyStreamAbort(id).catch(() => {
13593
13668
  });
13669
+ settle({ ok: false, error: "proxy stream reader canceled" });
13594
13670
  }
13595
13671
  });
13596
13672
  const writable = new WritableStream({
@@ -13605,16 +13681,18 @@ var init_ThreadStateImpl = __esm({
13605
13681
  closed = true;
13606
13682
  void stub.proxyStreamAbort(id).catch(() => {
13607
13683
  });
13684
+ settle({ ok: false, error: "proxy stream writer aborted" });
13608
13685
  }
13609
13686
  });
13610
13687
  return {
13611
13688
  readable,
13612
13689
  writable,
13613
13690
  close: (error) => {
13691
+ if (closed) return;
13614
13692
  closed = true;
13615
13693
  void stub.proxyStreamAbort(id).catch(() => {
13616
13694
  });
13617
- settleDone(error ? { ok: false, error } : { ok: true });
13695
+ settle(error ? { ok: false, error } : { ok: true });
13618
13696
  },
13619
13697
  done
13620
13698
  };
@@ -28588,23 +28666,51 @@ ${resultContent}${attachmentSummary}`;
28588
28666
  }
28589
28667
  return { side };
28590
28668
  }
28591
- // ─── Client byte streams (Worker-side TLS tunnels) ─────────────────────────
28592
- // A stream is a bidirectional byte pipe over the bridge WebSocket: the
28593
- // client opens a raw TCP socket per the stream's `meta` and shuttles bytes
28594
- // both ways; the Worker terminates TLS over those bytes (node:tls over the
28595
- // duplex), so the client only ever relays ciphertext. Streams are bound to a
28596
- // live socket and never park.
28669
+ // ─── Runtime byte streams ──────────────────────────────────────────────────
28670
+ // The existing bridge frames carry opaque capability metadata and bytes
28671
+ // between the runtime and its execution owner. Streams are live resources:
28672
+ // they are bound to one bridge socket and never park.
28597
28673
  /** Default time to wait for the client to accept a stream open. */
28598
28674
  static CLIENT_STREAM_OPEN_TIMEOUT_MS = 3e4;
28675
+ /** Poll cadence while waiting for a disconnected owner's bridge to return. */
28676
+ static OWNER_REATTACH_POLL_MS = 250;
28677
+ /**
28678
+ * The thread HAS an elected execution owner, but its bridge socket is gone
28679
+ * (daemon restart, network blip, DO handoff). Summon the owning machine
28680
+ * through the per-user hub and wait for a bridge to re-attach instead of
28681
+ * failing the stream open instantly — that instant failure is what ended
28682
+ * Sama One turns with "workspace unavailable" whenever a slow tool/approval
28683
+ * outlived the socket. Threads with NO recorded owner (subagent children)
28684
+ * still fail fast so ancestor walk-up routing stays immediate.
28685
+ */
28686
+ async awaitOwnerBridgeSocket(request) {
28687
+ if (request.wake?.userId) {
28688
+ void this.requestHubWake(request.wake.userId, request.wake.threadId);
28689
+ }
28690
+ const timeoutMs = typeof request.timeoutMs === "number" && request.timeoutMs > 0 ? request.timeoutMs : _DurableThread.CLIENT_STREAM_OPEN_TIMEOUT_MS;
28691
+ const deadline = Date.now() + timeoutMs;
28692
+ while (Date.now() < deadline) {
28693
+ await new Promise(
28694
+ (resolve2) => setTimeout(resolve2, _DurableThread.OWNER_REATTACH_POLL_MS)
28695
+ );
28696
+ const owner = await this.getExecutionOwner();
28697
+ const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
28698
+ if (socket) return socket;
28699
+ }
28700
+ return null;
28701
+ }
28599
28702
  /** Open a bidirectional byte stream to the connected owner client. */
28600
28703
  async openClientStream(request) {
28601
28704
  const owner = await this.getExecutionOwner();
28602
- const socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
28705
+ let socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
28706
+ if (!socket && owner) {
28707
+ socket = await this.awaitOwnerBridgeSocket(request);
28708
+ }
28603
28709
  if (!socket) {
28604
28710
  return {
28605
28711
  ok: false,
28606
28712
  error: "The workspace is not available right now. Try again in a moment.",
28607
- data: { noClient: true }
28713
+ data: { noClient: true, code: "unavailable" }
28608
28714
  };
28609
28715
  }
28610
28716
  const id = crypto.randomUUID();
@@ -28637,7 +28743,8 @@ ${resultContent}${attachmentSummary}`;
28637
28743
  state.openTimer = setTimeout(() => {
28638
28744
  resolve2({
28639
28745
  ok: false,
28640
- error: `The client did not accept the ${request.tool} stream within ${Math.round(timeoutMs / 1e3)}s.`
28746
+ error: `The client did not accept the ${request.tool} stream within ${Math.round(timeoutMs / 1e3)}s.`,
28747
+ code: "timeout"
28641
28748
  });
28642
28749
  }, timeoutMs);
28643
28750
  try {
@@ -28652,7 +28759,8 @@ ${resultContent}${attachmentSummary}`;
28652
28759
  } catch (error) {
28653
28760
  resolve2({
28654
28761
  ok: false,
28655
- error: `Failed to reach the client: ${error instanceof Error ? error.message : String(error)}`
28762
+ error: `Failed to reach the client: ${error instanceof Error ? error.message : String(error)}`,
28763
+ code: "unavailable"
28656
28764
  });
28657
28765
  }
28658
28766
  });
@@ -28665,7 +28773,11 @@ ${resultContent}${attachmentSummary}`;
28665
28773
  this.clientStreams.delete(id);
28666
28774
  state.settled = true;
28667
28775
  settleDone({ ok: false, error: openResult.error });
28668
- return { ok: false, error: openResult.error ?? "Stream open failed." };
28776
+ return {
28777
+ ok: false,
28778
+ error: openResult.error ?? "Stream open failed.",
28779
+ data: openResult.code ? { code: openResult.code } : void 0
28780
+ };
28669
28781
  }
28670
28782
  const sendChunked = (bytes) => {
28671
28783
  for (let i = 0; i < bytes.length; i += CLIENT_STREAM_CHUNK_BYTES) {
@@ -28709,8 +28821,14 @@ ${resultContent}${attachmentSummary}`;
28709
28821
  async proxyStreamOpen(request) {
28710
28822
  const opened = await this.openClientStream(request);
28711
28823
  if (!opened.ok) {
28712
- const noClient = !!(opened.data && opened.data.noClient);
28713
- return { ok: false, error: opened.error, noClient };
28824
+ const failureData = opened.data;
28825
+ const noClient = failureData?.noClient === true;
28826
+ return {
28827
+ ok: false,
28828
+ error: opened.error,
28829
+ noClient,
28830
+ code: failureData?.code
28831
+ };
28714
28832
  }
28715
28833
  const id = crypto.randomUUID();
28716
28834
  const duplex = opened.duplex;
@@ -28781,12 +28899,12 @@ ${resultContent}${attachmentSummary}`;
28781
28899
  }
28782
28900
  }
28783
28901
  /** Settle and remove a client stream exactly once. */
28784
- settleClientStream(id, result) {
28902
+ settleClientStream(id, result, openCode) {
28785
28903
  const state = this.clientStreams.get(id);
28786
28904
  if (!state) return;
28787
28905
  this.clientStreams.delete(id);
28788
28906
  if (state.openTimer) clearTimeout(state.openTimer);
28789
- if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed." });
28907
+ if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed.", code: openCode });
28790
28908
  if (!state.settled) {
28791
28909
  state.settled = true;
28792
28910
  try {
@@ -28830,7 +28948,7 @@ ${resultContent}${attachmentSummary}`;
28830
28948
  }
28831
28949
  case "stream_error": {
28832
28950
  const error = typeof parsed.error === "string" ? parsed.error : "Client stream error.";
28833
- this.settleClientStream(id, { ok: false, error });
28951
+ this.settleClientStream(id, { ok: false, error }, "refused");
28834
28952
  return;
28835
28953
  }
28836
28954
  case "stream_data": {
@@ -28857,7 +28975,7 @@ ${resultContent}${attachmentSummary}`;
28857
28975
  failClientStreamsForSocket(ws) {
28858
28976
  for (const [id, state] of Array.from(this.clientStreams.entries())) {
28859
28977
  if (state.socket === ws) {
28860
- this.settleClientStream(id, { ok: false, error: "The client connection was lost." });
28978
+ this.settleClientStream(id, { ok: false, error: "The client connection was lost." }, "unavailable");
28861
28979
  }
28862
28980
  }
28863
28981
  }
@@ -28881,6 +28999,11 @@ ${resultContent}${attachmentSummary}`;
28881
28999
  console.error("[DurableThread] Failed to arm park TTL (non-fatal):", error);
28882
29000
  }
28883
29001
  if (!userId) return;
29002
+ await this.requestHubWake(userId, threadId);
29003
+ }
29004
+ /** Ask the user's hub to summon the owning machine's daemon to a thread.
29005
+ * Best-effort: wake failure must never fail the caller's own path. */
29006
+ async requestHubWake(userId, threadId) {
28884
29007
  try {
28885
29008
  const namespace = this.env.AGENT_BUILDER_USER_HUB;
28886
29009
  if (!namespace) return;