@botbotgo/agent-harness 0.0.367 → 0.0.370

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.
@@ -371,6 +371,16 @@ export async function streamChatMessage(input) {
371
371
  renderContentBlocks(delta.contentBlocks, delta.agentId);
372
372
  return;
373
373
  }
374
+ if (delta.type === "plan.step") {
375
+ latestAgentId = delta.agentId || latestAgentId;
376
+ const item = delta.item;
377
+ const status = typeof item?.status === "string" ? item.status : "unknown";
378
+ const content = typeof item?.content === "string" ? item.content : "";
379
+ if ((input.showToolResults ?? true) && !input.requestEvents && content.trim().length > 0) {
380
+ writeChatStderr(`\n[${formatPerfClock(Date.now())} +${formatElapsed(Date.now())}]${formatAgentProgressLabel(delta.agentId)} [todo:${status}] ${content}\n`);
381
+ }
382
+ return;
383
+ }
374
384
  if (delta.type === "tool.result") {
375
385
  latestAgentId = delta.agentId || latestAgentId;
376
386
  if ((input.showToolResults ?? true) && !input.requestEvents) {
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.367";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.370";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-30";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.367";
1
+ export const AGENT_HARNESS_VERSION = "0.0.370";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-30";
@@ -59,6 +59,8 @@ export declare class AgentRuntimeAdapter {
59
59
  }): Promise<RequestResult>;
60
60
  private tryDelegateWithCompactRouter;
61
61
  private buildCompactDelegationReport;
62
+ private formatCompactDelegationReportForDisplay;
63
+ private streamDelegateWithCompactRouter;
62
64
  stream(binding: CompiledAgentBinding, input: MessageContent, sessionId: string, history?: TranscriptMessage[], options?: {
63
65
  context?: Record<string, unknown>;
64
66
  state?: Record<string, unknown>;
@@ -1139,6 +1139,224 @@ export class AgentRuntimeAdapter {
1139
1139
  report,
1140
1140
  };
1141
1141
  }
1142
+ formatCompactDelegationReportForDisplay(report) {
1143
+ const readStringArray = (key) => {
1144
+ const value = report[key];
1145
+ return Array.isArray(value)
1146
+ ? value.filter((item) => typeof item === "string" && item.trim().length > 0)
1147
+ : [];
1148
+ };
1149
+ const lines = [];
1150
+ const reportText = typeof report.report === "string" ? report.report.trim() : "";
1151
+ if (reportText) {
1152
+ lines.push(reportText);
1153
+ }
1154
+ const sections = [
1155
+ ["Routing", readStringArray("routing")],
1156
+ ["TODO Trace", readStringArray("todoTrace")],
1157
+ ["Step Results", readStringArray("stepResults")],
1158
+ ["Summary", readStringArray("summary")],
1159
+ ["Findings", readStringArray("findings")],
1160
+ ["Blockers", readStringArray("blockers")],
1161
+ ["Next Actions", readStringArray("nextActions")],
1162
+ ];
1163
+ for (const [title, items] of sections) {
1164
+ if (items.length === 0 || (items.length === 1 && items[0]?.toLowerCase() === "none")) {
1165
+ continue;
1166
+ }
1167
+ if (lines.length > 0) {
1168
+ lines.push("");
1169
+ }
1170
+ lines.push(`## ${title}`);
1171
+ lines.push(...items.map((item) => `- ${item}`));
1172
+ }
1173
+ return lines.join("\n");
1174
+ }
1175
+ async *streamDelegateWithCompactRouter(binding, input, sessionId, requestId, options = {}) {
1176
+ if (!isDelegationOnlyDeepAgentBinding(binding) || !this.options.bindingResolver) {
1177
+ return null;
1178
+ }
1179
+ const primaryModel = getBindingPrimaryModel(binding);
1180
+ if (!primaryModel) {
1181
+ return null;
1182
+ }
1183
+ const requestText = extractMessageText(input).trim();
1184
+ if (!requestText) {
1185
+ return null;
1186
+ }
1187
+ const subagents = getBindingSubagents(binding);
1188
+ const subagentNames = new Set(subagents.map((subagent) => subagent.name));
1189
+ const subagentCatalog = subagents
1190
+ .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
1191
+ .join("\n");
1192
+ const routingPolicy = getBindingSystemPrompt(binding);
1193
+ const prompt = [
1194
+ primaryModel.init?.think === false ? "/no_think" : "",
1195
+ "You are selecting a subagent for a delegation-only agent.",
1196
+ "Choose exactly one listed subagent when it can responsibly handle the request.",
1197
+ routingPolicy ? "Agent routing policy:" : "",
1198
+ routingPolicy ?? "",
1199
+ "Return only JSON with this shape:",
1200
+ "{\"subagent_type\":\"<listed subagent name>\"}",
1201
+ "If no listed subagent can handle the request, return only:",
1202
+ "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
1203
+ "Available subagents:",
1204
+ subagentCatalog,
1205
+ "User request:",
1206
+ requestText,
1207
+ ].filter(Boolean).join("\n\n");
1208
+ const model = await this.resolveModel(primaryModel);
1209
+ if (typeof model.invoke !== "function") {
1210
+ return null;
1211
+ }
1212
+ const invokeRouter = async (activePrompt, operationName) => this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(activePrompt, resolveLangChainInvocationConfig(binding, {
1213
+ sessionId,
1214
+ requestId,
1215
+ context: options.context,
1216
+ toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
1217
+ ...options,
1218
+ sessionId,
1219
+ requestId,
1220
+ }),
1221
+ })), resolveBindingTimeout(binding), operationName, "invoke"));
1222
+ const routerPrompts = [
1223
+ prompt,
1224
+ [
1225
+ prompt,
1226
+ "Your previous router output was invalid.",
1227
+ "Return only one JSON object now. Do not include prose, markdown, labels, or tool-call wrappers.",
1228
+ ].join("\n\n"),
1229
+ [
1230
+ primaryModel.init?.think === false ? "/no_think" : "",
1231
+ "Select one subagent from this exact list:",
1232
+ Array.from(subagentNames).join(", "),
1233
+ "Return JSON only:",
1234
+ "{\"subagent_type\":\"<one exact listed name>\"}",
1235
+ "User request:",
1236
+ requestText,
1237
+ ].filter(Boolean).join("\n\n"),
1238
+ [
1239
+ primaryModel.init?.think === false ? "/no_think" : "",
1240
+ "JSON only. Pick a listed subagent or refuse.",
1241
+ "Listed subagents:",
1242
+ Array.from(subagentNames).join(", "),
1243
+ "Allowed outputs:",
1244
+ "{\"subagent_type\":\"<listed name>\"}",
1245
+ "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
1246
+ "Request:",
1247
+ requestText,
1248
+ ].filter(Boolean).join("\n\n"),
1249
+ ];
1250
+ let selection = null;
1251
+ let previousRawText = "";
1252
+ for (let index = 0; index < routerPrompts.length && !selection; index += 1) {
1253
+ const activePrompt = index <= 1 || !previousRawText
1254
+ ? routerPrompts[index]
1255
+ : [routerPrompts[index], "Previous output:", previousRawText].join("\n\n");
1256
+ const raw = await invokeRouter(activePrompt, index === 0 ? "delegation router invoke" : `delegation router retry invoke ${index}`);
1257
+ previousRawText = readModelText(raw);
1258
+ selection = parseCompactRouterSelection(previousRawText, subagentNames);
1259
+ }
1260
+ if (selection?.refusedReason) {
1261
+ return {
1262
+ toolOutput: selection.refusedReason,
1263
+ delegatedSubagentType: null,
1264
+ delegatedResult: {
1265
+ sessionId,
1266
+ requestId,
1267
+ agentId: binding.agent.id,
1268
+ state: "failed",
1269
+ output: selection.refusedReason,
1270
+ finalMessageText: selection.refusedReason,
1271
+ },
1272
+ };
1273
+ }
1274
+ let subagentType = selection?.subagentType ?? "";
1275
+ if (!subagentNames.has(subagentType)) {
1276
+ const fallbackSubagent = subagentNames.values().next().value;
1277
+ if (typeof fallbackSubagent === "string" && fallbackSubagent) {
1278
+ subagentType = fallbackSubagent;
1279
+ }
1280
+ else {
1281
+ return null;
1282
+ }
1283
+ }
1284
+ const selectedBinding = this.options.bindingResolver(subagentType);
1285
+ if (!selectedBinding) {
1286
+ return null;
1287
+ }
1288
+ yield {
1289
+ kind: "commentary",
1290
+ content: `Delegating to ${subagentType}.`,
1291
+ agentId: binding.agent.id,
1292
+ };
1293
+ yield {
1294
+ kind: "commentary",
1295
+ content: "Starting delegated execution.",
1296
+ agentId: selectedBinding.agent.id,
1297
+ };
1298
+ const childRequestId = `${requestId}:${subagentType}`;
1299
+ const executedToolResults = [];
1300
+ let output = "";
1301
+ try {
1302
+ for await (const chunk of this.stream(selectedBinding, requestText, sessionId, [], {
1303
+ context: options.context,
1304
+ state: options.state,
1305
+ files: options.files,
1306
+ requestId: childRequestId,
1307
+ memoryContext: options.memoryContext,
1308
+ profiling: options.profiling,
1309
+ toolRuntimeContext: options.toolRuntimeContext,
1310
+ })) {
1311
+ if (typeof chunk === "string") {
1312
+ output += chunk;
1313
+ continue;
1314
+ }
1315
+ if (chunk.kind === "content") {
1316
+ output += chunk.content ?? "";
1317
+ continue;
1318
+ }
1319
+ if (chunk.kind === "tool-result") {
1320
+ executedToolResults.push({
1321
+ toolName: chunk.toolName,
1322
+ output: chunk.output,
1323
+ ...(chunk.isError !== undefined ? { isError: chunk.isError } : {}),
1324
+ });
1325
+ }
1326
+ yield { ...chunk, agentId: chunk.agentId ?? selectedBinding.agent.id };
1327
+ }
1328
+ }
1329
+ catch (error) {
1330
+ output = error instanceof Error ? error.message : String(error);
1331
+ return {
1332
+ toolOutput: output,
1333
+ delegatedSubagentType: subagentType,
1334
+ delegatedResult: {
1335
+ sessionId,
1336
+ requestId: childRequestId,
1337
+ agentId: selectedBinding.agent.id,
1338
+ state: "failed",
1339
+ output,
1340
+ finalMessageText: output,
1341
+ metadata: { executedToolResults },
1342
+ },
1343
+ };
1344
+ }
1345
+ const delegatedResult = {
1346
+ sessionId,
1347
+ requestId: childRequestId,
1348
+ agentId: selectedBinding.agent.id,
1349
+ state: "completed",
1350
+ output: sanitizeVisibleText(output),
1351
+ finalMessageText: sanitizeVisibleText(output),
1352
+ metadata: { executedToolResults },
1353
+ };
1354
+ return {
1355
+ toolOutput: resolveDelegatedResultOutput(delegatedResult),
1356
+ delegatedSubagentType: subagentType,
1357
+ delegatedResult,
1358
+ };
1359
+ }
1142
1360
  async *stream(binding, input, sessionId, history = [], options = {}) {
1143
1361
  const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
1144
1362
  ...options,
@@ -1163,11 +1381,7 @@ export class AgentRuntimeAdapter {
1163
1381
  content: "Selecting a specialist for delegated execution.",
1164
1382
  };
1165
1383
  }
