@standardagents/builder 0.17.0 → 0.17.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/index.js CHANGED
@@ -917,7 +917,7 @@ function resolvePlatformRouting(providerName, env) {
917
917
  var DEFAULT_PLATFORM_PROXY_ORIGIN, PROVIDER_BASE_PATHS;
918
918
  var init_platform_routing = __esm({
919
919
  "src/agents/providers/platform-routing.ts"() {
920
- DEFAULT_PLATFORM_PROXY_ORIGIN = "https://proxy.standardagents.ai";
920
+ DEFAULT_PLATFORM_PROXY_ORIGIN = "https://api.standardagents.ai";
921
921
  PROVIDER_BASE_PATHS = {
922
922
  cloudflare: "/ai/v1"
923
923
  };
@@ -1454,6 +1454,23 @@ function convertUsage(usage) {
1454
1454
  provider: usage.provider
1455
1455
  };
1456
1456
  }
1457
+ function inferProviderFromModelId(model) {
1458
+ const trimmed = model?.trim();
1459
+ if (!trimmed) return null;
1460
+ const slashIndex = trimmed.indexOf("/");
1461
+ if (slashIndex <= 0) return null;
1462
+ return trimmed.slice(0, slashIndex);
1463
+ }
1464
+ function inferInitialActualProvider(providerName, modelDef) {
1465
+ if (providerName !== "openrouter") return null;
1466
+ return inferProviderFromModelId(modelDef.model);
1467
+ }
1468
+ function normalizeProviderMetadataValue(value) {
1469
+ if (typeof value !== "string") return void 0;
1470
+ const trimmed = value.trim();
1471
+ if (!trimmed || trimmed.toLowerCase() === "unknown") return void 0;
1472
+ return trimmed;
1473
+ }
1457
1474
  var NON_VISION_PLACEHOLDER_TEXT, LLMRequest;
1458
1475
  var init_LLMRequest = __esm({
1459
1476
  "src/agents/LLMRequest.ts"() {
@@ -1622,6 +1639,7 @@ var init_LLMRequest = __esm({
1622
1639
  console.error("Failed to get provider name:", err);
1623
1640
  }
1624
1641
  const { requestBody, visionFiltering } = buildRequestBody(context, modelDef);
1642
+ const initialActualProvider = inferInitialActualProvider(providerName, modelDef);
1625
1643
  const requestBodyForLog = {
1626
1644
  ...requestBody,
1627
1645
  messages: stripBase64FromMessages(
@@ -1640,6 +1658,7 @@ var init_LLMRequest = __esm({
1640
1658
  id,
1641
1659
  message_id: state.rootMessageId || crypto.randomUUID(),
1642
1660
  provider: providerName || this.getProviderFromModel(modelId),
1661
+ actual_provider: initialActualProvider ?? void 0,
1643
1662
  model: modelId,
1644
1663
  model_name: modelDef.model,
1645
1664
  endpoint: "chat.completions",
@@ -1660,14 +1679,15 @@ var init_LLMRequest = __esm({
1660
1679
  await state.storage.sql.exec(
1661
1680
  `
1662
1681
  INSERT INTO logs (
1663
- id, message_id, provider, model, model_name, endpoint,
1682
+ id, message_id, provider, actual_provider, model, model_name, endpoint,
1664
1683
  request_body, tools_available, message_history_length, prompt_name,
1665
1684
  parent_log_id, retry_of_log_id, tools_schema, system_prompt, is_complete, created_at
1666
- ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)
1685
+ ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)
1667
1686
  `,
1668
1687
  logData.id,
1669
1688
  logData.message_id,
1670
1689
  logData.provider,
1690
+ logData.actual_provider,
1671
1691
  logData.model,
1672
1692
  logData.model_name,
1673
1693
  logData.endpoint,
@@ -1919,7 +1939,7 @@ var init_LLMRequest = __esm({
1919
1939
  const calculatedCost = calculateUsageCost(response.usage, resolvedPricing);
1920
1940
  const providerReportedCost = typeof response.usage.cost === "number" ? response.usage.cost : typeof response.usage.cost === "string" ? parseFloat(response.usage.cost) : null;
1921
1941
  const cost_total = providerReportedCost != null && !Number.isNaN(providerReportedCost) ? providerReportedCost : calculatedCost?.costTotal ?? null;
1922
- const actualProvider = response.usage.provider;
1942
+ const actualProvider = normalizeProviderMetadataValue(response.usage.provider) ?? null;
1923
1943
  const aggregateResponse = response._aggregate_response;
1924
1944
  const responseBody = aggregateResponse ? JSON.stringify(aggregateResponse) : JSON.stringify(response);
1925
1945
  const providerTools = [];
@@ -2347,8 +2367,13 @@ ${errorStack}` : ""}${errorDetails}`;
2347
2367
  }).catch((err) => {
2348
2368
  console.error("Async metadata fetch failed (non-fatal):", err);
2349
2369
  });
2350
- state.rootState.pendingMetadataPromises = state.rootState.pendingMetadataPromises || [];
2351
- state.rootState.pendingMetadataPromises.push(metadataPromise);
2370
+ const rootState = state.rootState || state;
2371
+ rootState.pendingMetadataPromises = rootState.pendingMetadataPromises || [];
2372
+ rootState.pendingMetadataPromises.push(metadataPromise);
2373
+ if (rootState !== state) {
2374
+ state.pendingMetadataPromises = state.pendingMetadataPromises || [];
2375
+ state.pendingMetadataPromises.push(metadataPromise);
2376
+ }
2352
2377
  }
2353
2378
  /**
2354
2379
  * Update log record with async metadata from provider.
@@ -2358,9 +2383,12 @@ ${errorStack}` : ""}${errorDetails}`;
2358
2383
  const updates = [];
2359
2384
  const values = [];
2360
2385
  let paramIndex = 1;
2361
- if (metadata.actual_provider !== void 0) {
2386
+ const actualProvider = normalizeProviderMetadataValue(
2387
+ metadata.actual_provider ?? metadata.actualProvider ?? metadata.providerName
2388
+ );
2389
+ if (actualProvider !== void 0) {
2362
2390
  updates.push(`actual_provider = ?${paramIndex++}`);
2363
- values.push(metadata.actual_provider);
2391
+ values.push(actualProvider);
2364
2392
  }
2365
2393
  if (metadata.generation_cost !== void 0) {
2366
2394
  updates.push(`cost_total = ?${paramIndex++}`);
@@ -2378,7 +2406,7 @@ ${errorStack}` : ""}${errorDetails}`;
2378
2406
  log_id: logId,
2379
2407
  data: {
2380
2408
  id: logId,
2381
- ...metadata.actual_provider !== void 0 && { actual_provider: metadata.actual_provider },
2409
+ ...actualProvider !== void 0 && { actual_provider: actualProvider },
2382
2410
  ...metadata.generation_cost !== void 0 && { cost_total: metadata.generation_cost }
2383
2411
  }
2384
2412
  });
@@ -3104,6 +3132,11 @@ var init_ToolExecutor = __esm({
3104
3132
  state.sequence.queue.push(...toolCalls);
3105
3133
  state.sequence.isHandling = true;
3106
3134
  while (state.sequence.queue.length > 0) {
3135
+ if (state.stopped) {
3136
+ state.sequence.queue = [];
3137
+ state.sequence.isHandling = false;
3138
+ break;
3139
+ }
3107
3140
  if (await state.thread.instance.shouldStop() || state.abortController?.signal.aborted) {
3108
3141
  state.sequence.queue = [];
3109
3142
  state.sequence.isHandling = false;
@@ -3132,7 +3165,17 @@ var init_ToolExecutor = __esm({
3132
3165
  });
3133
3166
  continue;
3134
3167
  }
3135
- const result = await this.executeToolCall(call, state, toolMessageId);
3168
+ const previousToolCall = state.currentToolCall;
3169
+ state.currentToolCall = {
3170
+ id: call.id,
3171
+ name: call.function.name
3172
+ };
3173
+ let result;
3174
+ try {
3175
+ result = await this.executeToolCall(call, state, toolMessageId);
3176
+ } finally {
3177
+ state.currentToolCall = previousToolCall;
3178
+ }
3136
3179
  if (result.status === "error") {
3137
3180
  const error = new Error(result.error || "Tool execution failed");
3138
3181
  if (result.stack) {
@@ -5001,6 +5044,11 @@ ${attachmentPaths}`;
5001
5044
  }
5002
5045
  }
5003
5046
  const toolStatus = processedResult.status === "success" ? "success" : "error";
5047
+ const parentNotifications = this.consumeParentNotificationsForToolCall(
5048
+ state,
5049
+ call
5050
+ );
5051
+ const notificationContent = parentNotifications[parentNotifications.length - 1]?.content;
5004
5052
  let message = {
5005
5053
  id: messageId,
5006
5054
  role: "tool",
@@ -5014,15 +5062,20 @@ ${attachmentPaths}`;
5014
5062
  parent_id: state.parentMessageId || null,
5015
5063
  depth: state.depth,
5016
5064
  attachments: attachmentRefs && attachmentRefs.length > 0 ? JSON.stringify(attachmentRefs) : null,
5017
- subagent_id: typeof processedResult.subagent_id === "string" && processedResult.subagent_id.trim().length > 0 ? processedResult.subagent_id.trim() : null
5065
+ subagent_id: typeof processedResult.subagent_id === "string" && processedResult.subagent_id.trim().length > 0 ? processedResult.subagent_id.trim() : null,
5066
+ metadata: parentNotifications.length > 0 ? {
5067
+ parent_notification: true,
5068
+ parent_notifications: parentNotifications,
5069
+ parent_notification_content: notificationContent
5070
+ } : void 0
5018
5071
  };
5019
5072
  message = await FlowEngine2.runBeforeCreateMessageHook(state, message);
5020
5073
  message.created_at = Date.now() * TIMESTAMP_MULTIPLIER;
5021
5074
  const currentPrompt = state.promptPath.length > 0 ? state.promptPath[state.promptPath.length - 1] : null;
5022
5075
  await state.storage.sql.exec(
5023
5076
  `
5024
- INSERT INTO messages (id, role, content, name, tool_call_id, created_at, tool_status, parent_id, depth, attachments, subagent_id, prompt)
5025
- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)
5077
+ INSERT INTO messages (id, role, content, name, tool_call_id, created_at, tool_status, parent_id, depth, attachments, subagent_id, prompt, metadata)
5078
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)
5026
5079
  `,
5027
5080
  message.id,
5028
5081
  message.role,
@@ -5035,7 +5088,8 @@ ${attachmentPaths}`;
5035
5088
  message.depth,
5036
5089
  message.attachments,
5037
5090
  message.subagent_id ?? null,
5038
- currentPrompt
5091
+ currentPrompt,
5092
+ message.metadata ? JSON.stringify(message.metadata) : null
5039
5093
  );
5040
5094
  await FlowEngine2.runAfterCreateMessageHook(state, message);
5041
5095
  const parentLogId = state.currentLogId || null;
@@ -5100,6 +5154,23 @@ ${attachmentPaths}`;
5100
5154
  await this.logError(state, error, `storeToolResult:${call.function.name}`);
5101
5155
  }
5102
5156
  }
5157
+ static consumeParentNotificationsForToolCall(state, call) {
5158
+ const pending = state.parentNotifications ?? [];
5159
+ if (pending.length === 0) return [];
5160
+ const matched = [];
5161
+ const remaining = [];
5162
+ for (const notification of pending) {
5163
+ const matchesCallId = notification.toolCallId === call.id;
5164
+ const matchesToolName = !notification.toolCallId && notification.toolName === call.function.name;
5165
+ if (matchesCallId || matchesToolName) {
5166
+ matched.push(notification);
5167
+ } else {
5168
+ remaining.push(notification);
5169
+ }
5170
+ }
5171
+ state.parentNotifications = remaining;
5172
+ return matched;
5173
+ }
5103
5174
  static async createSilentGeneratedAssetPathMessagesFromToolResult(state, sourceToolMessage, refs, enabled) {
5104
5175
  if (!enabled || !refs || refs.length === 0) {
5105
5176
  return;
@@ -5561,6 +5632,8 @@ var init_FlowEngine = __esm({
5561
5632
  stoppedBy: state.stoppedBy,
5562
5633
  stopReason: state.stopReason,
5563
5634
  stopReasonCode: state.stopReasonCode,
5635
+ sessionStopped: state.sessionStopped,
5636
+ sessionStopResult: state.sessionStopResult,
5564
5637
  stepCount: state.stepCount,
5565
5638
  stream: state.stream.httpStream
5566
5639
  };
@@ -5760,6 +5833,8 @@ var init_FlowEngine = __esm({
5760
5833
  stoppedBy: stateInput.stoppedBy,
5761
5834
  stopReason: stateInput.stopReason,
5762
5835
  stopReasonCode: stateInput.stopReasonCode,
5836
+ sessionStopped: stateInput.sessionStopped,
5837
+ sessionStopResult: stateInput.sessionStopResult,
5763
5838
  messageHistory: [],
5764
5839
  sequence: stateInput.sequence || {
5765
5840
  queue: [],
@@ -11729,34 +11804,79 @@ var init_ThreadStateImpl = __esm({
11729
11804
  this._metadata.tenvs = nextEnv;
11730
11805
  }
11731
11806
  async notifyParent(content) {
11732
- if (!content || !content.trim()) {
11807
+ const trimmedContent = content?.trim();
11808
+ if (!trimmedContent) {
11733
11809
  throw new Error("notifyParent requires non-empty content");
11734
11810
  }
11735
- const { parentThreadId, parentStub } = await this._getParentThreadStub();
11736
- if (typeof parentStub.queueMessage !== "function") {
11737
- throw new Error("notifyParent is not supported by this thread runtime");
11811
+ if (this._flowState) {
11812
+ const currentToolCall = this._flowState.currentToolCall;
11813
+ const notification = {
11814
+ content: trimmedContent,
11815
+ toolCallId: currentToolCall?.id ?? null,
11816
+ toolName: currentToolCall?.name ?? this._flowState.active?.tool ?? null,
11817
+ created_at: Date.now() * 1e3
11818
+ };
11819
+ this._flowState.parentNotifications = [
11820
+ ...this._flowState.parentNotifications ?? [],
11821
+ notification
11822
+ ];
11823
+ }
11824
+ const parentTarget = await this._getOptionalParentThreadStub();
11825
+ if (parentTarget && typeof parentTarget.parentStub.queueMessage === "function") {
11826
+ await parentTarget.parentStub.queueMessage(parentTarget.parentThreadId, {
11827
+ role: "user",
11828
+ content: trimmedContent,
11829
+ silent: true,
11830
+ metadata: { subagent_id: this.threadId }
11831
+ });
11738
11832
  }
11739
- await parentStub.queueMessage(parentThreadId, {
11740
- role: "user",
11741
- content: content.trim(),
11742
- silent: true,
11743
- metadata: { subagent_id: this.threadId }
11744
- });
11745
11833
  }
11746
11834
  async setStatus(status) {
11747
11835
  if (!status || !status.trim()) {
11748
11836
  throw new Error("setStatus requires non-empty status");
11749
11837
  }
11750
- const { parentThreadId, parentStub } = await this._getParentThreadStub();
11751
- if (typeof parentStub.updateChildRegistryStatus !== "function") {
11752
- throw new Error("setStatus is not supported by this thread runtime");
11838
+ const parentTarget = await this._getOptionalParentThreadStub();
11839
+ if (!parentTarget || typeof parentTarget.parentStub.updateChildRegistryStatus !== "function") {
11840
+ return;
11753
11841
  }
11754
- await parentStub.updateChildRegistryStatus(
11755
- parentThreadId,
11842
+ await parentTarget.parentStub.updateChildRegistryStatus(
11843
+ parentTarget.parentThreadId,
11756
11844
  this.threadId,
11757
11845
  status.trim()
11758
11846
  );
11759
11847
  }
11848
+ async stopSession(options = {}) {
11849
+ if (!this._flowState) {
11850
+ throw new Error("stopSession requires active execution");
11851
+ }
11852
+ const notifyParent = options.notifyParent?.trim();
11853
+ if (options.notifyParent !== void 0 && !notifyParent) {
11854
+ throw new Error("stopSession notifyParent requires non-empty content");
11855
+ }
11856
+ const status = options.status?.trim();
11857
+ if (options.status !== void 0 && !status) {
11858
+ throw new Error("stopSession status requires non-empty status");
11859
+ }
11860
+ if (notifyParent) {
11861
+ await this.notifyParent(notifyParent);
11862
+ }
11863
+ if (status) {
11864
+ await this.setStatus(status);
11865
+ }
11866
+ const result = options.result?.trim() || "Session stopped.";
11867
+ this._flowState.sessionStopped = true;
11868
+ this._flowState.sessionStopResult = result;
11869
+ this._flowState.stopped = true;
11870
+ this._flowState.stoppedBy = this._flowState.currentSide;
11871
+ this._flowState.stopReason = "Session stopped by state.stopSession()";
11872
+ this._flowState.stopReasonCode = "state_stop_session";
11873
+ this._flowState.emitTelemetry?.({
11874
+ type: "stopped",
11875
+ reason: this._flowState.stopReason,
11876
+ side: this._flowState.currentSide,
11877
+ timestamp: Date.now()
11878
+ });
11879
+ }
11760
11880
  async getChildThread(referenceId) {
11761
11881
  const agentBuilder = this._getAgentBuilderStub();
11762
11882
  const child = await agentBuilder.getThread(referenceId);
@@ -12169,19 +12289,47 @@ var init_ThreadStateImpl = __esm({
12169
12289
  return Object.keys(normalized).length > 0 ? normalized : null;
12170
12290
  }
12171
12291
  async _getParentThreadId() {
12292
+ const parent = await this._getOptionalParentThreadId();
12293
+ if (!parent) {
12294
+ throw new Error(`Thread ${this.threadId} has no parent thread`);
12295
+ }
12296
+ return parent;
12297
+ }
12298
+ async _getOptionalParentThreadId() {
12172
12299
  const metadataParent = typeof this._metadata.parent === "string" && this._metadata.parent.trim().length > 0 ? this._metadata.parent.trim() : null;
12173
12300
  if (metadataParent) {
12174
12301
  return metadataParent;
12175
12302
  }
12176
- const agentBuilder = this._getAgentBuilderStub();
12177
- const thread = await agentBuilder.getThread(this.threadId);
12303
+ let thread = null;
12304
+ try {
12305
+ const agentBuilder = this._getAgentBuilderStub();
12306
+ thread = await agentBuilder.getThread(this.threadId);
12307
+ } catch {
12308
+ return null;
12309
+ }
12178
12310
  const parent = thread && typeof thread.parent === "string" && thread.parent.trim().length > 0 ? thread.parent.trim() : null;
12179
- if (!parent) {
12180
- throw new Error(`Thread ${this.threadId} has no parent thread`);
12311
+ if (parent) {
12312
+ this._metadata.parent = parent;
12181
12313
  }
12182
- this._metadata.parent = parent;
12183
12314
  return parent;
12184
12315
  }
12316
+ async _getOptionalParentThreadStub() {
12317
+ const parentThreadId = await this._getOptionalParentThreadId();
12318
+ if (!parentThreadId) {
12319
+ return null;
12320
+ }
12321
+ try {
12322
+ const threadNamespace = this._threadInstance.env.AGENT_BUILDER_THREAD;
12323
+ if (!threadNamespace || typeof threadNamespace.idFromName !== "function" || typeof threadNamespace.get !== "function") {
12324
+ return null;
12325
+ }
12326
+ const durableId = threadNamespace.idFromName(parentThreadId);
12327
+ const parentStub = threadNamespace.get(durableId);
12328
+ return { parentThreadId, parentStub };
12329
+ } catch {
12330
+ return null;
12331
+ }
12332
+ }
12185
12333
  async _getParentThreadStub() {
12186
12334
  const parentThreadId = await this._getParentThreadId();
12187
12335
  const durableId = this._threadInstance.env.AGENT_BUILDER_THREAD.idFromName(parentThreadId);
@@ -18882,6 +19030,18 @@ function readRawRequestBody(req) {
18882
19030
  req.on("error", reject);
18883
19031
  });
18884
19032
  }
19033
+ function resolveContainedFile(baseDir, requestPath) {
19034
+ const resolvedBase = path8__default.resolve(baseDir);
19035
+ const resolved = path8__default.resolve(resolvedBase, `.${path8__default.sep}${requestPath.replace(/^\/+/, "")}`);
19036
+ if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase + path8__default.sep)) {
19037
+ return null;
19038
+ }
19039
+ try {
19040
+ return fs8__default.existsSync(resolved) && fs8__default.statSync(resolved).isFile() ? resolved : null;
19041
+ } catch {
19042
+ return null;
19043
+ }
19044
+ }
18885
19045
  function injectUiConfigIntoHtml(htmlContent) {
18886
19046
  const configScript = `<script>window.__AGENTBUILDER_CONFIG__ = { devMode: true };</script>`;
18887
19047
  return htmlContent.replace(/\/agents\//g, "/").replace("</head>", `${configScript}</head>`);
@@ -19024,6 +19184,15 @@ function createDevMiddleware(server, context) {
19024
19184
  if (isStaticAsset) {
19025
19185
  const cleanUrl = clientPath.split("?")[0];
19026
19186
  filePath = path8__default.join(clientDir, cleanUrl);
19187
+ if (!fs8__default.existsSync(filePath)) {
19188
+ const projectPublicFile = resolveContainedFile(
19189
+ path8__default.resolve(server.config.root, "public"),
19190
+ cleanUrl
19191
+ );
19192
+ if (projectPublicFile) {
19193
+ filePath = projectPublicFile;
19194
+ }
19195
+ }
19027
19196
  } else {
19028
19197
  filePath = path8__default.join(clientDir, "index.html");
19029
19198
  }
@@ -20320,6 +20489,27 @@ export { CodeExecutionBridge, DurableAgentBuilder, DurableThread };
20320
20489
  }
20321
20490
  }
20322
20491
  copyRecursive(clientDir, mountDir);
20492
+ const projectPublicDir = path8__default.resolve(process.cwd(), "public");
20493
+ if (fs8__default.existsSync(projectPublicDir)) {
20494
+ let copyPublicRecursive2 = function(src, dest) {
20495
+ const entries = fs8__default.readdirSync(src, { withFileTypes: true });
20496
+ for (const entry of entries) {
20497
+ const srcPath = path8__default.join(src, entry.name);
20498
+ const destPath = path8__default.join(dest, entry.name);
20499
+ if (entry.isDirectory()) {
20500
+ fs8__default.mkdirSync(destPath, { recursive: true });
20501
+ copyPublicRecursive2(srcPath, destPath);
20502
+ } else if (fs8__default.existsSync(destPath)) {
20503
+ console.warn(
20504
+ `[agentbuilder] Skipping public/ file that collides with a built UI asset: ${destPath}`
20505
+ );
20506
+ } else {
20507
+ fs8__default.copyFileSync(srcPath, destPath);
20508
+ }
20509
+ }
20510
+ };
20511
+ copyPublicRecursive2(projectPublicDir, mountDir);
20512
+ }
20323
20513
  }
20324
20514
  };
20325
20515
  return [
@@ -22233,6 +22423,7 @@ function createEndpointThreadStateBridge(state) {
22233
22423
  setEnv: (propertyName, value, options) => state.setEnv(propertyName, value, options),
22234
22424
  notifyParent: (content) => state.notifyParent(content),
22235
22425
  setStatus: (status) => state.setStatus(status),
22426
+ stopSession: (options) => state.stopSession(options),
22236
22427
  queueTool: (toolName, args) => state.queueTool(toolName, args),
22237
22428
  invokeTool: (toolName, args) => state.invokeTool(toolName, args),
22238
22429
  scheduleEffect: (name, args, delay) => state.scheduleEffect(name, args, delay),
@@ -23452,6 +23643,15 @@ var DurableThread = class extends DurableObject {
23452
23643
  const terminalToolNames = await this.getTerminalSessionToolNames(agentId);
23453
23644
  const sessionFailToolNames = terminalToolNames.fail;
23454
23645
  const hasTerminalSessionTools = terminalToolNames.all.size > 0;
23646
+ if (flowResult.sessionStopped) {
23647
+ await emitParentHandoffStatus("result");
23648
+ return {
23649
+ status: "success",
23650
+ result: flowResult.sessionStopResult?.trim() || "Session stopped.",
23651
+ attachments: void 0,
23652
+ terminal: true
23653
+ };
23654
+ }
23455
23655
  const latestInRunCursor = await this.ctx.storage.sql.exec(
23456
23656
  `
23457
23657
  SELECT role, name, content, tool_status, attachments
@@ -24031,27 +24231,30 @@ ${paths.join("\n")}`;
24031
24231
  if (!parentThreadId || !flowResult.stopped) {
24032
24232
  return;
24033
24233
  }
24234
+ const sessionStopped = flowResult.sessionStopped === true;
24034
24235
  const terminalToolNames = await this.getTerminalSessionToolNames(agentName);
24035
24236
  const sessionFailToolNames = terminalToolNames.fail;
24036
24237
  const hasTerminalSessionTools = terminalToolNames.all.size > 0;
24037
- const latestTerminalMessage = hasTerminalSessionTools ? await this.getLatestTerminalSessionMessageAfter(
24238
+ const latestTerminalMessage = !sessionStopped && hasTerminalSessionTools ? await this.getLatestTerminalSessionMessageAfter(
24038
24239
  previousTopLevelMessageId,
24039
24240
  terminalToolNames.all
24040
24241
  ) : null;
24041
- const latest = latestTerminalMessage ?? await this.getLatestTopLevelMessage();
24042
- if (!latest && !hasTerminalSessionTools) {
24242
+ const latest = sessionStopped ? null : latestTerminalMessage ?? await this.getLatestTopLevelMessage();
24243
+ if (!sessionStopped && !latest && !hasTerminalSessionTools) {
24043
24244
  return;
24044
24245
  }
24045
- if (latest && latest.id === previousTopLevelMessageId && !hasTerminalSessionTools) {
24246
+ if (!sessionStopped && latest && latest.id === previousTopLevelMessageId && !hasTerminalSessionTools) {
24046
24247
  return;
24047
24248
  }
24048
24249
  const isTerminalSessionMessage = !!latest && latest.role === "tool" && !!latest.name && terminalToolNames.all.has(latest.name);
24049
24250
  const isFailSessionTerminal = isTerminalSessionMessage && !!latest.name && sessionFailToolNames.has(latest.name);
24050
24251
  const isSuccessfulTerminalSessionMessage = isTerminalSessionMessage && latest?.tool_status !== "error";
24051
- const isFailureWithoutTerminalMessage = hasTerminalSessionTools && !isSuccessfulTerminalSessionMessage;
24252
+ const isFailureWithoutTerminalMessage = !sessionStopped && hasTerminalSessionTools && !isSuccessfulTerminalSessionMessage;
24052
24253
  const isErrorTerminal = latest?.tool_status === "error" || isFailSessionTerminal || isFailureWithoutTerminalMessage;
24053
24254
  let refs = [];
24054
- if (hasTerminalSessionTools) {
24255
+ if (sessionStopped) {
24256
+ refs = [];
24257
+ } else if (hasTerminalSessionTools) {
24055
24258
  refs = this.parseAttachmentRefsJson(latestTerminalMessage?.attachments ?? null) ?? [];
24056
24259
  } else if (latest?.attachments) {
24057
24260
  refs = this.parseAttachmentRefsJson(latest.attachments) ?? [];
@@ -24069,7 +24272,7 @@ ${paths.join("\n")}`;
24069
24272
  parentStub,
24070
24273
  `from-subagent-${threadId}`
24071
24274
  );
24072
- const resultContent = isFailureWithoutTerminalMessage ? this.buildMissingTerminalSessionFailure(flowResult) : latest?.content || (isErrorTerminal ? "Subagent execution failed." : "Subagent completed.");
24275
+ const resultContent = sessionStopped ? flowResult.sessionStopResult?.trim() || "Session stopped." : isFailureWithoutTerminalMessage ? this.buildMissingTerminalSessionFailure(flowResult) : latest?.content || (isErrorTerminal ? "Subagent execution failed." : "Subagent completed.");
24073
24276
  const attachmentSummary = this.formatSubagentAttachmentPathSummary(copiedRefs);
24074
24277
  const messageContent = isErrorTerminal ? `Subagent (reference: ${threadId}) has reported a failure:
24075
24278
 
@@ -24085,7 +24288,7 @@ ${resultContent}${attachmentSummary}`;
24085
24288
  metadata: { subagent_id: threadId }
24086
24289
  });
24087
24290
  }
24088
- const terminalChildRun = hasTerminalSessionTools;
24291
+ const terminalChildRun = sessionStopped || hasTerminalSessionTools;
24089
24292
  await parentStub.updateChildRegistryStatus(
24090
24293
  parentThreadId,
24091
24294
  threadId,