@botbotgo/agent-harness 0.0.329 → 0.0.332

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.328";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.331";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-23";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.328";
1
+ export const AGENT_HARNESS_VERSION = "0.0.331";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-23";
@@ -148,7 +148,14 @@ export async function* streamRuntimeExecution(options) {
148
148
  const shouldDeferStreamContent = () => shouldValidateStreamOutput && !emittedUnsafeStreamSideEffects;
149
149
  const flushDeferredStreamContent = async function* () {
150
150
  while (deferredStreamContent.length > 0) {
151
- yield deferredStreamContent.shift();
151
+ const next = deferredStreamContent.shift();
152
+ if (next.kind === "content") {
153
+ if (next.content) {
154
+ yield { kind: "content", content: next.content };
155
+ }
156
+ continue;
157
+ }
158
+ yield next;
152
159
  }
153
160
  };
154
161
  try {
@@ -5,6 +5,13 @@ export type ExecutedToolResult = {
5
5
  isError?: boolean;
6
6
  memoryCandidates?: MemoryCandidate[];
7
7
  };
8
+ export declare function resolveDeterministicFinalOutput(params: {
9
+ visibleOutput?: string;
10
+ toolFallback?: string;
11
+ executedToolResults?: ExecutedToolResult[];
12
+ }): string;
13
+ export declare function extractDelegatedFindingsText(executedToolResults: ExecutedToolResult[]): string;
14
+ export declare function extractToolResultFindingsText(executedToolResults: ExecutedToolResult[]): string;
8
15
  export declare function finalizeRequestResult(params: {
9
16
  bindingAgentId: string;
10
17
  sessionId: string;
@@ -1,6 +1,95 @@
1
1
  import { containsLikelySkillDocument, extractContentBlocks, extractEmptyAssistantMessageFailure, extractOutputContent, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, sanitizeVisibleText, tryParseJson, } from "../parsing/output-parsing.js";
2
+ import { salvageFunctionLikeToolCall } from "../parsing/output-tool-args.js";
2
3
  import { buildStateSnapshot } from "./model/message-assembly.js";
3
4
  import { asRecord } from "./tool/resolved-tool.js";
5
+ function looksLikeLeakedToolCallText(value) {
6
+ const normalized = sanitizeVisibleText(value).trim();
7
+ if (!normalized) {
8
+ return false;
9
+ }
10
+ if (salvageFunctionLikeToolCall(normalized)) {
11
+ return true;
12
+ }
13
+ const prefixedToolCallMatch = /^(?:\s*(?:Ready|Understood|Okay|Ok|Got it|Sure|All set|What is your request|Please provide a task for me to orchestrate)[.:?!]?\s*)+([A-Za-z_][A-Za-z0-9_]*\([\s\S]*\))\s*$/u.exec(normalized);
14
+ return !!(prefixedToolCallMatch && salvageFunctionLikeToolCall(prefixedToolCallMatch[1]));
15
+ }
16
+ function isPlaceholderTaskCompletion(value) {
17
+ const normalized = sanitizeVisibleText(value).trim();
18
+ return normalized === "Task completed";
19
+ }
20
+ function normalizeToolOutputText(output) {
21
+ const directText = typeof output === "string"
22
+ ? sanitizeVisibleText(output).trim()
23
+ : "";
24
+ if (directText && !looksLikeLeakedToolCallText(directText) && !isPlaceholderTaskCompletion(directText)) {
25
+ return directText;
26
+ }
27
+ const visibleOutput = sanitizeVisibleText(extractVisibleOutput(output)).trim();
28
+ if (visibleOutput && !looksLikeLeakedToolCallText(visibleOutput) && !isPlaceholderTaskCompletion(visibleOutput)) {
29
+ return visibleOutput;
30
+ }
31
+ const fallbackContext = sanitizeVisibleText(extractToolFallbackContext(output)).trim();
32
+ if (fallbackContext && !looksLikeLeakedToolCallText(fallbackContext) && !isPlaceholderTaskCompletion(fallbackContext)) {
33
+ return fallbackContext;
34
+ }
35
+ return "";
36
+ }
37
+ function extractLatestSuccessfulTaskResultText(executedToolResults) {
38
+ for (const toolResult of [...executedToolResults].reverse()) {
39
+ if (toolResult.isError === true || toolResult.toolName !== "task") {
40
+ continue;
41
+ }
42
+ const normalized = normalizeToolOutputText(toolResult.output);
43
+ if (normalized) {
44
+ return normalized;
45
+ }
46
+ }
47
+ return "";
48
+ }
49
+ function extractLatestSuccessfulNonTodoToolResultText(executedToolResults) {
50
+ for (const toolResult of [...executedToolResults].reverse()) {
51
+ if (toolResult.isError === true) {
52
+ continue;
53
+ }
54
+ if (toolResult.toolName === "task" || toolResult.toolName === "write_todos" || toolResult.toolName === "read_todos") {
55
+ continue;
56
+ }
57
+ const normalized = normalizeToolOutputText(toolResult.output);
58
+ if (normalized) {
59
+ return normalized;
60
+ }
61
+ }
62
+ return "";
63
+ }
64
+ export function resolveDeterministicFinalOutput(params) {
65
+ const visibleOutput = params.visibleOutput ?? "";
66
+ const toolFallback = params.toolFallback ?? "";
67
+ const executedToolResults = params.executedToolResults ?? [];
68
+ const delegatedTaskOutput = extractLatestSuccessfulTaskResultText(executedToolResults);
69
+ if (delegatedTaskOutput) {
70
+ return delegatedTaskOutput;
71
+ }
72
+ const sanitizedVisibleOutput = visibleOutput && !looksLikeLeakedToolCallText(visibleOutput)
73
+ ? sanitizeVisibleText(visibleOutput).trim()
74
+ : "";
75
+ if (sanitizedVisibleOutput) {
76
+ return sanitizedVisibleOutput;
77
+ }
78
+ const successfulToolOutput = extractLatestSuccessfulNonTodoToolResultText(executedToolResults);
79
+ if (successfulToolOutput) {
80
+ return successfulToolOutput;
81
+ }
82
+ const sanitizedToolFallback = toolFallback && !looksLikeLeakedToolCallText(toolFallback)
83
+ ? sanitizeVisibleText(toolFallback).trim()
84
+ : "";
85
+ return sanitizedToolFallback;
86
+ }
87
+ export function extractDelegatedFindingsText(executedToolResults) {
88
+ return extractLatestSuccessfulTaskResultText(executedToolResults);
89
+ }
90
+ export function extractToolResultFindingsText(executedToolResults) {
91
+ return extractLatestSuccessfulNonTodoToolResultText(executedToolResults);
92
+ }
4
93
  export function finalizeRequestResult(params) {
5
94
  const { bindingAgentId, sessionId, requestId, result, executedToolResults } = params;
6
95
  const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
@@ -12,7 +101,12 @@ export function finalizeRequestResult(params) {
12
101
  throw new Error(emptyAssistantMessageFailure);
13
102
  }
14
103
  const serializedResult = JSON.stringify(result, null, 2);
15
- const output = visibleOutput || toolFallback || (containsLikelySkillDocument(result) ? "" : serializedResult);
104
+ const output = resolveDeterministicFinalOutput({
105
+ visibleOutput,
106
+ toolFallback,
107
+ executedToolResults,
108
+ })
109
+ || (containsLikelySkillDocument(result) ? "" : serializedResult);
16
110
  const finalMessageText = sanitizeVisibleText(output);
17
111
  const outputContent = extractOutputContent(result);
18
112
  const contentBlocks = extractContentBlocks(result);
@@ -12,6 +12,7 @@ import { materializeDeepAgentSkillSourcePaths } from "./compat/deepagent-compat.
12
12
  import { DEFAULT_SUBAGENT_PROMPT } from "../prompts/runtime-prompts.js";
13
13
  import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../prompts/runtime-prompts.js";
14
14
  import { createStreamEventProjectionState, projectRuntimeStreamEvent } from "./stream-event-projection.js";
15
+ import { resolveDeterministicFinalOutput } from "./invocation-result.js";
15
16
  const EMPTY_TOOL_NAME_MAPPING = {
16
17
  originalToModelFacing: new Map(),
17
18
  modelFacingToOriginal: new Map(),
@@ -268,9 +269,10 @@ export async function invokeBuiltinTaskTool(input) {
268
269
  if (typeof runnable.streamEvents === "function") {
269
270
  const runWithStreamInspection = async (recoveryInstruction) => {
270
271
  const projectionState = createStreamEventProjectionState();
272
+ const executedToolResults = [];
271
273
  const events = await runnable.streamEvents(buildMessages(recoveryInstruction), invokeConfig);
272
274
  for await (const event of events) {
273
- projectRuntimeStreamEvent({
275
+ const projectedChunks = projectRuntimeStreamEvent({
274
276
  event,
275
277
  allowVisibleStreamDeltas: false,
276
278
  includeStateStreamOutput: false,
@@ -280,15 +282,25 @@ export async function invokeBuiltinTaskTool(input) {
280
282
  primaryTools: [],
281
283
  state: projectionState,
282
284
  });
285
+ for (const chunk of projectedChunks) {
286
+ if (chunk.kind !== "tool-result") {
287
+ continue;
288
+ }
289
+ executedToolResults.push({
290
+ toolName: chunk.toolName,
291
+ output: chunk.output,
292
+ isError: chunk.isError,
293
+ });
294
+ }
283
295
  }
284
- return projectionState;
296
+ return { projectionState, executedToolResults };
285
297
  };
286
- let projectionState = await runWithStreamInspection();
298
+ let { projectionState, executedToolResults } = await runWithStreamInspection();
287
299
  if (requiresDelegatedExecutionRecovery(projectionState)) {
288
300
  const recoveryInstruction = projectionState.hasIncompletePlanState && projectionState.emittedToolError
289
301
  ? `${AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION}\n\n${DELEGATED_FAILURE_PLAN_RECONCILIATION_INSTRUCTION}`
290
302
  : AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION;
291
- projectionState = await runWithStreamInspection(recoveryInstruction);
303
+ ({ projectionState, executedToolResults } = await runWithStreamInspection(recoveryInstruction));
292
304
  }
293
305
  if (requiresDelegatedExecutionRecovery(projectionState)) {
294
306
  throw new Error(formatDelegatedExecutionBlocker(projectionState));
@@ -297,14 +309,20 @@ export async function invokeBuiltinTaskTool(input) {
297
309
  throw new Error("Delegated investigation ended without any real tool execution evidence.");
298
310
  }
299
311
  if (projectionState.emittedToolError) {
300
- const blockerMessage = projectionState.emittedOutput.trim()
301
- || formatDelegatedExecutionBlocker(projectionState);
312
+ const blockerMessage = resolveDeterministicFinalOutput({
313
+ visibleOutput: projectionState.emittedOutput.trim(),
314
+ executedToolResults,
315
+ }) || formatDelegatedExecutionBlocker(projectionState);
302
316
  if (hasUnresolvedDelegatedExecution(projectionState) || !projectionState.emittedSuccessfulToolResult) {
303
317
  throw new Error(blockerMessage);
304
318
  }
305
319
  }
306
- if (projectionState.emittedOutput.trim()) {
307
- return projectionState.emittedOutput.trim();
320
+ const deterministicOutput = resolveDeterministicFinalOutput({
321
+ visibleOutput: projectionState.emittedOutput.trim(),
322
+ executedToolResults,
323
+ });
324
+ if (deterministicOutput) {
325
+ return deterministicOutput;
308
326
  }
309
327
  if (projectionState.emittedToolResult) {
310
328
  throw new Error("Delegated investigation performed tool work but did not return surfaced findings.");
@@ -415,7 +415,7 @@ async function createNodeLlamaCppModel(model) {
415
415
  }));
416
416
  }
417
417
  catch (error) {
418
- throw new Error(`Failed to initialize ${model.provider} model ${model.id}. Install node-llama-cpp in the application workspace and ensure the GGUF file exists at ${modelPath}.`, { cause: error });
418
+ throw new Error(`Failed to initialize ${model.provider} model ${model.id}. Install @langchain/community and node-llama-cpp in the application workspace and ensure the GGUF file exists at ${modelPath}.`, { cause: error });
419
419
  }
420
420
  }
421
421
  export async function createResolvedModel(model, modelResolver) {
@@ -1,4 +1,5 @@
1
1
  import { sanitizeVisibleText } from "../parsing/output-parsing.js";
2
+ import { salvageFunctionLikeToolCall } from "../parsing/output-tool-args.js";
2
3
  import { computeIncrementalOutput, extractInterruptPayload, extractReasoningStreamOutput, sanitizeRetainedUpstreamEvent, extractStateStreamOutput, extractTerminalStreamOutput, extractToolResult, extractVisibleStreamOutput, normalizeTerminalOutputKey, } from "../parsing/stream-event-parsing.js";
3
4
  import { resolveModelFacingToolName } from "./tool/tool-name-mapping.js";
4
5
  export function createStreamEventProjectionState() {
@@ -24,6 +25,20 @@ export function createStreamEventProjectionState() {
24
25
  seenTerminalOutputs: new Set(),
25
26
  };
26
27
  }
28
+ function shouldSuppressVisibleToolCallText(value) {
29
+ const trimmed = value.trim();
30
+ if (!trimmed) {
31
+ return false;
32
+ }
33
+ if (salvageFunctionLikeToolCall(trimmed)) {
34
+ return true;
35
+ }
36
+ const prefixedToolCallMatch = /^(?:\s*(?:Ready|Understood|Okay|Ok|Got it|Sure|All set)[.:!]?\s*)+([A-Za-z_][A-Za-z0-9_]*\([\s\S]*\))\s*$/u.exec(trimmed);
37
+ if (!prefixedToolCallMatch) {
38
+ return false;
39
+ }
40
+ return salvageFunctionLikeToolCall(prefixedToolCallMatch[1]) !== null;
41
+ }
27
42
  function readSummaryCounts(summary) {
28
43
  if (typeof summary !== "object" || summary === null) {
29
44
  return null;
@@ -218,7 +233,7 @@ export function projectRuntimeStreamEvent(params) {
218
233
  const allowStreamedVisibleContent = allowVisibleContent && !state.emittedToolResult && !state.emittedToolError;
219
234
  if (allowVisibleStreamDeltas && allowStreamedVisibleContent) {
220
235
  const visibleStreamOutput = extractVisibleStreamOutput(event);
221
- if (visibleStreamOutput) {
236
+ if (visibleStreamOutput && !shouldSuppressVisibleToolCallText(visibleStreamOutput)) {
222
237
  const nextOutput = computeIncrementalOutput(state.emittedOutput, visibleStreamOutput);
223
238
  state.emittedOutput = nextOutput.accumulated;
224
239
  if (nextOutput.delta) {
@@ -228,8 +243,8 @@ export function projectRuntimeStreamEvent(params) {
228
243
  }
229
244
  if (includeStateStreamOutput && allowVisibleContent) {
230
245
  const stateStreamOutput = extractStateStreamOutput(event);
231
- if (stateStreamOutput) {
232
- const nextOutput = computeIncrementalOutput(state.emittedOutput, sanitizeVisibleText(stateStreamOutput));
246
+ if (stateStreamOutput && !shouldSuppressVisibleToolCallText(stateStreamOutput)) {
247
+ const nextOutput = computeIncrementalOutput(state.emittedOutput, stateStreamOutput);
233
248
  state.emittedOutput = nextOutput.accumulated;
234
249
  if (nextOutput.delta) {
235
250
  chunks.push({ kind: "content", content: nextOutput.delta });
@@ -269,7 +284,7 @@ export function projectRuntimeStreamEvent(params) {
269
284
  state.emittedDelegatedTerminalOutput = true;
270
285
  }
271
286
  }
272
- if (output) {
287
+ if (output && !shouldSuppressVisibleToolCallText(output)) {
273
288
  const outputKey = normalizeTerminalOutputKey(output);
274
289
  if (!outputKey || !state.seenTerminalOutputs.has(outputKey)) {
275
290
  if (outputKey) {
@@ -1,6 +1,8 @@
1
+ import { resolveDeterministicFinalOutput, } from "../../adapter/invocation-result.js";
1
2
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError } from "../../agent-runtime-adapter.js";
2
3
  import { ExecutionReconciliationError } from "../../adapter/flow/stream-runtime.js";
3
4
  import { buildRequestPlanState, summarizeBuiltinWriteTodosArgs } from "../../adapter/runtime-adapter-support.js";
5
+ import { sanitizeVisibleText } from "../../parsing/output-parsing.js";
4
6
  import { describeRuntimeError, renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
5
7
  import { getBindingPrimaryModel } from "../../support/compiled-binding.js";
6
8
  import { createContentBlocksItem, createToolResultKey, } from "../events/streaming.js";
@@ -466,7 +468,9 @@ export async function* streamHarnessRun(options) {
466
468
  ]).then(([loadedPriorHistory, resolvedReleaseRunSlot]) => [loadedPriorHistory, resolvedReleaseRunSlot]);
467
469
  releaseRunSlot = acquiredReleaseRunSlot;
468
470
  const toolErrors = [];
471
+ let sawSuccessfulToolResult = false;
469
472
  let lastToolResultKey = null;
473
+ const executedToolResults = [];
470
474
  const recalledMemories = options.invocation.memoryRecall?.items ?? [];
471
475
  for (const item of createRuntimeMemoryRecallSteps(options.sessionId, options.requestId, recalledMemories)) {
472
476
  yield item;
@@ -637,9 +641,17 @@ export async function* streamHarnessRun(options) {
637
641
  continue;
638
642
  }
639
643
  lastToolResultKey = toolResultKey;
644
+ executedToolResults.push({
645
+ toolName: normalizedChunk.toolName,
646
+ output: normalizedChunk.output,
647
+ isError: normalizedChunk.isError,
648
+ });
640
649
  if (normalizedChunk.isError) {
641
650
  toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
642
651
  }
652
+ else {
653
+ sawSuccessfulToolResult = true;
654
+ }
643
655
  yield {
644
656
  type: "tool-result",
645
657
  sessionId: options.sessionId,
@@ -714,10 +726,18 @@ export async function* streamHarnessRun(options) {
714
726
  content: normalizedChunk.content,
715
727
  };
716
728
  }
717
- if (!assistantOutput && toolErrors.length > 0) {
729
+ if (!assistantOutput && toolErrors.length > 0 && !sawSuccessfulToolResult) {
718
730
  assistantOutput = toolErrors.join("\n\n");
719
731
  emitted = true;
720
732
  }
733
+ const resolvedAssistantOutput = resolveDeterministicFinalOutput({
734
+ visibleOutput: assistantOutput,
735
+ executedToolResults,
736
+ });
737
+ if (sanitizeVisibleText(resolvedAssistantOutput) !== sanitizeVisibleText(assistantOutput)) {
738
+ assistantOutput = resolvedAssistantOutput;
739
+ emitted = emitted || assistantOutput.length > 0;
740
+ }
721
741
  currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
722
742
  if (!assistantOutput) {
723
743
  const actual = await options.invokeWithHistory(options.binding, options.input, options.sessionId, options.requestId);
@@ -1,4 +1,3 @@
1
- import { type Memory as Mem0Memory, type Message as Mem0Message } from "mem0ai";
2
1
  import type { HarnessEvent, HarnessEventProjection } from "../../../contracts/types.js";
3
2
  import type { RuntimePersistence } from "../../../persistence/types.js";
4
3
  import { type StoreLike } from "./store.js";
@@ -17,6 +16,24 @@ export type ResolvedMem0Config = {
17
16
  writeOnApprovalResolution: boolean;
18
17
  writeOnRequestCompletion: boolean;
19
18
  };
19
+ type Mem0Message = {
20
+ role: string;
21
+ content: string;
22
+ };
23
+ type Mem0Memory = {
24
+ id?: string;
25
+ memory?: string;
26
+ data?: {
27
+ memory?: string;
28
+ };
29
+ score?: number;
30
+ categories?: string[];
31
+ metadata?: Record<string, unknown>;
32
+ agent_id?: string;
33
+ request_id?: string;
34
+ created_at?: string | Date;
35
+ updated_at?: string | Date;
36
+ };
20
37
  type Mem0ClientLike = {
21
38
  add(messages: Mem0Message[], options?: Record<string, unknown>): Promise<unknown>;
22
39
  search(query: string, options?: Record<string, unknown>): Promise<Mem0Memory[]>;
@@ -1,6 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import path from "node:path";
3
- import { MemoryClient } from "mem0ai";
4
3
  import { extractMessageText } from "../../../utils/message-content.js";
5
4
  import { FileBackedStore } from "./store.js";
6
5
  const MEM0_EVENT_TYPES = new Set([
@@ -79,27 +78,24 @@ async function createDefaultMem0Client(config) {
79
78
  if (!apiKey) {
80
79
  throw new Error(`runtimeMemory.mem0 is enabled but environment variable ${config.apiKeyEnv} is not set`);
81
80
  }
82
- return new MemoryClient({
83
- apiKey,
84
- ...(config.host ? { host: config.host } : {}),
85
- ...(config.organizationName ? { organizationName: config.organizationName } : {}),
86
- ...(config.projectName ? { projectName: config.projectName } : {}),
87
- ...(config.organizationId ? { organizationId: config.organizationId } : {}),
88
- ...(config.projectId ? { projectId: config.projectId } : {}),
89
- });
81
+ return createCloudMem0Client(config, apiKey);
90
82
  }
91
83
  function normalizeMem0Host(host) {
92
84
  return host.replace(/\/+$/, "");
93
85
  }
86
+ function buildMem0Headers(apiKey) {
87
+ return {
88
+ "Content-Type": "application/json",
89
+ ...(apiKey ? { Authorization: `Token ${apiKey}` } : {}),
90
+ };
91
+ }
94
92
  async function selfHostedMem0Request(config, pathname, options = {}) {
95
93
  if (!config.host) {
96
94
  throw new Error("Self-hosted mem0 client requires runtimeMemory.mem0.host.");
97
95
  }
98
96
  const response = await fetch(`${normalizeMem0Host(config.host)}${pathname}`, {
99
97
  method: options.method ?? "GET",
100
- headers: {
101
- "Content-Type": "application/json",
102
- },
98
+ headers: buildMem0Headers(),
103
99
  ...(options.body ? { body: JSON.stringify(options.body) } : {}),
104
100
  });
105
101
  if (!response.ok) {
@@ -107,6 +103,54 @@ async function selfHostedMem0Request(config, pathname, options = {}) {
107
103
  }
108
104
  return response.json();
109
105
  }
106
+ function applyMem0Scope(payload, config) {
107
+ const scoped = { ...payload };
108
+ if (config.organizationName && config.projectName) {
109
+ scoped.org_name = config.organizationName;
110
+ scoped.project_name = config.projectName;
111
+ }
112
+ if (config.organizationId && config.projectId) {
113
+ scoped.org_id = config.organizationId;
114
+ scoped.project_id = config.projectId;
115
+ delete scoped.org_name;
116
+ delete scoped.project_name;
117
+ }
118
+ return scoped;
119
+ }
120
+ async function cloudMem0Request(config, apiKey, pathname, options = {}) {
121
+ const host = normalizeMem0Host(config.host ?? "https://api.mem0.ai");
122
+ const response = await fetch(`${host}${pathname}`, {
123
+ method: options.method ?? "GET",
124
+ headers: buildMem0Headers(apiKey),
125
+ ...(options.body ? { body: JSON.stringify(applyMem0Scope(options.body, config)) } : {}),
126
+ });
127
+ if (!response.ok) {
128
+ throw new Error(`Mem0 request failed: ${response.status} ${response.statusText}`);
129
+ }
130
+ return response.json();
131
+ }
132
+ function createCloudMem0Client(config, apiKey) {
133
+ return {
134
+ async add(messages, options = {}) {
135
+ return cloudMem0Request(config, apiKey, "/v1/memories/", {
136
+ method: "POST",
137
+ body: {
138
+ messages,
139
+ ...options,
140
+ },
141
+ });
142
+ },
143
+ async search(query, options = {}) {
144
+ return cloudMem0Request(config, apiKey, "/v1/memories/search/", {
145
+ method: "POST",
146
+ body: {
147
+ query,
148
+ ...options,
149
+ },
150
+ });
151
+ },
152
+ };
153
+ }
110
154
  function createSelfHostedMem0Client(config) {
111
155
  return {
112
156
  async add(messages, options = {}) {
@@ -275,7 +319,7 @@ export class Mem0SemanticRecall {
275
319
  const createdAt = toIsoString(record.created_at) ?? new Date(0).toISOString();
276
320
  const updatedAt = toIsoString(record.updated_at) ?? createdAt;
277
321
  return {
278
- id: record.id,
322
+ id: typeof record.id === "string" && record.id.trim().length > 0 ? record.id : memory,
279
323
  memory,
280
324
  score: typeof record.score === "number" && Number.isFinite(record.score) ? record.score : 0,
281
325
  categories: Array.isArray(record.categories)
@@ -1,11 +1,93 @@
1
1
  import { AIMessage } from "langchain";
2
2
  import { salvageFunctionLikeToolCall, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
3
+ function consumeLeadingFunctionLikeToolCall(value) {
4
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\(/.exec(value);
5
+ if (!match) {
6
+ return null;
7
+ }
8
+ let depthParen = 0;
9
+ let depthBracket = 0;
10
+ let depthBrace = 0;
11
+ let inString = false;
12
+ let quoteChar = "";
13
+ let escaping = false;
14
+ for (let index = match[0].length - 1; index < value.length; index += 1) {
15
+ const char = value[index];
16
+ if (inString) {
17
+ if (escaping) {
18
+ escaping = false;
19
+ continue;
20
+ }
21
+ if (char === "\\") {
22
+ escaping = true;
23
+ continue;
24
+ }
25
+ if (char === quoteChar) {
26
+ inString = false;
27
+ quoteChar = "";
28
+ }
29
+ continue;
30
+ }
31
+ if (char === "\"" || char === "'") {
32
+ inString = true;
33
+ quoteChar = char;
34
+ continue;
35
+ }
36
+ if (char === "(") {
37
+ depthParen += 1;
38
+ continue;
39
+ }
40
+ if (char === ")") {
41
+ depthParen -= 1;
42
+ if (depthParen === 0 && depthBracket === 0 && depthBrace === 0) {
43
+ const candidate = value.slice(0, index + 1);
44
+ return salvageFunctionLikeToolCall(candidate) ? candidate : null;
45
+ }
46
+ continue;
47
+ }
48
+ if (char === "[")
49
+ depthBracket += 1;
50
+ else if (char === "]")
51
+ depthBracket -= 1;
52
+ else if (char === "{")
53
+ depthBrace += 1;
54
+ else if (char === "}")
55
+ depthBrace -= 1;
56
+ }
57
+ return null;
58
+ }
59
+ function stripVisibleFunctionLikeToolCallText(value) {
60
+ let remaining = value.trim();
61
+ let removedLeadingCall = false;
62
+ while (remaining.length > 0) {
63
+ const consumed = consumeLeadingFunctionLikeToolCall(remaining);
64
+ if (!consumed) {
65
+ break;
66
+ }
67
+ removedLeadingCall = true;
68
+ remaining = remaining.slice(consumed.length).trimStart();
69
+ }
70
+ const lineFiltered = remaining
71
+ .split("\n")
72
+ .map((line) => line.trim())
73
+ .filter((line) => !salvageFunctionLikeToolCall(line))
74
+ .join("\n")
75
+ .trim();
76
+ if (lineFiltered.length > 0) {
77
+ const prefixedToolCallMatch = /^(?:\s*(?:Ready|Understood|Okay|Ok|Got it|Sure|All set|Please provide a task for me to orchestrate)[.:!]?\s*)+([A-Za-z_][A-Za-z0-9_]*\([\s\S]*\))\s*$/u.exec(lineFiltered);
78
+ if (prefixedToolCallMatch && salvageFunctionLikeToolCall(prefixedToolCallMatch[1])) {
79
+ return "";
80
+ }
81
+ return lineFiltered;
82
+ }
83
+ return removedLeadingCall ? "" : value.trim();
84
+ }
3
85
  export function sanitizeVisibleText(value) {
4
- return value
86
+ return stripVisibleFunctionLikeToolCallText(value
5
87
  .replace(/[A-Za-z0-9_]*Middleware\.after_model/g, "")
6
88
  .replace(/todoListMiddleware\.after_model/g, "")
7
89
  .replace(/__end__+/g, "")
8
- .trim();
90
+ .trim());
9
91
  }
10
92
  function isLikelyMemoryWriteArgsObject(value) {
11
93
  if (typeof value !== "object" || !value || Array.isArray(value)) {
@@ -104,7 +186,7 @@ function extractAssistantTextFromMessages(messages) {
104
186
  const content = extractMessageContent(message);
105
187
  if (content)
106
188
  return content;
107
- continue;
189
+ return "";
108
190
  }
109
191
  const ids = Array.isArray(typed.id) ? typed.id.filter((item) => typeof item === "string") : [];
110
192
  const typeName = ids.at(-1);
@@ -123,6 +205,7 @@ function extractAssistantTextFromMessages(messages) {
123
205
  const content = extractMessageContent(message);
124
206
  if (content)
125
207
  return content;
208
+ return "";
126
209
  }
127
210
  return "";
128
211
  }
@@ -65,7 +65,7 @@ async function createNodeLlamaCppEmbeddings(embeddingModel) {
65
65
  });
66
66
  }
67
67
  catch (error) {
68
- throw new Error(`Failed to initialize ${embeddingModel.provider} embedding model ${embeddingModel.id}. Install node-llama-cpp in the application workspace and ensure the GGUF file exists at ${modelPath}.`, { cause: error });
68
+ throw new Error(`Failed to initialize ${embeddingModel.provider} embedding model ${embeddingModel.id}. Install @langchain/community and node-llama-cpp in the application workspace and ensure the GGUF file exists at ${modelPath}.`, { cause: error });
69
69
  }
70
70
  }
71
71
  async function createLazyLlamaIndexEmbeddingModel(embeddingModel) {
@@ -1,8 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { mkdir } from "node:fs/promises";
3
- import { Document } from "@langchain/core/documents";
4
3
  import { createClient } from "@libsql/client";
5
- import { LibSQLVectorStore } from "@langchain/community/vectorstores/libsql";
6
4
  import { QdrantClient } from "@qdrant/js-client-rest";
7
5
  import { compileVectorStore } from "../../workspace/resource-compilers.js";
8
6
  import { getRuntimeStorageRoots, resolveRefId } from "../../workspace/support/workspace-ref-utils.js";
@@ -52,6 +50,69 @@ async function ensureLibSqlSchema(db, table, column, dimensions) {
52
50
  ON ${safeTable}(libsql_vector_idx(${safeColumn}))
53
51
  `);
54
52
  }
53
+ function serializeVector(vector) {
54
+ if (!vector.every((value) => typeof value === "number" && Number.isFinite(value))) {
55
+ throw new Error("Invalid vector: all elements must be finite numbers.");
56
+ }
57
+ return `[${vector.join(",")}]`;
58
+ }
59
+ async function addLibSqlDocuments(db, table, column, documents, vectors) {
60
+ const safeTable = assertIdentifier(table, "table name");
61
+ const safeColumn = assertIdentifier(column, "column name");
62
+ const statements = documents.map((document, index) => ({
63
+ sql: `INSERT INTO ${safeTable} (content, metadata, ${safeColumn}) VALUES (:content, :metadata, vector(:embedding)) RETURNING ${safeTable}.rowid AS id`,
64
+ args: {
65
+ content: document.pageContent,
66
+ metadata: JSON.stringify(document.metadata ?? {}),
67
+ embedding: serializeVector(vectors[index] ?? []),
68
+ },
69
+ }));
70
+ if (statements.length === 0) {
71
+ return [];
72
+ }
73
+ const results = await db.batch(statements);
74
+ return results.flatMap((result) => result.rows.map((row) => String(row.id)));
75
+ }
76
+ async function similaritySearchLibSql(db, table, column, queryVector, limit) {
77
+ const safeTable = assertIdentifier(table, "table name");
78
+ const safeColumn = assertIdentifier(column, "column name");
79
+ const safeIndex = assertIdentifier(`idx_${safeTable}_${safeColumn}`, "index name");
80
+ const rows = await db.execute({
81
+ sql: `SELECT ${safeTable}.rowid AS id, ${safeTable}.content, ${safeTable}.metadata, vector_distance_cos(${safeTable}.${safeColumn}, vector(:queryVector)) AS distance
82
+ FROM vector_top_k('${safeIndex}', vector(:queryVector), CAST(:limit AS INTEGER)) AS top_k
83
+ JOIN ${safeTable} ON top_k.rowid = ${safeTable}.rowid`,
84
+ args: {
85
+ queryVector: serializeVector(queryVector),
86
+ limit,
87
+ },
88
+ });
89
+ return rows.rows.map((row) => {
90
+ const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : {};
91
+ const metadata = typeof parsedMetadata === "object" && parsedMetadata && !Array.isArray(parsedMetadata)
92
+ ? parsedMetadata
93
+ : {};
94
+ return {
95
+ pageContent: typeof row.content === "string" ? row.content : "",
96
+ metadata,
97
+ score: typeof row.distance === "number" ? 1 - row.distance : undefined,
98
+ };
99
+ });
100
+ }
101
+ async function deleteLibSqlVectors(db, table, params) {
102
+ const safeTable = assertIdentifier(table, "table name");
103
+ if (params.deleteAll) {
104
+ await db.execute(`DELETE FROM ${safeTable}`);
105
+ return;
106
+ }
107
+ const ids = params.ids ?? [];
108
+ if (ids.length === 0) {
109
+ throw new Error('You must provide an "ids" parameter or a "deleteAll" parameter.');
110
+ }
111
+ await db.batch(ids.map((id) => ({
112
+ sql: `DELETE FROM ${safeTable} WHERE rowid = :id`,
113
+ args: { id },
114
+ })));
115
+ }
55
116
  function createQdrantClientForStore(vectorStore) {
56
117
  return new QdrantClient({
57
118
  ...(vectorStore.url ? { url: vectorStore.url } : {}),
@@ -197,35 +258,17 @@ export async function resolveCompiledVectorStore(workspace, vectorStore, options
197
258
  embeddingModelRef: vectorStore.embeddingModelRef,
198
259
  addDocuments: async (documents) => {
199
260
  await ensureSchemaForTexts(documents.map((document) => document.pageContent));
200
- const store = new LibSQLVectorStore(embeddings, {
201
- db,
202
- table,
203
- column,
204
- });
205
- return store.addDocuments(documents.map((document) => new Document(document)));
261
+ const vectors = await embeddings.embedDocuments(documents.map((document) => document.pageContent));
262
+ return addLibSqlDocuments(db, table, column, documents, vectors);
206
263
  },
207
264
  similaritySearch: async (query, limit) => {
208
265
  await ensureSchemaForTexts([query]);
209
- const store = new LibSQLVectorStore(embeddings, {
210
- db,
211
- table,
212
- column,
213
- });
214
- const rows = (await store.similaritySearchWithScore(query, limit));
215
- return rows.map(([document, score]) => ({
216
- pageContent: document.pageContent,
217
- metadata: document.metadata,
218
- score: typeof score === "number" ? 1 - score : score,
219
- }));
266
+ const queryVector = await embeddings.embedQuery(query);
267
+ return similaritySearchLibSql(db, table, column, queryVector, limit);
220
268
  },
221
269
  delete: async (params) => {
222
270
  await ensureSchemaForTexts(["seed"]);
223
- const store = new LibSQLVectorStore(embeddings, {
224
- db,
225
- table,
226
- column,
227
- });
228
- await store.delete(params);
271
+ await deleteLibSqlVectors(db, table, params);
229
272
  },
230
273
  };
231
274
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.329",
3
+ "version": "0.0.332",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -48,7 +48,6 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@langchain/anthropic": "^1.3.27",
51
- "@langchain/community": "^1.1.27",
52
51
  "@langchain/core": "^1.1.41",
53
52
  "@langchain/google": "^0.1.10",
54
53
  "@langchain/langgraph": "^1.2.9",
@@ -61,7 +60,6 @@
61
60
  "deepagents": "^1.9.0",
62
61
  "langchain": "^1.3.4",
63
62
  "llamaindex": "^0.12.1",
64
- "mem0ai": "^2.4.6",
65
63
  "mustache": "^4.2.0",
66
64
  "yaml": "^2.8.1",
67
65
  "zod": "^3.25.76"
@@ -89,6 +87,7 @@
89
87
  "release:publish": "npm publish --access public --registry https://registry.npmjs.org/"
90
88
  },
91
89
  "devDependencies": {
90
+ "@langchain/community": "^1.1.27",
92
91
  "@types/node": "^25.5.2",
93
92
  "typescript": "^6.0.2",
94
93
  "vite": "^7.3.2",