1166
- const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, options.requestId ?? sessionId, {
1167
- ...options,
1168
- sessionId,
1169
- requestId: options.requestId,
1170
- });
1384
+ const compactDelegation = yield* this.streamDelegateWithCompactRouter(binding, input, sessionId, options.requestId ?? sessionId, options);
1171
1385
  if (compactDelegation) {
1172
1386
  const compactReport = this.buildCompactDelegationReport(compactDelegation);
1173
1387
  yield {
@@ -1177,9 +1391,9 @@ export class AgentRuntimeAdapter {
1177
1391
  };
1178
1392
  yield {
1179
1393
  kind: "content",
1180
- content: JSON.stringify(compactDelegation.delegatedSubagentType === null
1181
- ? compactDelegation.toolOutput
1182
- : compactReport),
1394
+ content: compactDelegation.delegatedSubagentType === null
1395
+ ? String(compactDelegation.toolOutput ?? "")
1396
+ : this.formatCompactDelegationReportForDisplay(compactReport),
1183
1397
  };
1184
1398
  return;
1185
1399
  }
@@ -3,6 +3,7 @@ import { type InternalHarnessStreamItem } from "../events/streaming.js";
3
3
  type RuntimeStreamChunk = {
4
4
  kind: "commentary";
5
5
  content: string;
6
+ agentId?: string;
6
7
  } | string | {
7
8
  kind: "content" | "interrupt" | "reasoning" | "tool-result" | "upstream-event";
8
9
  content?: string;
@@ -10,9 +11,11 @@ type RuntimeStreamChunk = {
10
11
  output?: unknown;
11
12
  isError?: boolean;
12
13
  event?: unknown;
14
+ agentId?: string;
13
15
  } | {
14
16
  kind: "profile";
15
17
  step: RequestExecutionStep;
18
+ agentId?: string;
16
19
  };
17
20
  type StreamRunOptions = {
18
21
  binding: CompiledAgentBinding;
@@ -284,16 +284,16 @@ function normalizeStreamChunk(chunk) {
284
284
  return { kind: "content", content: chunk };
285
285
  }
286
286
  if (chunk.kind === "commentary") {
287
- return { kind: "commentary", content: chunk.content ?? "" };
287
+ return { kind: "commentary", content: chunk.content ?? "", agentId: chunk.agentId };
288
288
  }
289
289
  if (chunk.kind === "upstream-event") {
290
- return { kind: "upstream-event", event: (chunk.event ?? {}) };
290
+ return { kind: "upstream-event", event: (chunk.event ?? {}), agentId: chunk.agentId };
291
291
  }
292
292
  if (chunk.kind === "interrupt") {
293
- return { kind: "interrupt", content: chunk.content };
293
+ return { kind: "interrupt", content: chunk.content, agentId: chunk.agentId };
294
294
  }
295
295
  if (chunk.kind === "reasoning") {
296
- return { kind: "reasoning", content: chunk.content ?? "" };
296
+ return { kind: "reasoning", content: chunk.content ?? "", agentId: chunk.agentId };
297
297
  }
298
298
  if (chunk.kind === "tool-result") {
299
299
  return {
@@ -301,15 +301,17 @@ function normalizeStreamChunk(chunk) {
301
301
  toolName: chunk.toolName ?? "unknown_tool",
302
302
  output: chunk.output,
303
303
  isError: chunk.isError,
304
+ agentId: chunk.agentId,
304
305
  };
305
306
  }
306
307
  if (chunk.kind === "profile") {
307
308
  return {
308
309
  kind: "profile",
309
310
  step: chunk.step,
311
+ agentId: chunk.agentId,
310
312
  };
311
313
  }
312
- return { kind: "content", content: chunk.content ?? "" };
314
+ return { kind: "content", content: chunk.content ?? "", agentId: chunk.agentId };
313
315
  }
314
316
  function normalizeCommentaryText(value) {
315
317
  return value
@@ -645,7 +647,7 @@ export async function* streamHarnessRun(options) {
645
647
  let lastToolResultKey = null;
646
648
  const executedToolResults = [];
647
649
  const emittedCommentary = new Set();
648
- const emitCommentary = function* (content) {
650
+ const emitCommentary = function* (content, agentIdOverride) {
649
651
  const normalized = normalizeCommentaryText(content);
650
652
  if (!normalized || emittedCommentary.has(normalized)) {
651
653
  return;
@@ -655,7 +657,7 @@ export async function* streamHarnessRun(options) {
655
657
  type: "commentary",
656
658
  sessionId: options.sessionId,
657
659
  requestId: options.requestId,
658
- agentId: currentAgentId,
660
+ agentId: agentIdOverride ?? currentAgentId,
659
661
  content: normalized,
660
662
  };
661
663
  };
@@ -804,7 +806,7 @@ export async function* streamHarnessRun(options) {
804
806
  return;
805
807
  }
806
808
  if (normalizedChunk.kind === "commentary") {
807
- yield* emitCommentary(normalizedChunk.content);
809
+ yield* emitCommentary(normalizedChunk.content, normalizedChunk.agentId);
808
810
  continue;
809
811
  }
810
812
  if (normalizedChunk.kind === "reasoning") {
@@ -824,6 +826,7 @@ export async function* streamHarnessRun(options) {
824
826
  continue;
825
827
  }
826
828
  if (normalizedChunk.kind === "tool-result") {
829
+ const chunkAgentId = normalizedChunk.agentId ?? currentAgentId;
827
830
  const toolResultKey = createToolResultKey(normalizedChunk.toolName, normalizedChunk.output, normalizedChunk.isError);
828
831
  if (toolResultKey === lastToolResultKey) {
829
832
  continue;
@@ -844,7 +847,7 @@ export async function* streamHarnessRun(options) {
844
847
  type: "tool-result",
845
848
  sessionId: options.sessionId,
846
849
  requestId: options.requestId,
847
- agentId: currentAgentId,
850
+ agentId: chunkAgentId,
848
851
  toolName: normalizedChunk.toolName,
849
852
  output: normalizedChunk.output,
850
853
  isError: normalizedChunk.isError,
@@ -873,16 +876,16 @@ export async function* streamHarnessRun(options) {
873
876
  currentPlanState = mergedPlanState;
874
877
  const progression = buildPlanStateProgression(previousPlanState, currentPlanState);
875
878
  for (const progressionStep of progression) {
876
- for (const item of await emitPlanStateUpdate(options, currentAgentId, progressionStep.planState)) {
879
+ for (const item of await emitPlanStateUpdate(options, chunkAgentId, progressionStep.planState)) {
877
880
  yield item;
878
881
  }
879
882
  if (progressionStep.commentary) {
880
- yield* emitCommentary(progressionStep.commentary);
883
+ yield* emitCommentary(progressionStep.commentary, chunkAgentId);
881
884
  }
882
885
  }
883
886
  const commentary = summarizePlanState(currentPlanState);
884
887
  if (commentary) {
885
- yield* emitCommentary(commentary);
888
+ yield* emitCommentary(commentary, chunkAgentId);
886
889
  }
887
890
  }
888
891
  }
@@ -899,16 +902,16 @@ export async function* streamHarnessRun(options) {
899
902
  currentPlanState = reconciledPlanState;
900
903
  const progression = buildPlanStateProgression(previousPlanState, currentPlanState);
901
904
  for (const progressionStep of progression) {
902
- for (const item of await emitPlanStateUpdate(options, currentAgentId, progressionStep.planState)) {
905
+ for (const item of await emitPlanStateUpdate(options, chunkAgentId, progressionStep.planState)) {
903
906
  yield item;
904
907
  }
905
908
  if (progressionStep.commentary) {
906
- yield* emitCommentary(progressionStep.commentary);
909
+ yield* emitCommentary(progressionStep.commentary, chunkAgentId);
907
910
  }
908
911
  }
909
912
  const commentary = summarizePlanState(currentPlanState);
910
913
  if (commentary) {
911
- yield* emitCommentary(commentary);
914
+ yield* emitCommentary(commentary, chunkAgentId);
912
915
  }
913
916
  }
914
917
  }
@@ -396,6 +396,16 @@ export function salvageJsonToolCalls(value) {
396
396
  .filter((item) => item !== null);
397
397
  }
398
398
  function normalizeWriteTodosArgs(args) {
399
+ if (typeof args.name === "string" &&
400
+ args.name === "write_todos" &&
401
+ typeof args.arguments === "object" &&
402
+ args.arguments !== null &&
403
+ !Array.isArray(args.arguments)) {
404
+ return normalizeWriteTodosArgs(args.arguments);
405
+ }
406
+ if (Array.isArray(args.items) && !Array.isArray(args.todos)) {
407
+ return normalizeWriteTodosArgs({ ...args, todos: args.items });
408
+ }
399
409
  if (!Array.isArray(args.todos)) {
400
410
  return args;
401
411
  }
@@ -2,26 +2,33 @@ import type { RequestExecutionStep } from "../../contracts/types.js";
2
2
  export type RuntimeStreamChunk = {
3
3
  kind: "upstream-event";
4
4
  event: unknown;
5
+ agentId?: string;
5
6
  } | {
6
7
  kind: "commentary";
7
8
  content: string;
9
+ agentId?: string;
8
10
  } | {
9
11
  kind: "content";
10
12
  content: string;
13
+ agentId?: string;
11
14
  } | {
12
15
  kind: "reasoning";
13
16
  content: string;
17
+ agentId?: string;
14
18
  } | {
15
19
  kind: "interrupt";
16
20
  content: string;
21
+ agentId?: string;
17
22
  } | {
18
23
  kind: "tool-result";
19
24
  toolName: string;
20
25
  output: unknown;
21
26
  isError?: boolean;
27
+ agentId?: string;
22
28
  } | {
23
29
  kind: "profile";
24
30
  step: RequestExecutionStep;
31
+ agentId?: string;
25
32
  };
26
33
  export declare function sanitizeStreamPayload(value: unknown): unknown;
27
34
  export declare function sanitizeRetainedUpstreamEvent(event: unknown): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.367",
3
+ "version": "0.0.370",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -56,7 +56,7 @@
56
56
  "@libsql/client": "^0.17.0",
57
57
  "@llamaindex/ollama": "^0.1.23",
58
58
  "@modelcontextprotocol/sdk": "^1.12.0",
59
- "@qdrant/js-client-rest": "1.13.0",
59
+ "@qdrant/js-client-rest": "1.17.0",
60
60
  "deepagents": "^1.9.0",
61
61
  "langchain": "^1.3.4",
62
62
  "llamaindex": "^0.12.1",
@@ -82,7 +82,7 @@
82
82
  "docs:sync-dev-nav": "node ./scripts/sync-developer-docs-nav.mjs",
83
83
  "docs:sync-release-notes": "node ./scripts/sync-release-notes-html.mjs",
84
84
  "docs:sync-docs-html": "node ./scripts/sync-release-notes-html.mjs && node ./scripts/sync-developer-docs-nav.mjs",
85
- "release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs && node ./scripts/archive-release-notes.mjs && node ./scripts/sync-release-notes-html.mjs",
85
+ "release:prepare": "node ./scripts/prepare-release-version.mjs && node ./scripts/sync-example-version.mjs && node ./scripts/archive-release-notes.mjs && node ./scripts/sync-release-notes-html.mjs",
86
86
  "release:pack": "npm pack --dry-run",
87
87
  "release:publish": "npm publish --access public --registry https://registry.npmjs.org/"
88
88
  },
@@ -99,7 +99,7 @@
99
99
  "hono": "^4.12.14",
100
100
  "langsmith": "^0.5.20",
101
101
  "undici": "^6.24.0",
102
- "@qdrant/js-client-rest": "1.13.0",
102
+ "@qdrant/js-client-rest": "1.17.0",
103
103
  "vite": "^7.3.2",
104
104
  "openai": "6.33.0",
105
105
  "protobufjs": "^7.5.5"