@botbotgo/agent-harness 0.0.378 → 0.0.380

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.
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.378";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.380";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-30";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.378";
1
+ export const AGENT_HARNESS_VERSION = "0.0.380";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-30";
@@ -212,14 +212,14 @@ function isTodoPlanningToolName(name) {
212
212
  || name === "tool_call_write_todos"
213
213
  || name === "tool_call_read_todos";
214
214
  }
215
- function shouldLimitPromptedJsonToolsToPlanning(input) {
215
+ function shouldLimitToolsToPlanning(input) {
216
216
  const text = stringifyNodeLlamaCppInput(input);
217
217
  return text.includes("required visible planning contract")
218
218
  && !hasPriorToolResultForToolName(input, "write_todos")
219
219
  && !hasPriorToolResultForToolName(input, "tool_call_write_todos");
220
220
  }
221
- function selectPromptedJsonToolsForTurn(input, boundTools) {
222
- if (!shouldLimitPromptedJsonToolsToPlanning(input)) {
221
+ function selectPlanningToolsForTurn(input, boundTools) {
222
+ if (!shouldLimitToolsToPlanning(input)) {
223
223
  return boundTools;
224
224
  }
225
225
  const planningTools = boundTools.filter((tool) => isTodoPlanningToolName(readBoundToolName(tool)));
@@ -270,26 +270,52 @@ function normalizeProviderFacingInput(input) {
270
270
  }
271
271
  return input;
272
272
  }
273
- function createProviderToolMessageCompatModel(model) {
273
+ function createProviderToolMessageCompatModel(model, boundTools = []) {
274
+ const resolveTargetForTurn = (input) => {
275
+ if (boundTools.length === 0 || typeof model.bindTools !== "function") {
276
+ return model;
277
+ }
278
+ const effectiveBoundTools = selectPlanningToolsForTurn(input, boundTools);
279
+ return model.bindTools.call(model, effectiveBoundTools);
280
+ };
274
281
  return new Proxy(model, {
275
282
  has(target, prop) {
276
- if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
283
+ if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "streamEvents" || prop === "withConfig") {
277
284
  return true;
278
285
  }
279
286
  return prop in target;
280
287
  },
281
288
  get(target, prop, receiver) {
282
289
  if (prop === "bindTools") {
283
- return (tools) => {
284
- const bound = target.bindTools.call(target, tools);
285
- return createProviderToolMessageCompatModel(bound);
286
- };
290
+ return (tools) => createProviderToolMessageCompatModel(target, tools);
287
291
  }
288
292
  if (prop === "invoke") {
289
- return (input, config) => target.invoke.call(target, normalizeProviderFacingInput(input), config);
293
+ return (input, config) => {
294
+ const effectiveTarget = resolveTargetForTurn(input);
295
+ return effectiveTarget.invoke.call(effectiveTarget, normalizeProviderFacingInput(input), config);
296
+ };
290
297
  }
291
298
  if (prop === "stream") {
292
- return (input, config) => target.stream.call(target, normalizeProviderFacingInput(input), config);
299
+ return (input, config) => {
300
+ const effectiveTarget = resolveTargetForTurn(input);
301
+ return effectiveTarget.stream.call(effectiveTarget, normalizeProviderFacingInput(input), config);
302
+ };
303
+ }
304
+ if (prop === "streamEvents") {
305
+ return (input, config) => {
306
+ const effectiveTarget = resolveTargetForTurn(input);
307
+ if (typeof effectiveTarget.streamEvents === "function") {
308
+ return effectiveTarget.streamEvents.call(effectiveTarget, normalizeProviderFacingInput(input), config);
309
+ }
310
+ return (async function* () {
311
+ const output = await effectiveTarget.invoke.call(effectiveTarget, normalizeProviderFacingInput(input), config);
312
+ yield {
313
+ event: "on_chat_model_end",
314
+ name: typeof effectiveTarget.constructor?.name === "string" ? effectiveTarget.constructor.name : "ChatModel",
315
+ data: { output },
316
+ };
317
+ })();
318
+ };
293
319
  }
294
320
  if (prop === "withConfig" && typeof target.withConfig === "function") {
295
321
  return (config) => createProviderToolMessageCompatModel(target.withConfig.call(target, config));
@@ -485,11 +511,20 @@ function withPromptedJsonToolPrompt(input, tools, options = {}) {
485
511
  if (toolInstructions.length === 0) {
486
512
  return stringifyNodeLlamaCppInput(input);
487
513
  }
514
+ const forcedPlanningInstruction = options.forcePlanningToolCall
515
+ ? [
516
+ "Required planning tool call:",
517
+ "You must call write_todos now before any domain tool or final answer.",
518
+ "Return exactly one JSON object for write_todos with concrete todo items and statuses.",
519
+ "Do not write prose, markdown, analysis, or a plain-text plan.",
520
+ ].join("\n")
521
+ : "";
488
522
  const systemContent = `${NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION}\n\n${toolInstructions.join("\n\n")}`;
489
523
  const prompt = stringifyNodeLlamaCppInput(input);
490
524
  return [
491
525
  options.suppressThinking ? NO_THINK_CONTROL_TOKEN : "",
492
526
  systemContent,
527
+ forcedPlanningInstruction,
493
528
  prompt,
494
529
  PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER,
495
530
  ].filter(Boolean).join("\n\n");
@@ -497,7 +532,7 @@ function withPromptedJsonToolPrompt(input, tools, options = {}) {
497
532
  function createPromptedJsonToolBindableModel(model, boundTools = [], options = {}) {
498
533
  return new Proxy(model, {
499
534
  has(target, prop) {
500
- if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
535
+ if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "streamEvents" || prop === "withConfig") {
501
536
  return true;
502
537
  }
503
538
  return prop in target;
@@ -508,8 +543,11 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
508
543
  }
509
544
  if (prop === "invoke") {
510
545
  return async (input, config) => {
511
- const effectiveBoundTools = selectPromptedJsonToolsForTurn(input, boundTools);
512
- const rawResult = await target.invoke(effectiveBoundTools.length > 0 ? withPromptedJsonToolPrompt(input, effectiveBoundTools, options) : input, config);
546
+ const effectiveBoundTools = selectPlanningToolsForTurn(input, boundTools);
547
+ const forcePlanningToolCall = shouldLimitToolsToPlanning(input);
548
+ const rawResult = await target.invoke(effectiveBoundTools.length > 0
549
+ ? withPromptedJsonToolPrompt(input, effectiveBoundTools, { ...options, forcePlanningToolCall })
550
+ : input, config);
513
551
  if (effectiveBoundTools.length === 0) {
514
552
  return rawResult;
515
553
  }
@@ -540,6 +578,18 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
540
578
  })();
541
579
  };
542
580
  }
581
+ if (prop === "streamEvents") {
582
+ return async (input, config) => {
583
+ const value = await receiver.invoke(input, config);
584
+ return (async function* () {
585
+ yield {
586
+ event: "on_chat_model_end",
587
+ name: typeof target.constructor?.name === "string" ? target.constructor.name : "ChatModel",
588
+ data: { output: value },
589
+ };
590
+ })();
591
+ };
592
+ }
543
593
  if (prop === "withConfig" && typeof target.withConfig === "function") {
544
594
  return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools, options);
545
595
  }
@@ -547,7 +597,7 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
547
597
  return typeof member === "function" ? member.bind(target) : member;
548
598
  },
549
599
  getOwnPropertyDescriptor(target, prop) {
550
- if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
600
+ if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "streamEvents" || prop === "withConfig") {
551
601
  return {
552
602
  configurable: true,
553
603
  enumerable: false,
@@ -236,7 +236,10 @@ function getPlanStateFromUpstreamEvent(input) {
236
236
  return null;
237
237
  }
238
238
  const typed = input.event;
239
- const todos = extractTodosArray(typed.data?.output)
239
+ const isWriteTodosStart = typed.event === "on_tool_start"
240
+ && typed.name === "write_todos";
241
+ const todos = (isWriteTodosStart ? extractTodosArray(typed.data?.input) : null)
242
+ ?? extractTodosArray(typed.data?.output)
240
243
  ?? extractTodosArray(typed.data?.chunk);
241
244
  if (!todos) {
242
245
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.378",
3
+ "version": "0.0.380",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",