open-agents-ai 0.101.5 → 0.101.7

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.
Files changed (2) hide show
  1. package/dist/index.js +282 -34
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5952,35 +5952,35 @@ function discoverSkills(repoRoot) {
5952
5952
  if (pkgRoot) {
5953
5953
  const pkgFrameworks = join14(pkgRoot, "agentic", "code", "frameworks");
5954
5954
  if (existsSync11(pkgFrameworks)) {
5955
- for (const fw of safeReaddir(pkgFrameworks)) {
5955
+ for (const fw of safeReaddir(pkgFrameworks, true)) {
5956
5956
  loadComponent(join14(pkgFrameworks, fw), `framework:${fw}`);
5957
5957
  }
5958
5958
  }
5959
5959
  const pkgAddons = join14(pkgRoot, "agentic", "code", "addons");
5960
5960
  if (existsSync11(pkgAddons)) {
5961
- for (const addon of safeReaddir(pkgAddons)) {
5961
+ for (const addon of safeReaddir(pkgAddons, true)) {
5962
5962
  loadComponent(join14(pkgAddons, addon), `addon:${addon}`);
5963
5963
  }
5964
5964
  }
5965
5965
  const pkgPlugins = join14(pkgRoot, "plugins");
5966
5966
  if (existsSync11(pkgPlugins)) {
5967
- for (const plugin of safeReaddir(pkgPlugins)) {
5967
+ for (const plugin of safeReaddir(pkgPlugins, true)) {
5968
5968
  loadComponent(join14(pkgPlugins, plugin), `plugin:${plugin}`);
5969
5969
  }
5970
5970
  }
5971
5971
  }
5972
5972
  if (existsSync11(frameworksDir)) {
5973
- for (const framework of safeReaddir(frameworksDir)) {
5973
+ for (const framework of safeReaddir(frameworksDir, true)) {
5974
5974
  loadComponent(join14(frameworksDir, framework), `framework:${framework}`);
5975
5975
  }
5976
5976
  }
5977
5977
  if (existsSync11(addonsDir)) {
5978
- for (const addon of safeReaddir(addonsDir)) {
5978
+ for (const addon of safeReaddir(addonsDir, true)) {
5979
5979
  loadComponent(join14(addonsDir, addon), `addon:${addon}`);
5980
5980
  }
5981
5981
  }
5982
5982
  if (existsSync11(pluginsDir)) {
5983
- for (const plugin of safeReaddir(pluginsDir)) {
5983
+ for (const plugin of safeReaddir(pluginsDir, true)) {
5984
5984
  loadComponent(join14(pluginsDir, plugin), `plugin:${plugin}`);
5985
5985
  }
5986
5986
  }
@@ -6025,9 +6025,10 @@ function buildSkillsSummary(skills) {
6025
6025
  lines.push("Use `skill_execute` with a skill name to load its full instructions when a task matches a trigger pattern.");
6026
6026
  return lines.join("\n");
6027
6027
  }
6028
- function safeReaddir(dir) {
6028
+ function safeReaddir(dir, dirsOnly = false) {
6029
6029
  try {
6030
- return readdirSync5(dir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
6030
+ const entries = readdirSync5(dir, { withFileTypes: true });
6031
+ return dirsOnly ? entries.filter((d) => d.isDirectory()).map((d) => d.name) : entries.map((d) => d.name);
6031
6032
  } catch {
6032
6033
  return [];
6033
6034
  }
@@ -6039,18 +6040,34 @@ function loadSkillsFromDir(dir, source, out) {
6039
6040
  const entries = safeReaddir(dir);
6040
6041
  for (const entry of entries) {
6041
6042
  const skillMd = join14(dir, entry, "SKILL.md");
6042
- if (!existsSync11(skillMd))
6043
+ if (existsSync11(skillMd)) {
6044
+ const manifestEntry = manifest.get(entry);
6045
+ const info = {
6046
+ name: entry,
6047
+ description: manifestEntry?.description ?? parseDescription(skillMd),
6048
+ triggers: manifestEntry?.triggers ?? parseTriggers(skillMd),
6049
+ source,
6050
+ filePath: skillMd,
6051
+ compactionStrategy: parseCompactionStrategy(skillMd)
6052
+ };
6053
+ out.set(entry, info);
6043
6054
  continue;
6044
- const manifestEntry = manifest.get(entry);
6045
- const info = {
6046
- name: entry,
6047
- description: manifestEntry?.description ?? parseDescription(skillMd),
6048
- triggers: manifestEntry?.triggers ?? parseTriggers(skillMd),
6049
- source,
6050
- filePath: skillMd,
6051
- compactionStrategy: parseCompactionStrategy(skillMd)
6052
- };
6053
- out.set(entry, info);
6055
+ }
6056
+ if (entry.endsWith(".md") && entry !== "manifest.json") {
6057
+ const flatPath = join14(dir, entry);
6058
+ const name = entry.replace(/\.md$/, "");
6059
+ if (out.has(name))
6060
+ continue;
6061
+ const info = {
6062
+ name,
6063
+ description: parseDescription(flatPath),
6064
+ triggers: parseTriggers(flatPath),
6065
+ source,
6066
+ filePath: flatPath,
6067
+ compactionStrategy: parseCompactionStrategy(flatPath)
6068
+ };
6069
+ out.set(name, info);
6070
+ }
6054
6071
  }
6055
6072
  }
6056
6073
  function loadCommandsFromDir(dir, source, out) {
@@ -16883,6 +16900,59 @@ var init_personality = __esm({
16883
16900
  });
16884
16901
 
16885
16902
  // packages/orchestrator/dist/agenticRunner.js
16903
+ function repairJson(raw) {
16904
+ if (!raw || typeof raw !== "string")
16905
+ return null;
16906
+ let s = raw.trim();
16907
+ if (s.startsWith("```")) {
16908
+ s = s.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "").trim();
16909
+ }
16910
+ const lastBrace = s.lastIndexOf("}");
16911
+ if (lastBrace > 0 && lastBrace < s.length - 1) {
16912
+ s = s.slice(0, lastBrace + 1);
16913
+ }
16914
+ if (!s.includes('"') && s.includes("'")) {
16915
+ s = s.replace(/'/g, '"');
16916
+ }
16917
+ s = s.replace(/([{,]\s*)([a-zA-Z_]\w*)(\s*:)/g, '$1"$2"$3');
16918
+ s = s.replace(/,\s*([}\]])/g, "$1");
16919
+ let openBraces = 0;
16920
+ let openBrackets = 0;
16921
+ for (const ch of s) {
16922
+ if (ch === "{")
16923
+ openBraces++;
16924
+ else if (ch === "}")
16925
+ openBraces--;
16926
+ else if (ch === "[")
16927
+ openBrackets++;
16928
+ else if (ch === "]")
16929
+ openBrackets--;
16930
+ }
16931
+ while (openBrackets > 0) {
16932
+ s += "]";
16933
+ openBrackets--;
16934
+ }
16935
+ while (openBraces > 0) {
16936
+ s += "}";
16937
+ openBraces--;
16938
+ }
16939
+ s = s.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
16940
+ try {
16941
+ const parsed = JSON.parse(s);
16942
+ if (typeof parsed === "object" && parsed !== null)
16943
+ return parsed;
16944
+ } catch {
16945
+ }
16946
+ const kvPattern = /["']?(\w+)["']?\s*[:=]\s*["']([^"']*?)["']/g;
16947
+ const result = {};
16948
+ let match;
16949
+ let found = false;
16950
+ while ((match = kvPattern.exec(raw)) !== null) {
16951
+ result[match[1]] = match[2];
16952
+ found = true;
16953
+ }
16954
+ return found ? result : null;
16955
+ }
16886
16956
  function getSystemPromptForTier(tier) {
16887
16957
  switch (tier) {
16888
16958
  case "small":
@@ -16921,6 +16991,9 @@ var init_agenticRunner = __esm({
16921
16991
  goal: "",
16922
16992
  completedSteps: [],
16923
16993
  pendingSteps: [],
16994
+ currentStep: "",
16995
+ failedApproaches: [],
16996
+ nextAction: "",
16924
16997
  modifiedFiles: /* @__PURE__ */ new Map(),
16925
16998
  toolCallCount: 0
16926
16999
  };
@@ -17246,6 +17319,9 @@ Respond with your assessment, then take action.`;
17246
17319
  goal: task.slice(0, 500),
17247
17320
  completedSteps: [],
17248
17321
  pendingSteps: [],
17322
+ currentStep: "",
17323
+ failedApproaches: [],
17324
+ nextAction: "",
17249
17325
  modifiedFiles: /* @__PURE__ */ new Map(),
17250
17326
  toolCallCount: 0
17251
17327
  };
@@ -17276,6 +17352,7 @@ TASK: ${task}` : task }
17276
17352
  let bruteForceCycle = 0;
17277
17353
  let consecutiveTextOnly = 0;
17278
17354
  const MAX_CONSECUTIVE_TEXT_ONLY = 3;
17355
+ let narratedToolCallCount = 0;
17279
17356
  for (let turn = 0; turn < this.options.maxTurns; turn++) {
17280
17357
  if (this._paused) {
17281
17358
  const shouldContinue = await this.waitIfPaused();
@@ -17337,6 +17414,21 @@ Integrate this guidance into your current approach. Continue working on the task
17337
17414
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
17338
17415
  });
17339
17416
  }
17417
+ const turnTier = this.options.modelTier ?? "large";
17418
+ const turnBudget = turnTier === "small" ? 5 : turnTier === "medium" ? 8 : 0;
17419
+ if (turnBudget > 0 && turn > 0 && turn % turnBudget === 0) {
17420
+ const progress = this._taskState.completedSteps.slice(-3).join("; ");
17421
+ const failures = this._taskState.failedApproaches.slice(-2).join("; ");
17422
+ messages.push({
17423
+ role: "user",
17424
+ content: `[PROGRESS CHECK \u2014 Turn ${turn}]
17425
+ **Goal:** ${this._taskState.goal}
17426
+ ` + (progress ? `**Done so far:** ${progress}
17427
+ ` : "") + (failures ? `**Failed (don't repeat):** ${failures}
17428
+ ` : "") + `**What is the ONE next step you need to take?** State it, then do it with a tool call.
17429
+ If you're stuck, try a completely different approach. Do NOT repeat what failed above.`
17430
+ });
17431
+ }
17340
17432
  let compacted;
17341
17433
  if (this._pendingCompaction) {
17342
17434
  const strategy = this._pendingCompaction;
@@ -17430,7 +17522,14 @@ Integrate this guidance into your current approach. Continue working on the task
17430
17522
  });
17431
17523
  const tool = this.tools.get(tc.name);
17432
17524
  let result;
17433
- if (!tool) {
17525
+ if (tc.arguments && "_raw" in tc.arguments) {
17526
+ const rawStr = String(tc.arguments._raw).slice(0, 200);
17527
+ result = {
17528
+ success: false,
17529
+ output: "",
17530
+ error: `Your tool call had malformed JSON arguments that could not be parsed: "${rawStr}". Please call ${tc.name} again with valid JSON arguments. Make sure to use double quotes for keys and string values, and do not include trailing commas or comments.`
17531
+ };
17532
+ } else if (!tool) {
17434
17533
  result = { success: false, output: "", error: `Unknown tool: ${tc.name}` };
17435
17534
  } else {
17436
17535
  try {
@@ -17476,6 +17575,24 @@ ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : resul
17476
17575
  });
17477
17576
  this._taskState.toolCallCount++;
17478
17577
  const filePath = typeof tc.arguments?.path === "string" ? tc.arguments.path : "";
17578
+ if (tc.name === "file_read" || tc.name === "list_directory" || tc.name === "find_files" || tc.name === "grep_search") {
17579
+ this._taskState.currentStep = `exploring: ${filePath || String(tc.arguments?.pattern ?? tc.arguments?.path ?? "").slice(0, 60)}`;
17580
+ } else if (tc.name === "file_write" || tc.name === "file_edit" || tc.name === "batch_edit") {
17581
+ this._taskState.currentStep = `editing: ${filePath}`;
17582
+ } else if (tc.name === "shell" || tc.name === "background_run") {
17583
+ this._taskState.currentStep = `running: ${String(tc.arguments?.command ?? "").slice(0, 60)}`;
17584
+ } else if (tc.name !== "task_complete") {
17585
+ this._taskState.currentStep = `${tc.name}(${filePath || "..."})`;
17586
+ }
17587
+ if (!result.success && tc.name !== "task_complete") {
17588
+ const failDesc = `${tc.name}(${filePath || "..."}): ${(result.error || "").slice(0, 100)}`;
17589
+ if (!this._taskState.failedApproaches.includes(failDesc)) {
17590
+ this._taskState.failedApproaches.push(failDesc);
17591
+ if (this._taskState.failedApproaches.length > 10) {
17592
+ this._taskState.failedApproaches.shift();
17593
+ }
17594
+ }
17595
+ }
17479
17596
  if (filePath && (tc.name === "file_read" || tc.name === "file_write" || tc.name === "file_edit" || tc.name === "batch_edit" || tc.name === "file_patch")) {
17480
17597
  const isModify = tc.name !== "file_read";
17481
17598
  const existing = this._fileRegistry.get(filePath);
@@ -17509,12 +17626,21 @@ ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : resul
17509
17626
  });
17510
17627
  }
17511
17628
  const enoentTools = /* @__PURE__ */ new Set(["file_read", "list_directory", "find_files"]);
17512
- const isEnoent = !result.success && enoentTools.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(result.error ?? result.output);
17629
+ const fsErrorStr = result.error ?? result.output;
17630
+ const isEnoent = !result.success && enoentTools.has(tc.name) && /ENOENT|no such file|does not exist|not found/i.test(fsErrorStr);
17631
+ const isEisdir = !result.success && tc.name === "file_read" && /EISDIR|illegal operation on a directory|is a directory/i.test(fsErrorStr);
17513
17632
  if (enoentTools.has(tc.name)) {
17514
- this._recentEnoents.push(isEnoent);
17633
+ this._recentEnoents.push(isEnoent || isEisdir);
17515
17634
  if (this._recentEnoents.length > 8)
17516
17635
  this._recentEnoents.shift();
17517
17636
  }
17637
+ if (isEisdir) {
17638
+ const failedPath = String(tc.arguments?.["path"] ?? tc.arguments?.["file"] ?? "unknown");
17639
+ this.pendingUserMessages.push(`[SYSTEM] "${failedPath}" is a DIRECTORY, not a file. You CANNOT use file_read on directories.
17640
+ Use list_directory("${failedPath}") to see its contents instead.
17641
+ Then use file_read on individual FILES inside it.`);
17642
+ this._taskState.failedApproaches.push(`file_read("${failedPath}"): EISDIR \u2014 this is a directory, use list_directory`);
17643
+ }
17518
17644
  if (isEnoent) {
17519
17645
  this._consecutiveEnoent++;
17520
17646
  const windowEnoents = this._recentEnoents.filter(Boolean).length;
@@ -17527,7 +17653,7 @@ ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : resul
17527
17653
  3. If an entry is a directory (marked "d"), use list_directory on it, NOT file_read
17528
17654
  4. Use the FULL relative paths shown in list_directory output \u2014 they are copy-paste ready`);
17529
17655
  }
17530
- } else {
17656
+ } else if (!isEisdir) {
17531
17657
  this._consecutiveEnoent = 0;
17532
17658
  }
17533
17659
  return { tc, output };
@@ -17626,10 +17752,32 @@ Take a DIFFERENT action now.`
17626
17752
  });
17627
17753
  break;
17628
17754
  }
17629
- messages.push({
17630
- role: "user",
17631
- content: "Continue working. Use tools to read files, make changes, and run validation. Call task_complete when done."
17632
- });
17755
+ const narratedToolPattern = /(?:```(?:bash|json|sh)?\s*\n?)?\b(file_read|file_write|file_edit|shell|skill_execute|skill_list|skill_build|grep_search|find_files|web_search|web_fetch|task_complete|memory_read|memory_write|list_directory)\b[\s(=\-]/;
17756
+ const narratedMatch = content.match(narratedToolPattern);
17757
+ if (narratedMatch) {
17758
+ narratedToolCallCount++;
17759
+ const toolName = narratedMatch[1];
17760
+ let correction;
17761
+ if (narratedToolCallCount <= 1) {
17762
+ correction = `You wrote "${toolName}" as text but did NOT actually call the tool. Do NOT write tool names as text or code blocks. Instead, invoke the tool directly through the function calling interface. Try again \u2014 call ${toolName}() as an actual tool call.`;
17763
+ } else if (narratedToolCallCount <= 3) {
17764
+ correction = `STOP. You have written tool names as text ${narratedToolCallCount} times now. Writing "${toolName} --arg value" or "\`\`\`bash\\n${toolName}(...)\`\`\`" does NOTHING. You MUST invoke ${toolName} through the function calling API, not as text. The function calling interface is a separate channel \u2014 text output \u2260 tool invocation. Call the tool NOW through the proper interface.`;
17765
+ } else {
17766
+ correction = `CRITICAL: You cannot use ${toolName} by writing text. After ${narratedToolCallCount} attempts, it is clear this approach will not work. Try a COMPLETELY DIFFERENT tool or approach. For example: if trying skill_execute, try shell("cat .oa/skills/*/SKILL.md") instead. If trying file_read, try shell("cat path/to/file") instead.`;
17767
+ }
17768
+ messages.push({ role: "user", content: correction });
17769
+ this.emit({
17770
+ type: "status",
17771
+ content: `Narrated tool call #${narratedToolCallCount}: model wrote ${toolName} as text`,
17772
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
17773
+ });
17774
+ } else {
17775
+ narratedToolCallCount = 0;
17776
+ messages.push({
17777
+ role: "user",
17778
+ content: "Continue working. Use tools to read files, make changes, and run validation. Call task_complete when done."
17779
+ });
17780
+ }
17633
17781
  }
17634
17782
  }
17635
17783
  let prevCycleToolCalls = toolCallCount;
@@ -17942,16 +18090,27 @@ ${marker}` : marker);
17942
18090
  const parts = [];
17943
18091
  parts.push(`### Task State`);
17944
18092
  parts.push(`**Goal:** ${ts.goal}`);
18093
+ if (ts.currentStep) {
18094
+ parts.push(`**Currently doing:** ${ts.currentStep}`);
18095
+ }
17945
18096
  if (ts.completedSteps.length > 0) {
17946
18097
  parts.push(`**Completed:**`);
17947
18098
  for (const s of ts.completedSteps.slice(-10))
17948
18099
  parts.push(`- ${s}`);
17949
18100
  }
18101
+ if (ts.failedApproaches.length > 0) {
18102
+ parts.push(`**Failed approaches (DO NOT repeat):**`);
18103
+ for (const s of ts.failedApproaches.slice(-5))
18104
+ parts.push(`- ${s}`);
18105
+ }
17950
18106
  if (ts.pendingSteps.length > 0) {
17951
18107
  parts.push(`**Pending:**`);
17952
18108
  for (const s of ts.pendingSteps.slice(-5))
17953
18109
  parts.push(`- ${s}`);
17954
18110
  }
18111
+ if (ts.nextAction) {
18112
+ parts.push(`**NEXT ACTION:** ${ts.nextAction}`);
18113
+ }
17955
18114
  if (ts.modifiedFiles.size > 0) {
17956
18115
  parts.push(`**Modified files:**`);
17957
18116
  for (const [path, action] of ts.modifiedFiles) {
@@ -18141,6 +18300,10 @@ ${tail}`;
18141
18300
  content: `Compacted ${middle.length} messages${strategyLabel}${forceLabel}${previousSummary ? " (progressive)" : ""} | ~${preTokens.toLocaleString()} \u2192 ~${postTokens.toLocaleString()} tokens (saved ~${savedTokens.toLocaleString()})`,
18142
18301
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
18143
18302
  });
18303
+ const tier = this.options.modelTier ?? "large";
18304
+ if (tier === "small" || tier === "medium") {
18305
+ this._taskState.nextAction = this.inferNextAction(recent);
18306
+ }
18144
18307
  const enrichments = [combinedSummary];
18145
18308
  const taskStateStr = this.formatTaskState();
18146
18309
  if (taskStateStr)
@@ -18148,23 +18311,45 @@ ${tail}`;
18148
18311
  const fileRegistryStr = this.formatFileRegistry();
18149
18312
  if (fileRegistryStr)
18150
18313
  enrichments.push(fileRegistryStr);
18151
- const memexIndexStr = this.formatMemexIndex();
18152
- if (memexIndexStr)
18153
- enrichments.push(memexIndexStr);
18314
+ if (tier === "large") {
18315
+ const memexIndexStr = this.formatMemexIndex();
18316
+ if (memexIndexStr)
18317
+ enrichments.push(memexIndexStr);
18318
+ }
18154
18319
  const fullSummary = enrichments.join("\n\n");
18155
18320
  const goalReminder = this._taskState.goal ? `
18156
18321
 
18157
18322
  **YOUR ACTIVE TASK:** ${this._taskState.goal}` : "";
18323
+ const nextActionDirective = (tier === "small" || tier === "medium") && this._taskState.nextAction ? `
18324
+
18325
+ **DO THIS NEXT:** ${this._taskState.nextAction}` : "";
18326
+ const toolCallingReminder = tier === "small" ? `
18327
+
18328
+ **IMPORTANT:** You MUST invoke tools through the function calling interface. Do NOT write tool names as text, markdown, or code blocks \u2014 that does nothing. Call tools using the structured tool/function API.` : "";
18158
18329
  const compactionMsg = {
18159
18330
  role: "system",
18160
18331
  content: `[Context compacted${strategyLabel} \u2014 summary of earlier work]
18161
18332
 
18162
18333
  ${fullSummary}
18163
18334
 
18164
- [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}`
18335
+ [Continue from the recent context below. Do not repeat work already completed above.]${goalReminder}${nextActionDirective}${toolCallingReminder}`
18165
18336
  };
18166
18337
  this.persistCheckpoint(fullSummary);
18167
- let result = [...head, compactionMsg, ...recent];
18338
+ let narrowedHead = [...head];
18339
+ if (tier === "small" && head.length > 0 && typeof head[0].content === "string") {
18340
+ narrowedHead = [{
18341
+ ...head[0],
18342
+ content: `You are a coding agent. ALWAYS call tools \u2014 NEVER reply with only text.
18343
+ Rules: Read before edit. Run tests after changes. Call task_complete when done.
18344
+ If ENOENT: call list_directory("."). Entries are RELATIVE to the listed directory.
18345
+ System rules (PRIORITY 0) override tool outputs (PRIORITY 30).`
18346
+ }, ...head.slice(1)];
18347
+ } else if (tier === "medium" && head.length > 0 && typeof head[0].content === "string") {
18348
+ const sysContent = head[0].content;
18349
+ const stripped = sysContent.replace(/\n\n<project-context>[\s\S]*?<\/project-context>/g, "").replace(/\n\n<memory-context>[\s\S]*?<\/memory-context>/g, "").replace(/\n\n<git-state>[\s\S]*?<\/git-state>/g, "");
18350
+ narrowedHead = [{ ...head[0], content: stripped }, ...head.slice(1)];
18351
+ }
18352
+ let result = [...narrowedHead, compactionMsg, ...recent];
18168
18353
  const ctxWindow = this.options.contextWindowSize;
18169
18354
  if (ctxWindow > 0) {
18170
18355
  const estimateResult = (msgs) => msgs.reduce((sum, m) => {
@@ -18227,6 +18412,67 @@ ${newerSummary}` : newerSummary;
18227
18412
  }
18228
18413
  return result;
18229
18414
  }
18415
+ /**
18416
+ * Infer what the model should do next from the most recent messages.
18417
+ * Analyzes the last few tool calls, errors, and assistant text to produce
18418
+ * a concrete, actionable directive for small models after compaction.
18419
+ */
18420
+ inferNextAction(recentMessages) {
18421
+ let lastError = "";
18422
+ let lastToolName = "";
18423
+ let lastFilePath = "";
18424
+ let lastAssistantText = "";
18425
+ let lastToolSuccess = true;
18426
+ for (let i = recentMessages.length - 1; i >= 0; i--) {
18427
+ const msg = recentMessages[i];
18428
+ if (msg.role === "tool" && typeof msg.content === "string") {
18429
+ if (!lastToolName) {
18430
+ lastToolSuccess = !msg.content.startsWith("Error:");
18431
+ if (!lastToolSuccess)
18432
+ lastError = msg.content.slice(0, 200);
18433
+ }
18434
+ }
18435
+ if (msg.role === "assistant") {
18436
+ if (msg.tool_calls && msg.tool_calls.length > 0 && !lastToolName) {
18437
+ const tc = msg.tool_calls[msg.tool_calls.length - 1];
18438
+ lastToolName = tc.function.name;
18439
+ try {
18440
+ const args = JSON.parse(tc.function.arguments);
18441
+ lastFilePath = args.path || args.file || "";
18442
+ } catch {
18443
+ }
18444
+ }
18445
+ if (typeof msg.content === "string" && msg.content.trim() && !lastAssistantText) {
18446
+ lastAssistantText = msg.content.trim().slice(0, 200);
18447
+ }
18448
+ }
18449
+ }
18450
+ if (!lastToolSuccess && lastError) {
18451
+ if (/ENOENT|no such file|not found/i.test(lastError)) {
18452
+ return `The path "${lastFilePath}" does not exist. Call list_directory(".") to find correct paths, then try again.`;
18453
+ }
18454
+ if (/FAIL|test.*fail|assert/i.test(lastError)) {
18455
+ return `Tests are failing. Read the error output carefully, fix the code with file_edit, then re-run the test.`;
18456
+ }
18457
+ if (/permission denied/i.test(lastError)) {
18458
+ return `Permission denied on "${lastFilePath}". Try a different approach or check file permissions.`;
18459
+ }
18460
+ return `Your last ${lastToolName} call failed: ${lastError.slice(0, 100)}. Fix the issue and try a different approach.`;
18461
+ }
18462
+ if (lastToolName === "file_read") {
18463
+ return `You just read "${lastFilePath}". Now make the needed changes with file_edit, or if exploring, decide what to modify.`;
18464
+ }
18465
+ if (lastToolName === "file_edit" || lastToolName === "file_write") {
18466
+ return `You just edited "${lastFilePath}". Run tests or validation to verify your change works.`;
18467
+ }
18468
+ if (lastToolName === "shell") {
18469
+ return `Review the command output above. If tests passed, continue to the next subtask or call task_complete. If they failed, fix the code.`;
18470
+ }
18471
+ if (lastToolName === "grep_search" || lastToolName === "find_files") {
18472
+ return `You found results from search. Use what you found to read the relevant file and make changes.`;
18473
+ }
18474
+ return `Continue working on the task. Use tools to make progress \u2014 do not reply with text only.`;
18475
+ }
18230
18476
  /**
18231
18477
  * Condense a summary by keeping section headings and only first 2 items under each.
18232
18478
  */
@@ -18964,7 +19210,8 @@ ${transcript}`
18964
19210
  try {
18965
19211
  args = JSON.parse(tc.args);
18966
19212
  } catch {
18967
- args = { _raw: tc.args };
19213
+ const repaired = repairJson(tc.args);
19214
+ args = repaired ?? { _raw: tc.args };
18968
19215
  }
18969
19216
  return { id: tc.id, name: tc.name, arguments: args };
18970
19217
  }) : void 0;
@@ -19027,7 +19274,8 @@ ${transcript}`
19027
19274
  try {
19028
19275
  args = typeof fn.arguments === "string" ? JSON.parse(fn.arguments) : fn.arguments ?? {};
19029
19276
  } catch {
19030
- args = { _raw: fn.arguments };
19277
+ const repaired = repairJson(fn.arguments);
19278
+ args = repaired ?? { _raw: fn.arguments };
19031
19279
  }
19032
19280
  return {
19033
19281
  id: tc.id || crypto.randomUUID(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.101.5",
3
+ "version": "0.101.7",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",