@standardagents/builder 0.40.5-next.3b25e2a → 0.40.6

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/index.js CHANGED
@@ -5083,9 +5083,7 @@ Message one with subagent_message(referenceId, message).`
5083
5083
  if (resumable) {
5084
5084
  await this.settleResumableChildAfterRun(
5085
5085
  state,
5086
- stub,
5087
- childThreadId,
5088
- childResult
5086
+ childThreadId
5089
5087
  );
5090
5088
  } else if (stub.terminateThread) {
5091
5089
  await stub.terminateThread(childThreadId);
@@ -5097,7 +5095,7 @@ Message one with subagent_message(referenceId, message).`
5097
5095
  attachments: copiedAttachments,
5098
5096
  subagent_id: childThreadId
5099
5097
  };
5100
- return resumable && !this.isTerminalSubagentResult(childResult) ? this.withReferenceInToolResult(toolResult, childThreadId) : toolResult;
5098
+ return resumable ? this.withReferenceInToolResult(toolResult, childThreadId) : toolResult;
5101
5099
  }
5102
5100
  const backgroundRun = (async () => {
5103
5101
  try {
@@ -5132,9 +5130,7 @@ Message one with subagent_message(referenceId, message).`
5132
5130
  if (resumable) {
5133
5131
  await this.settleResumableChildAfterRun(
5134
5132
  state,
5135
- stub,
5136
- childThreadId,
5137
- childResult
5133
+ childThreadId
5138
5134
  );
5139
5135
  } else if (stub.terminateThread) {
5140
5136
  await stub.terminateThread(childThreadId);
@@ -5529,18 +5525,8 @@ Message one with subagent_message(referenceId, message).`
5529
5525
  JSON.stringify(next)
5530
5526
  );
5531
5527
  }
5532
- static isTerminalSubagentResult(result) {
5533
- return result.terminal === true;
5534
- }
5535
- static async settleResumableChildAfterRun(state, childThread, reference, result) {
5536
- if (!this.isTerminalSubagentResult(result)) {
5537
- await this.updateChildStatus(state, reference, "idle");
5538
- return;
5539
- }
5540
- await this.updateChildStatus(state, reference, "terminated");
5541
- if (typeof childThread.terminateThread === "function") {
5542
- await childThread.terminateThread(reference);
5543
- }
5528
+ static async settleResumableChildAfterRun(state, reference) {
5529
+ await this.updateChildStatus(state, reference, "idle");
5544
5530
  }
5545
5531
  static async buildChildInvocationMessages(state, childThread, content, receivesMessages, parentAttachments, initialArguments) {
5546
5532
  const copiedAttachments = await this.copyAttachmentsBetweenThreads(
@@ -14736,27 +14722,51 @@ var init_ThreadStateImpl = __esm({
14736
14722
  const withWake = (threadId) => wakeUserId ? { ...request, wake: { threadId, userId: wakeUserId } } : request;
14737
14723
  const local = await instance.openClientStream(withWake(this._metadata.id));
14738
14724
  if (local.ok || !this.isNoClient(local)) return local;
14739
- const env = this._threadInstance.env;
14740
- if (!env?.AGENT_BUILDER_THREAD) return local;
14725
+ if (typeof instance.createProxyStreamReceiver !== "function" || typeof instance.proxyStreamReceive !== "function") {
14726
+ return local;
14727
+ }
14728
+ let abortRemote = null;
14729
+ const receiver = instance.createProxyStreamReceiver((error) => abortRemote?.(error));
14730
+ const discardReceiver = async (error) => {
14731
+ await instance.proxyStreamReceive(receiver.id, { type: "abort", error });
14732
+ };
14733
+ const namespace = instance.env.AGENT_BUILDER_THREAD;
14741
14734
  const seen = /* @__PURE__ */ new Set([this._metadata.id]);
14742
14735
  let parentId = await this.resolveParentThreadId(this._metadata.id);
14743
14736
  while (parentId && !seen.has(parentId)) {
14744
14737
  seen.add(parentId);
14745
14738
  try {
14746
- const stub = env.AGENT_BUILDER_THREAD.get(env.AGENT_BUILDER_THREAD.idFromName(parentId));
14739
+ const stub = namespace.get(namespace.idFromName(parentId));
14747
14740
  if (typeof stub.proxyStreamOpen === "function") {
14748
- const opened = await stub.proxyStreamOpen(withWake(parentId));
14741
+ const opened = await stub.proxyStreamOpen(withWake(parentId), {
14742
+ threadId: this._metadata.id,
14743
+ receiverId: receiver.id
14744
+ });
14749
14745
  if (opened.ok && opened.id) {
14746
+ if (opened.eventDriven !== true) {
14747
+ await stub.proxyStreamAbort(opened.id).catch(() => {
14748
+ });
14749
+ await discardReceiver("The ancestor runtime does not support event-driven streams.");
14750
+ return {
14751
+ ok: false,
14752
+ error: "The ancestor runtime does not support event-driven streams.",
14753
+ data: { code: "unsupported" }
14754
+ };
14755
+ }
14756
+ abortRemote = (error) => {
14757
+ void stub.proxyStreamAbort(opened.id).catch(() => {
14758
+ });
14759
+ if (error) {
14760
+ void instance.proxyStreamReceive(receiver.id, { type: "abort", error });
14761
+ }
14762
+ };
14750
14763
  return {
14751
14764
  ok: true,
14752
- duplex: this.buildProxyDuplex(
14753
- stub,
14754
- opened.id,
14755
- opened.parkedPull === true
14756
- )
14765
+ duplex: this.buildEventProxyDuplex(stub, opened.id, receiver, instance)
14757
14766
  };
14758
14767
  }
14759
14768
  if (opened.error && !opened.noClient) {
14769
+ await discardReceiver(opened.error);
14760
14770
  return {
14761
14771
  ok: false,
14762
14772
  error: opened.error,
@@ -14768,69 +14778,21 @@ var init_ThreadStateImpl = __esm({
14768
14778
  }
14769
14779
  parentId = await this.resolveParentThreadId(parentId);
14770
14780
  }
14781
+ await discardReceiver(local.error ?? "No execution-owner bridge is available.");
14771
14782
  return local;
14772
14783
  }
14773
14784
  /**
14774
14785
  * Build a client duplex that bridges to a stream opened on an ancestor DO
14775
- * (the bridge holder) via chunk-pull RPCs. `readable` issues PARKED pulls —
14776
- * a current ancestor holds one `proxyStreamPull` until bytes, completion,
14777
- * error, or abort, so an idle open stream performs zero repeated pulls.
14778
- * Older ancestors use the bounded long-pull compatibility path. `writable`
14779
- * forwards each chunk with `proxyStreamWrite`; teardown calls
14780
- * `proxyStreamAbort`.
14786
+ * (the bridge holder). The readable is the receiver registered on this DO;
14787
+ * writes and terminal actions are the only child-to-ancestor RPCs.
14781
14788
  */
14782
- static PROXY_PULL_MAX_WAIT_MS = 2e4;
14783
- buildProxyDuplex(stub, id, supportsParkedPull = false) {
14784
- let settleDone;
14785
- const done = new Promise((r) => settleDone = r);
14789
+ buildEventProxyDuplex(stub, id, receiver, instance) {
14786
14790
  let closed = false;
14787
- let settled = false;
14788
- const settle = (result) => {
14789
- if (settled) return;
14790
- settled = true;
14791
- settleDone(result);
14791
+ const settleLocal = (error) => {
14792
+ if (typeof instance.proxyStreamReceive !== "function") return;
14793
+ const frame = error ? { type: "abort", error } : { type: "end" };
14794
+ void instance.proxyStreamReceive(receiver.id, frame);
14792
14795
  };
14793
- const readable = new ReadableStream({
14794
- async pull(controller) {
14795
- while (!closed) {
14796
- const pullStartedAt = Date.now();
14797
- let res;
14798
- try {
14799
- res = await stub.proxyStreamPull(
14800
- id,
14801
- supportsParkedPull ? void 0 : _ThreadStateImpl.PROXY_PULL_MAX_WAIT_MS
14802
- );
14803
- } catch (error) {
14804
- controller.error(error instanceof Error ? error : new Error(String(error)));
14805
- settle({ ok: false, error: "proxy stream pull failed" });
14806
- return;
14807
- }
14808
- for (const chunk of res.chunks) {
14809
- controller.enqueue(decodeStreamChunk(chunk));
14810
- }
14811
- if (res.done) {
14812
- controller.close();
14813
- settle(res.error ? { ok: false, error: res.error } : { ok: true });
14814
- return;
14815
- }
14816
- if (res.chunks.length > 0) return;
14817
- if (supportsParkedPull) {
14818
- controller.error(new Error("parked proxy pull returned without data"));
14819
- settle({ ok: false, error: "parked proxy pull returned without data" });
14820
- return;
14821
- }
14822
- if (Date.now() - pullStartedAt < 250) {
14823
- await new Promise((r) => setTimeout(r, 250));
14824
- }
14825
- }
14826
- },
14827
- cancel() {
14828
- closed = true;
14829
- void stub.proxyStreamAbort(id).catch(() => {
14830
- });
14831
- settle({ ok: false, error: "proxy stream reader canceled" });
14832
- }
14833
- });
14834
14796
  const writable = new WritableStream({
14835
14797
  async write(chunk) {
14836
14798
  await stub.proxyStreamWrite(id, encodeStreamChunk(chunk));
@@ -14843,20 +14805,20 @@ var init_ThreadStateImpl = __esm({
14843
14805
  closed = true;
14844
14806
  void stub.proxyStreamAbort(id).catch(() => {
14845
14807
  });
14846
- settle({ ok: false, error: "proxy stream writer aborted" });
14808
+ settleLocal("The proxy stream writer was aborted.");
14847
14809
  }
14848
14810
  });
14849
14811
  return {
14850
- readable,
14812
+ readable: receiver.readable,
14851
14813
  writable,
14852
14814
  close: (error) => {
14853
14815
  if (closed) return;
14854
14816
  closed = true;
14855
14817
  void stub.proxyStreamAbort(id).catch(() => {
14856
14818
  });
14857
- settle(error ? { ok: false, error } : { ok: true });
14819
+ settleLocal(error);
14858
14820
  },
14859
- done
14821
+ done: receiver.done
14860
14822
  };
14861
14823
  }
14862
14824
  isNoClient(res) {
@@ -16819,7 +16781,7 @@ function createHmrReloader() {
16819
16781
  const isConfigFile = isModelFile || isProviderFile || isPromptFile || isAgentFile || isEffectFile;
16820
16782
  if (!isToolFile && !isThreadApiFile && !isHookFile && !isConfigFile && !isUserEndpointFile) return;
16821
16783
  reloader.invalidateSourceFileModule(server, file);
16822
- if (isConfigFile || isToolFile) regenerateTypes();
16784
+ if (isConfigFile || isToolFile || isHookFile) regenerateTypes();
16823
16785
  if (isToolFile) await reloader.reloadToolsModule(server);
16824
16786
  if (isModelFile) await reloader.reloadModelsModule(server);
16825
16787
  if (isProviderFile) await reloader.reloadProvidersModule(server);
@@ -18947,6 +18909,29 @@ async function scanPromptsDirectory(dir) {
18947
18909
  async function scanAgentsDirectory(dir) {
18948
18910
  return scanConfigDirectory(dir, /export\s+default\s+defineAgent/);
18949
18911
  }
18912
+ function findLegacyRootAgentFiles(agentsDir) {
18913
+ const agentsRoot = path9__default.dirname(agentsDir);
18914
+ if (path9__default.basename(agentsDir) !== "agents" || path9__default.basename(agentsRoot) !== "agents") {
18915
+ return [];
18916
+ }
18917
+ if (!fs9__default.existsSync(agentsRoot)) return [];
18918
+ return fs9__default.readdirSync(agentsRoot, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".ts")).map((entry) => path9__default.join(agentsRoot, entry.name)).filter((filePath) => {
18919
+ try {
18920
+ return /export\s+default\s+defineAgent/.test(fs9__default.readFileSync(filePath, "utf8"));
18921
+ } catch {
18922
+ return false;
18923
+ }
18924
+ }).sort();
18925
+ }
18926
+ function warnLegacyRootAgentFiles(agentsDir) {
18927
+ const expectedDir = path9__default.relative(process.cwd(), agentsDir).replace(/\\/g, "/");
18928
+ for (const filePath of findLegacyRootAgentFiles(agentsDir)) {
18929
+ const source = path9__default.relative(process.cwd(), filePath).replace(/\\/g, "/");
18930
+ console.warn(
18931
+ `[agentbuilder] Agent definition "${source}" is outside the scanned agent directory. Move it to "${expectedDir}/" so it is registered.`
18932
+ );
18933
+ }
18934
+ }
18950
18935
  async function scanEffectsDirectory(dir) {
18951
18936
  const effects = [];
18952
18937
  if (!fs9__default.existsSync(dir)) {
@@ -22657,7 +22642,32 @@ function isHostedInstance(env) {
22657
22642
  }
22658
22643
  return Boolean(value);
22659
22644
  }
22660
- function isTrustedLocalDevelopmentRequest(request, env) {
22645
+ function normalizeTrustedDevHosts(hosts) {
22646
+ const normalized = /* @__PURE__ */ new Set();
22647
+ for (const rawHost of hosts ?? []) {
22648
+ const candidate = rawHost.trim().toLowerCase().replace(/\.$/, "");
22649
+ try {
22650
+ const parsed = new URL(`http://${candidate}`);
22651
+ if (!candidate || candidate.includes("*") || parsed.hostname.toLowerCase().replace(/\.$/, "") !== candidate || parsed.port || parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
22652
+ throw new Error("invalid hostname");
22653
+ }
22654
+ } catch {
22655
+ throw new Error(
22656
+ `Invalid trusted development hostname "${rawHost}". Use an exact hostname without a scheme, port, path, or wildcard.`
22657
+ );
22658
+ }
22659
+ normalized.add(candidate);
22660
+ }
22661
+ return [...normalized];
22662
+ }
22663
+ function compiledTrustedDevHosts() {
22664
+ try {
22665
+ return typeof __STANDARD_AGENTS_TRUSTED_DEV_HOSTS__ !== "undefined" && Array.isArray(__STANDARD_AGENTS_TRUSTED_DEV_HOSTS__) ? __STANDARD_AGENTS_TRUSTED_DEV_HOSTS__ : [];
22666
+ } catch {
22667
+ return [];
22668
+ }
22669
+ }
22670
+ function isTrustedLocalDevelopmentRequest(request, env, trustedDevHosts = compiledTrustedDevHosts()) {
22661
22671
  if (isHostedInstance(env)) return false;
22662
22672
  let hostname;
22663
22673
  try {
@@ -22665,7 +22675,7 @@ function isTrustedLocalDevelopmentRequest(request, env) {
22665
22675
  } catch {
22666
22676
  return false;
22667
22677
  }
22668
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost") || hostname.endsWith(".orb.local");
22678
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost") || hostname.endsWith(".orb.local") || trustedDevHosts.some((trusted) => trusted.trim().toLowerCase() === hostname);
22669
22679
  }
22670
22680
  function isPublicApiRoute(routePath, request, env) {
22671
22681
  if (PUBLIC_ROUTES.has(routePath)) return true;
@@ -22814,7 +22824,8 @@ function createDevMiddleware(server, context) {
22814
22824
  installedProviderPackageMap,
22815
22825
  hmr,
22816
22826
  sourceDirs,
22817
- regenerateTypes
22827
+ regenerateTypes,
22828
+ trustedDevHosts
22818
22829
  } = context;
22819
22830
  server.watcher.on("add", async (file) => {
22820
22831
  await hmr.handleFileChange(file, server, sourceDirs, regenerateTypes);
@@ -22848,7 +22859,8 @@ function createDevMiddleware(server, context) {
22848
22859
  try {
22849
22860
  trustedLocalHost = isTrustedLocalDevelopmentRequest(
22850
22861
  new Request(`http://${req.headers.host || "invalid.remote"}${routePath}`),
22851
- {}
22862
+ {},
22863
+ trustedDevHosts
22852
22864
  );
22853
22865
  } catch {
22854
22866
  }
@@ -24082,7 +24094,7 @@ var firstPartyProviderPackages = [
24082
24094
  "@standardagents/openrouter",
24083
24095
  "@standardagents/xai"
24084
24096
  ];
24085
- var depsToInclude = [
24097
+ var baseDepsToInclude = [
24086
24098
  "zod",
24087
24099
  "zod/v3",
24088
24100
  "zod/v4",
@@ -24093,17 +24105,24 @@ var depsToInclude = [
24093
24105
  // a root dep). The `parent > child` form pre-bundles it at startup so Vite
24094
24106
  // doesn't discover it mid-request and reload the worker (which would destroy
24095
24107
  // an in-flight Durable Object execution and drop the client bridge).
24096
- "@standardagents/openrouter > @openrouter/sdk",
24097
24108
  "magic-string",
24098
24109
  "@standardagents/spec",
24099
24110
  // Incremental JSON reader used by the runtime to surface a tool's progress
24100
24111
  // argument from partial streamed tool-call arguments (tool_call_started).
24101
- "@formkit/jsonreader",
24102
- // Pre-bundle the provider packages themselves so their whole subtree (SDKs,
24103
- // pricing tables) lands in the initial optimized bundle and nothing is
24104
- // discovered lazily by workerd.
24105
- ...firstPartyProviderPackages
24112
+ "@formkit/jsonreader"
24106
24113
  ];
24114
+ function depsToInclude(options = {}) {
24115
+ const installedProviders = options.isPackageInstalled ? firstPartyProviderPackages.filter(options.isPackageInstalled) : firstPartyProviderPackages;
24116
+ const openRouterTransitive = installedProviders.includes("@standardagents/openrouter") ? ["@standardagents/openrouter > @openrouter/sdk"] : [];
24117
+ return [...baseDepsToInclude, ...openRouterTransitive, ...installedProviders];
24118
+ }
24119
+ function trustedDevHostDefines(options) {
24120
+ return {
24121
+ __STANDARD_AGENTS_TRUSTED_DEV_HOSTS__: JSON.stringify(
24122
+ normalizeTrustedDevHosts(options.trustedDevHosts)
24123
+ )
24124
+ };
24125
+ }
24107
24126
  var packingDeps = [
24108
24127
  "rollup",
24109
24128
  "@rollup/plugin-commonjs",
@@ -24112,7 +24131,7 @@ var packingDeps = [
24112
24131
  "fsevents",
24113
24132
  "typescript"
24114
24133
  ];
24115
- function createPluginViteConfig(pluginModuleUrl) {
24134
+ function createPluginViteConfig(pluginModuleUrl, options = {}) {
24116
24135
  const currentDir = path9__default.dirname(fileURLToPath(pluginModuleUrl));
24117
24136
  const isInDist = currentDir.endsWith("dist");
24118
24137
  const builderClientDir = path9__default.resolve(
@@ -24120,6 +24139,7 @@ function createPluginViteConfig(pluginModuleUrl) {
24120
24139
  isInDist ? "./client" : "../dist/client"
24121
24140
  );
24122
24141
  return {
24142
+ define: trustedDevHostDefines(options),
24123
24143
  publicDir: fs9__default.existsSync(builderClientDir) ? builderClientDir : void 0,
24124
24144
  build: {
24125
24145
  rollupOptions: {
@@ -24135,7 +24155,7 @@ function createPluginViteConfig(pluginModuleUrl) {
24135
24155
  // `npm run dev` ("There is a new version of the pre-bundle for X").
24136
24156
  holdUntilCrawlEnd: true,
24137
24157
  exclude: depsToExclude,
24138
- include: depsToInclude.filter((dep) => dep !== "zod/v3" && dep !== "zod/v4" && dep !== "zod/v4/core")
24158
+ include: depsToInclude(options).filter((dep) => dep !== "zod/v3" && dep !== "zod/v4" && dep !== "zod/v4/core")
24139
24159
  },
24140
24160
  ssr: {
24141
24161
  noExternal: [
@@ -24240,7 +24260,11 @@ function createAgentBuilderCloudflarePlugins(options) {
24240
24260
  }
24241
24261
  });
24242
24262
  }
24243
- function applyPluginEnvironmentConfig(config) {
24263
+ function applyPluginEnvironmentConfig(config, options = {}) {
24264
+ config.define = {
24265
+ ...config.define || {},
24266
+ ...trustedDevHostDefines(options)
24267
+ };
24244
24268
  config.optimizeDeps = config.optimizeDeps || {};
24245
24269
  config.optimizeDeps.holdUntilCrawlEnd = true;
24246
24270
  config.optimizeDeps.exclude = [
@@ -24249,7 +24273,7 @@ function applyPluginEnvironmentConfig(config) {
24249
24273
  ];
24250
24274
  config.optimizeDeps.include = [
24251
24275
  ...config.optimizeDeps.include || [],
24252
- ...depsToInclude.filter((dep) => !config.optimizeDeps?.include?.includes(dep))
24276
+ ...depsToInclude(options).filter((dep) => !config.optimizeDeps?.include?.includes(dep))
24253
24277
  ];
24254
24278
  config.build = config.build || {};
24255
24279
  config.build.rollupOptions = config.build.rollupOptions || {};
@@ -24309,6 +24333,7 @@ function normalizeDeviceApprovalBrand(value) {
24309
24333
  // src/plugin.ts
24310
24334
  function agentbuilder(options = {}) {
24311
24335
  const deviceApproval = normalizeDeviceApprovalBrand(options.deviceApproval);
24336
+ const trustedDevHosts = normalizeTrustedDevHosts(options.trustedDevHosts);
24312
24337
  const toolsDir = options.toolsDir ? path9__default.resolve(process.cwd(), options.toolsDir) : path9__default.resolve(process.cwd(), "agents/tools");
24313
24338
  const hooksDir = options.hooksDir ? path9__default.resolve(process.cwd(), options.hooksDir) : path9__default.resolve(process.cwd(), "agents/hooks");
24314
24339
  const threadApiDir = options.apiDir ? path9__default.resolve(process.cwd(), options.apiDir) : path9__default.resolve(process.cwd(), "agents/api");
@@ -24413,7 +24438,8 @@ export { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog
24413
24438
  installedProviderPackageMap,
24414
24439
  hmr,
24415
24440
  sourceDirs,
24416
- regenerateTypes
24441
+ regenerateTypes,
24442
+ trustedDevHosts
24417
24443
  };
24418
24444
  const workerEntry = path9__default.relative(process.cwd(), generatedWorkerEntry).split(path9__default.sep).join("/");
24419
24445
  const corePlugin = {
@@ -24421,12 +24447,18 @@ export { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog
24421
24447
  enforce: "pre",
24422
24448
  config() {
24423
24449
  writeGeneratedWorkerEntry();
24424
- const viteConfig = createPluginViteConfig(import.meta.url);
24450
+ const viteConfig = createPluginViteConfig(import.meta.url, {
24451
+ isPackageInstalled: isProviderPackageInstalled,
24452
+ trustedDevHosts
24453
+ });
24425
24454
  return viteConfig;
24426
24455
  },
24427
24456
  // Apply exclusions and inclusions to ALL environments including Cloudflare worker
24428
24457
  configEnvironment(_name, config) {
24429
- applyPluginEnvironmentConfig(config);
24458
+ applyPluginEnvironmentConfig(config, {
24459
+ isPackageInstalled: isProviderPackageInstalled,
24460
+ trustedDevHosts
24461
+ });
24430
24462
  },
24431
24463
  resolveId(id) {
24432
24464
  if (id === VIRTUAL_TOOLS_ID) {
@@ -24471,6 +24503,7 @@ export { CodeExecutionBridge, DurableAgentBuilder, DurableThread, ThreadWatchdog
24471
24503
  },
24472
24504
  buildStart() {
24473
24505
  writeGeneratedWorkerEntry();
24506
+ warnLegacyRootAgentFiles(agentsDir);
24474
24507
  regenerateTypes();
24475
24508
  this.addWatchFile(toolsDir);
24476
24509
  this.addWatchFile(threadApiDir);
@@ -29242,12 +29275,9 @@ ${result ?? error ?? "No result content."}${attachmentSummary}`;
29242
29275
  await parentThread.updateChildRegistryStatus(
29243
29276
  params.parent_thread_id,
29244
29277
  childThread.id,
29245
- childResult.terminal ? "terminated" : "idle"
29278
+ "idle"
29246
29279
  );
29247
29280
  }
29248
- if (childResult.terminal && typeof childThreadStub.terminateThread === "function") {
29249
- await childThreadStub.terminateThread(childThread.id);
29250
- }
29251
29281
  } else if (typeof childThreadStub.terminateThread === "function") {
29252
29282
  await childThreadStub.terminateThread(childThread.id);
29253
29283
  }
@@ -31042,6 +31072,8 @@ var DurableThread = class _DurableThread extends DurableObject {
31042
31072
  // opened on and dies with it. Streams are in-memory only — they exist solely
31043
31073
  // while the client is attached and never park durably.
31044
31074
  clientStreams = /* @__PURE__ */ new Map();
31075
+ proxyStreamReceivers = /* @__PURE__ */ new Map();
31076
+ eventProxyStreams = /* @__PURE__ */ new Map();
31045
31077
  alarmQueue;
31046
31078
  runtimeSafety;
31047
31079
  alarmQueueRearmed = false;
@@ -31153,6 +31185,10 @@ var DurableThread = class _DurableThread extends DurableObject {
31153
31185
  clientName: typeof attachment.clientName === "string" ? attachment.clientName : null,
31154
31186
  clientKind: typeof attachment.clientKind === "string" ? attachment.clientKind : null
31155
31187
  });
31188
+ const interrupted = this.parseProxyStreamAttachments(attachment.proxyStreams);
31189
+ if (interrupted.length > 0) {
31190
+ this.recoverInterruptedProxyStreams(ws, interrupted);
31191
+ }
31156
31192
  }
31157
31193
  }
31158
31194
  }
@@ -33243,11 +33279,13 @@ ${paths.join("\n")}`;
33243
33279
  try {
33244
33280
  const parentId = this.env.AGENT_BUILDER_THREAD.idFromName(parentThreadId);
33245
33281
  const parentStub = this.env.AGENT_BUILDER_THREAD.get(parentId);
33246
- const parentCommunication = await this.getParentCommunicationModeForChild(
33282
+ const childRegistryEntry = await this.getParentRegistryEntryForChild(
33247
33283
  parentStub,
33248
33284
  parentThreadId,
33249
33285
  threadId
33250
33286
  );
33287
+ const parentCommunication = childRegistryEntry?.parentCommunication === "explicit" ? "explicit" : "implicit";
33288
+ const resumableChild = childRegistryEntry?.resumable === true;
33251
33289
  const copiedRefs = await this.copyAttachmentRefsToThread(
33252
33290
  refs,
33253
33291
  parentStub,
@@ -33270,10 +33308,11 @@ ${resultContent}${attachmentSummary}`;
33270
33308
  });
33271
33309
  }
33272
33310
  const terminalChildRun = sessionStopped || hasTerminalSessionTools;
33311
+ const terminateChildThread = terminalChildRun && !resumableChild;
33273
33312
  await parentStub.updateChildRegistryStatus(
33274
33313
  parentThreadId,
33275
33314
  threadId,
33276
- terminalChildRun ? "terminated" : "idle"
33315
+ terminateChildThread ? "terminated" : "idle"
33277
33316
  );
33278
33317
  if (parentCommunication !== "explicit") {
33279
33318
  await this.persistParentHandoffStatusMessage(
@@ -33281,23 +33320,22 @@ ${resultContent}${attachmentSummary}`;
33281
33320
  isErrorTerminal ? "failure" : "result"
33282
33321
  );
33283
33322
  }
33284
- if (terminalChildRun) {
33323
+ if (terminateChildThread) {
33285
33324
  await this.terminateThread(threadId);
33286
33325
  }
33287
33326
  } catch (error) {
33288
33327
  console.error("[DurableThread] Failed to deliver subagent completion to parent:", error);
33289
33328
  }
33290
33329
  }
33291
- async getParentCommunicationModeForChild(parentStub, parentThreadId, childThreadId) {
33330
+ async getParentRegistryEntryForChild(parentStub, parentThreadId, childThreadId) {
33292
33331
  if (typeof parentStub.getChildrenRegistry !== "function") {
33293
- return "implicit";
33332
+ return void 0;
33294
33333
  }
33295
33334
  try {
33296
33335
  const entries = await parentStub.getChildrenRegistry(parentThreadId);
33297
- const childEntry = entries.find((entry) => entry.reference === childThreadId);
33298
- return childEntry?.parentCommunication === "explicit" ? "explicit" : "implicit";
33336
+ return entries.find((entry) => entry.reference === childThreadId);
33299
33337
  } catch {
33300
- return "implicit";
33338
+ return void 0;
33301
33339
  }
33302
33340
  }
33303
33341
  /**
@@ -34845,7 +34883,7 @@ ${resultContent}${attachmentSummary}`;
34845
34883
  void 0,
34846
34884
  INTERNAL_TOOL_DELIVERY
34847
34885
  );
34848
- if (delivered?.resume) sides.add(delivered.side);
34886
+ if (delivered) sides.add(delivered.side);
34849
34887
  }
34850
34888
  if (threadId) {
34851
34889
  for (const side of sides) {
@@ -35147,17 +35185,14 @@ ${resultContent}${attachmentSummary}`;
35147
35185
  deliveredResultWaiters = /* @__PURE__ */ new Map();
35148
35186
  notifyDeliveredResultWaiters(toolCallId) {
35149
35187
  const waiters = this.deliveredResultWaiters?.get(toolCallId);
35150
- if (!waiters || waiters.size === 0) return false;
35188
+ if (!waiters) return;
35151
35189
  this.deliveredResultWaiters.delete(toolCallId);
35152
- let notified = false;
35153
35190
  for (const wake of waiters) {
35154
35191
  try {
35155
35192
  wake();
35156
- notified = true;
35157
35193
  } catch {
35158
35194
  }
35159
35195
  }
35160
- return notified;
35161
35196
  }
35162
35197
  /**
35163
35198
  * Await a parked forwarded call's delivered result: one immediate read,
@@ -35302,46 +35337,37 @@ ${resultContent}${attachmentSummary}`;
35302
35337
  const side = pending.side === "b" ? "b" : "a";
35303
35338
  const content = ok ? result ?? "" : `Error: ${error ?? "tool failed"}`;
35304
35339
  const toolStatus = ok ? "success" : "error";
35305
- const resultMessageId = crypto.randomUUID();
35306
- const resultCreatedAt = Date.now() * TIMESTAMP_MULTIPLIER;
35307
- const accepted = this.ctx.storage.transactionSync(() => {
35308
- const claimed = this.ctx.storage.sql.exec(
35309
- `UPDATE pending_client_calls
35310
- SET status = 'resolved'
35311
- WHERE tool_call_id = ? AND status = 'pending'`,
35340
+ await this.ctx.storage.sql.exec(
35341
+ `UPDATE messages SET content = ?, tool_status = ? WHERE tool_call_id = ? AND role = 'tool'`,
35342
+ content,
35343
+ toolStatus,
35344
+ toolCallId
35345
+ );
35346
+ const updated = await this.ctx.storage.sql.exec(
35347
+ `SELECT COUNT(*) AS total FROM messages WHERE tool_call_id = ? AND role = 'tool'`,
35348
+ toolCallId
35349
+ );
35350
+ if (Number(updated.toArray()[0]?.total ?? 0) === 0) {
35351
+ const toolRow = await this.ctx.storage.sql.exec(
35352
+ `SELECT tool FROM pending_client_calls WHERE tool_call_id = ? LIMIT 1`,
35312
35353
  toolCallId
35313
35354
  );
35314
- if (claimed.rowsWritten !== 1) return false;
35315
- this.ctx.storage.sql.exec(
35316
- `UPDATE messages SET content = ?, tool_status = ? WHERE tool_call_id = ? AND role = 'tool'`,
35355
+ await this.ctx.storage.sql.exec(
35356
+ `INSERT INTO messages (id, role, content, name, tool_call_id, tool_status, created_at, status, prompt)
35357
+ VALUES (?, 'tool', ?, ?, ?, ?, ?, 'completed', NULL)`,
35358
+ crypto.randomUUID(),
35317
35359
  content,
35360
+ toolRow.toArray()[0]?.tool ?? null,
35361
+ toolCallId,
35318
35362
  toolStatus,
35319
- toolCallId
35363
+ Date.now() * TIMESTAMP_MULTIPLIER
35320
35364
  );
35321
- const updated = this.ctx.storage.sql.exec(
35322
- `SELECT COUNT(*) AS total FROM messages WHERE tool_call_id = ? AND role = 'tool'`,
35323
- toolCallId
35324
- );
35325
- if (Number(updated.toArray()[0]?.total ?? 0) === 0) {
35326
- const toolRow = this.ctx.storage.sql.exec(
35327
- `SELECT tool FROM pending_client_calls WHERE tool_call_id = ? LIMIT 1`,
35328
- toolCallId
35329
- );
35330
- this.ctx.storage.sql.exec(
35331
- `INSERT INTO messages (id, role, content, name, tool_call_id, tool_status, created_at, status, prompt)
35332
- VALUES (?, 'tool', ?, ?, ?, ?, ?, 'completed', NULL)`,
35333
- resultMessageId,
35334
- content,
35335
- toolRow.toArray()[0]?.tool ?? null,
35336
- toolCallId,
35337
- toolStatus,
35338
- resultCreatedAt
35339
- );
35340
- }
35341
- return true;
35342
- });
35343
- if (!accepted) return null;
35344
- const resumedInline = this.notifyDeliveredResultWaiters(toolCallId);
35365
+ }
35366
+ await this.ctx.storage.sql.exec(
35367
+ `UPDATE pending_client_calls SET status = 'resolved' WHERE tool_call_id = ?`,
35368
+ toolCallId
35369
+ );
35370
+ this.notifyDeliveredResultWaiters(toolCallId);
35345
35371
  try {
35346
35372
  const msgCursor = await this.ctx.storage.sql.exec(
35347
35373
  `SELECT id, role, content, name, tool_call_id, tool_status, created_at, depth
@@ -35352,7 +35378,7 @@ ${resultContent}${attachmentSummary}`;
35352
35378
  if (msg) this.broadcastMessage(msg);
35353
35379
  } catch {
35354
35380
  }
35355
- return { side, resume: !resumedInline };
35381
+ return { side };
35356
35382
  }
35357
35383
  // ─── Runtime byte streams ──────────────────────────────────────────────────
35358
35384
  // The existing bridge frames carry opaque capability metadata and bytes
@@ -35418,8 +35444,138 @@ ${resultContent}${attachmentSummary}`;
35418
35444
  });
35419
35445
  }
35420
35446
  }
35447
+ parseProxyStreamAttachments(value) {
35448
+ if (!Array.isArray(value)) return [];
35449
+ const parsed = [];
35450
+ for (const item of value) {
35451
+ if (!item || typeof item !== "object") continue;
35452
+ const candidate = item;
35453
+ if (typeof candidate.id === "string" && candidate.target && typeof candidate.target.threadId === "string" && typeof candidate.target.receiverId === "string") {
35454
+ parsed.push({
35455
+ id: candidate.id,
35456
+ target: {
35457
+ threadId: candidate.target.threadId,
35458
+ receiverId: candidate.target.receiverId
35459
+ }
35460
+ });
35461
+ }
35462
+ }
35463
+ return parsed;
35464
+ }
35465
+ /** Keep live proxy routes on the hibernating bridge socket for restart cleanup. */
35466
+ updateBridgeProxyAttachment(socket) {
35467
+ if (typeof socket.serializeAttachment !== "function") return;
35468
+ let attachment = { type: "bridge" };
35469
+ try {
35470
+ const current = socket.deserializeAttachment();
35471
+ if (current && typeof current === "object") {
35472
+ attachment = { ...current };
35473
+ }
35474
+ } catch {
35475
+ }
35476
+ const proxyStreams = Array.from(this.clientStreams.entries()).filter(([, state]) => state.socket === socket && state.proxyTarget !== null).map(([id, state]) => ({ id, target: state.proxyTarget }));
35477
+ try {
35478
+ socket.serializeAttachment({ ...attachment, proxyStreams });
35479
+ } catch {
35480
+ }
35481
+ }
35482
+ async deliverProxyStreamFrame(target, frame) {
35483
+ try {
35484
+ const namespace = this.env.AGENT_BUILDER_THREAD;
35485
+ const stub = namespace.get(namespace.idFromName(target.threadId));
35486
+ return await stub.proxyStreamReceive(target.receiverId, frame);
35487
+ } catch {
35488
+ return false;
35489
+ }
35490
+ }
35491
+ /** Abort live proxy routes restored without their in-memory stream state. */
35492
+ recoverInterruptedProxyStreams(socket, streams) {
35493
+ const error = "The stream ended because its runtime restarted.";
35494
+ for (const stream of streams) {
35495
+ try {
35496
+ if (socket.readyState === WebSocket.OPEN) {
35497
+ socket.send(JSON.stringify({ type: "stream_abort", id: stream.id, error }));
35498
+ }
35499
+ } catch {
35500
+ }
35501
+ this.ctx.waitUntil(
35502
+ this.deliverProxyStreamFrame(stream.target, { type: "abort", error }).then(() => {
35503
+ })
35504
+ );
35505
+ }
35506
+ this.updateBridgeProxyAttachment(socket);
35507
+ }
35508
+ /** Register the child side before any ancestor opens an owner stream. */
35509
+ createProxyStreamReceiver(onCancel) {
35510
+ const id = crypto.randomUUID();
35511
+ let controller = null;
35512
+ let settleDone;
35513
+ const done = new Promise((resolve3) => {
35514
+ settleDone = resolve3;
35515
+ });
35516
+ const readable = new ReadableStream({
35517
+ start(value) {
35518
+ controller = value;
35519
+ },
35520
+ cancel: (reason) => {
35521
+ const receiver2 = this.proxyStreamReceivers.get(id);
35522
+ if (!receiver2 || receiver2.settled) return;
35523
+ const error = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "The proxy stream reader was canceled.";
35524
+ receiver2.settle({ ok: false, error });
35525
+ receiver2.onCancel(error);
35526
+ }
35527
+ });
35528
+ const receiver = {
35529
+ controller,
35530
+ settle: (result) => {
35531
+ if (receiver.settled) return;
35532
+ receiver.settled = true;
35533
+ this.proxyStreamReceivers.delete(id);
35534
+ settleDone(result);
35535
+ },
35536
+ onCancel,
35537
+ settled: false
35538
+ };
35539
+ this.proxyStreamReceivers.set(id, receiver);
35540
+ return { id, readable, done };
35541
+ }
35542
+ async proxyStreamReceiverExists(receiverId) {
35543
+ return this.proxyStreamReceivers.has(receiverId);
35544
+ }
35545
+ async proxyStreamReceive(receiverId, frame) {
35546
+ const receiver = this.proxyStreamReceivers.get(receiverId);
35547
+ if (!receiver || receiver.settled) return false;
35548
+ if (frame.type === "data") {
35549
+ try {
35550
+ receiver.controller?.enqueue(decodeStreamChunk(frame.data));
35551
+ } catch (error2) {
35552
+ const message = error2 instanceof Error ? error2.message : String(error2);
35553
+ try {
35554
+ receiver.controller?.error(error2 instanceof Error ? error2 : new Error(message));
35555
+ } catch {
35556
+ }
35557
+ receiver.settle({ ok: false, error: message });
35558
+ }
35559
+ return true;
35560
+ }
35561
+ if (frame.type === "end") {
35562
+ try {
35563
+ receiver.controller?.close();
35564
+ } catch {
35565
+ }
35566
+ receiver.settle({ ok: true });
35567
+ return true;
35568
+ }
35569
+ const error = frame.error ?? "The proxied client stream was aborted.";
35570
+ try {
35571
+ receiver.controller?.error(new Error(error));
35572
+ } catch {
35573
+ }
35574
+ receiver.settle({ ok: false, error });
35575
+ return true;
35576
+ }
35421
35577
  /** Open a bidirectional byte stream to the connected owner client. */
35422
- async openClientStream(request) {
35578
+ async openClientStream(request, proxyTarget = null) {
35423
35579
  const owner = await this.getExecutionOwner();
35424
35580
  let socket = this.getOpenBridgeSocket(owner?.client_id ?? null);
35425
35581
  if (!socket && owner) {
@@ -35452,9 +35608,12 @@ ${resultContent}${attachmentSummary}`;
35452
35608
  opened: null,
35453
35609
  openTimer: null,
35454
35610
  settleDone,
35455
- settled: false
35611
+ settled: false,
35612
+ proxyTarget,
35613
+ delivery: Promise.resolve()
35456
35614
  };
35457
35615
  this.clientStreams.set(id, state);
35616
+ this.updateBridgeProxyAttachment(socket);
35458
35617
  this.bridgeLastSeen.set(socket, Date.now());
35459
35618
  const openResult = await new Promise((resolve3) => {
35460
35619
  state.opened = resolve3;
@@ -35490,6 +35649,7 @@ ${resultContent}${attachmentSummary}`;
35490
35649
  }
35491
35650
  if (!openResult.ok) {
35492
35651
  this.clientStreams.delete(id);
35652
+ this.updateBridgeProxyAttachment(socket);
35493
35653
  state.settled = true;
35494
35654
  settleDone({ ok: false, error: openResult.error });
35495
35655
  return {
@@ -35531,14 +35691,28 @@ ${resultContent}${attachmentSummary}`;
35531
35691
  };
35532
35692
  return { ok: true, duplex };
35533
35693
  }
35534
- // ─── Subagent stream proxy-up (chunk-pull, no cross-DO ReadableStream) ─────
35535
- // A subagent (child thread, no bridge) proxies its whole stream request UP to
35536
- // this bridge-holding root. We open the client stream locally, then buffer
35537
- // incoming bytes so the child can drain them over plain request/response RPCs
35538
- // — sidestepping the unproven DO-to-DO ReadableStream return.
35539
- proxyStreams = /* @__PURE__ */ new Map();
35540
- async proxyStreamOpen(request) {
35541
- const opened = await this.openClientStream(request);
35694
+ // ─── Event-driven subagent stream proxy ──────────────────────────────────
35695
+ // A child registers a local receiver before the bridge-holding ancestor
35696
+ // opens the client stream. The ancestor invokes that receiver only for real
35697
+ // data/end/abort events; there is no pull loop or permanently outstanding
35698
+ // cross-DO RPC while the stream is idle.
35699
+ async proxyStreamOpen(request, target) {
35700
+ const namespace = this.env.AGENT_BUILDER_THREAD;
35701
+ let receiverExists = false;
35702
+ try {
35703
+ const destination = namespace.get(namespace.idFromName(target.threadId));
35704
+ receiverExists = await destination.proxyStreamReceiverExists(target.receiverId);
35705
+ } catch {
35706
+ receiverExists = false;
35707
+ }
35708
+ if (!receiverExists) {
35709
+ return {
35710
+ ok: false,
35711
+ error: "The destination stream receiver is unavailable.",
35712
+ code: "unavailable"
35713
+ };
35714
+ }
35715
+ const opened = await this.openClientStream(request, target);
35542
35716
  if (!opened.ok) {
35543
35717
  const failureData = opened.data;
35544
35718
  const noClient = failureData?.noClient === true;
@@ -35550,82 +35724,23 @@ ${resultContent}${attachmentSummary}`;
35550
35724
  };
35551
35725
  }
35552
35726
  const id = crypto.randomUUID();
35553
- const duplex = opened.duplex;
35554
- const reader = duplex.readable.getReader();
35555
- const writer = duplex.writable.getWriter();
35556
- const entry = {
35557
- writer,
35558
- reader,
35559
- buffer: [],
35560
- done: false,
35561
- error: void 0,
35562
- pump: Promise.resolve(),
35563
- close: (error) => duplex.close(error),
35564
- waiter: null,
35565
- notify: () => {
35566
- const waiter = entry.waiter;
35567
- entry.waiter = null;
35568
- waiter?.();
35569
- }
35570
- };
35571
- entry.pump = (async () => {
35572
- try {
35573
- while (true) {
35574
- const { value, done } = await reader.read();
35575
- if (done) break;
35576
- if (value) {
35577
- entry.buffer.push(value);
35578
- entry.notify();
35579
- }
35580
- }
35581
- } catch (error) {
35582
- entry.error = error instanceof Error ? error.message : String(error);
35583
- } finally {
35584
- entry.done = true;
35585
- entry.notify();
35586
- }
35587
- })();
35588
- void duplex.done.then((d) => {
35589
- entry.done = true;
35590
- if (!d.ok && !entry.error) entry.error = d.error;
35591
- entry.notify();
35727
+ this.eventProxyStreams.set(id, {
35728
+ writer: opened.duplex.writable.getWriter(),
35729
+ close: (error) => opened.duplex.close(error),
35730
+ target
35592
35731
  });
35593
- this.proxyStreams.set(id, entry);
35594
- return { ok: true, id, parkedPull: true };
35732
+ return { ok: true, id, eventDriven: true };
35595
35733
  }
35596
35734
  async proxyStreamWrite(id, dataB64) {
35597
- const entry = this.proxyStreams.get(id);
35735
+ const entry = this.eventProxyStreams.get(id);
35598
35736
  if (!entry) return;
35599
35737
  try {
35600
35738
  await entry.writer.write(decodeStreamChunk(dataB64));
35601
35739
  } catch {
35602
35740
  }
35603
35741
  }
35604
- async proxyStreamPull(id, maxWaitMs) {
35605
- const entry = this.proxyStreams.get(id);
35606
- if (!entry) return { chunks: [], done: true, error: "unknown proxy stream" };
35607
- if (entry.buffer.length === 0 && !entry.done) {
35608
- await new Promise((resolve3) => {
35609
- const deadline = typeof maxWaitMs === "number" && maxWaitMs > 0 ? setTimeout(() => {
35610
- if (entry.waiter === wake) entry.waiter = null;
35611
- resolve3();
35612
- }, maxWaitMs) : null;
35613
- const wake = () => {
35614
- if (deadline) clearTimeout(deadline);
35615
- resolve3();
35616
- };
35617
- entry.waiter = wake;
35618
- });
35619
- }
35620
- const chunks = entry.buffer.splice(0).map((b) => encodeStreamChunk(b));
35621
- if (entry.done && entry.buffer.length === 0) {
35622
- this.proxyStreams.delete(id);
35623
- return { chunks, done: true, error: entry.error };
35624
- }
35625
- return { chunks, done: false };
35626
- }
35627
35742
  async proxyStreamEnd(id) {
35628
- const entry = this.proxyStreams.get(id);
35743
+ const entry = this.eventProxyStreams.get(id);
35629
35744
  if (!entry) return;
35630
35745
  try {
35631
35746
  await entry.writer.close();
@@ -35633,30 +35748,49 @@ ${resultContent}${attachmentSummary}`;
35633
35748
  }
35634
35749
  }
35635
35750
  async proxyStreamAbort(id) {
35636
- const entry = this.proxyStreams.get(id);
35751
+ const entry = this.eventProxyStreams.get(id);
35637
35752
  if (!entry) return;
35638
- this.proxyStreams.delete(id);
35639
- entry.done = true;
35640
- if (!entry.error) entry.error = "aborted";
35641
- entry.notify();
35753
+ this.eventProxyStreams.delete(id);
35642
35754
  try {
35643
35755
  entry.close("aborted");
35644
35756
  } catch {
35645
35757
  }
35646
35758
  }
35759
+ forgetEventProxyStream(clientStreamId, target) {
35760
+ const matchesTarget = (candidate) => candidate.threadId === target.threadId && candidate.receiverId === target.receiverId;
35761
+ for (const [id, entry] of Array.from(this.eventProxyStreams.entries())) {
35762
+ if (id !== clientStreamId && !matchesTarget(entry.target)) continue;
35763
+ this.eventProxyStreams.delete(id);
35764
+ try {
35765
+ entry.writer.releaseLock();
35766
+ } catch {
35767
+ }
35768
+ }
35769
+ }
35647
35770
  /** Settle and remove a client stream exactly once. */
35648
35771
  settleClientStream(id, result, openCode) {
35649
35772
  const state = this.clientStreams.get(id);
35650
35773
  if (!state) return;
35651
35774
  this.clientStreams.delete(id);
35775
+ this.updateBridgeProxyAttachment(state.socket);
35652
35776
  if (state.openTimer) clearTimeout(state.openTimer);
35653
35777
  if (state.opened) state.opened({ ok: false, error: result.error ?? "Stream closed.", code: openCode });
35654
35778
  if (!state.settled) {
35655
35779
  state.settled = true;
35656
- try {
35657
- if (result.ok) state.controller?.close();
35658
- else state.controller?.error(new Error(result.error ?? "Client stream aborted."));
35659
- } catch {
35780
+ if (state.proxyTarget) {
35781
+ const target = state.proxyTarget;
35782
+ const frame = result.ok ? { type: "end" } : { type: "abort", error: result.error ?? "Client stream aborted." };
35783
+ state.delivery = state.delivery.then(async () => {
35784
+ await this.deliverProxyStreamFrame(target, frame);
35785
+ });
35786
+ this.ctx.waitUntil(state.delivery);
35787
+ this.forgetEventProxyStream(id, target);
35788
+ } else {
35789
+ try {
35790
+ if (result.ok) state.controller?.close();
35791
+ else state.controller?.error(new Error(result.error ?? "Client stream aborted."));
35792
+ } catch {
35793
+ }
35660
35794
  }
35661
35795
  state.settleDone(result);
35662
35796
  }
@@ -35699,6 +35833,18 @@ ${resultContent}${attachmentSummary}`;
35699
35833
  }
35700
35834
  case "stream_data": {
35701
35835
  if (typeof parsed.data !== "string") return;
35836
+ if (state.proxyTarget) {
35837
+ const target = state.proxyTarget;
35838
+ const frame = { type: "data", data: parsed.data };
35839
+ state.delivery = state.delivery.then(async () => {
35840
+ const delivered = await this.deliverProxyStreamFrame(target, frame);
35841
+ if (!delivered && this.clientStreams.get(id) === state) {
35842
+ this.abortClientStream(id, "The destination stream receiver is unavailable.");
35843
+ }
35844
+ });
35845
+ this.ctx.waitUntil(state.delivery);
35846
+ return;
35847
+ }
35702
35848
  try {
35703
35849
  state.controller?.enqueue(decodeStreamChunk(parsed.data));
35704
35850
  } catch {
@@ -36526,11 +36672,12 @@ ${resultContent}${attachmentSummary}`;
36526
36672
  if (!parentThreadId) return;
36527
36673
  const parentId = this.env.AGENT_BUILDER_THREAD.idFromName(parentThreadId);
36528
36674
  const parentStub = this.env.AGENT_BUILDER_THREAD.get(parentId);
36529
- const parentCommunication = await this.getParentCommunicationModeForChild(
36675
+ const childRegistryEntry = await this.getParentRegistryEntryForChild(
36530
36676
  parentStub,
36531
36677
  parentThreadId,
36532
36678
  threadId
36533
36679
  );
36680
+ const parentCommunication = childRegistryEntry?.parentCommunication === "explicit" ? "explicit" : "implicit";
36534
36681
  if (parentCommunication !== "explicit") {
36535
36682
  await parentStub.queueMessage(parentThreadId, {
36536
36683
  role: "user",