open-agents-ai 0.187.222 → 0.187.224

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 +304 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -317843,6 +317843,117 @@ function appendExpandableContent(parent, fullText, opts) {
317843
317843
  return wrapper;
317844
317844
  }
317845
317845
 
317846
+ // WO-CHAT-RESUME-TOOLS — render a single tool_call event into a parent
317847
+ // container. Used by BOTH the live SSE handler AND the chat history
317848
+ // restore path so reloads see the same intermediate-state dropdowns
317849
+ // they saw during the original turn. The chunkLike object accepts the
317850
+ // SSE chunk shape ({type:'tool_call', tool, args}) OR the persisted
317851
+ // session message shape ({role:'tool_call', tool, args}).
317852
+ function renderToolCallEvent(parent, chunkLike) {
317853
+ const details = document.createElement('details');
317854
+ details.style.cssText = 'background:#1e1e22;border-left:2px solid #b2920a;margin:2px 0;font-size:0.7rem';
317855
+ const summary = document.createElement('summary');
317856
+ summary.style.cssText = 'padding:4px 8px;color:#b2920a;cursor:pointer';
317857
+
317858
+ const toolName = chunkLike.tool || 'tool';
317859
+ let a = (chunkLike.args && typeof chunkLike.args === 'object') ? chunkLike.args : {};
317860
+ if (typeof chunkLike.args === 'string') {
317861
+ try { a = JSON.parse(chunkLike.args); } catch { a = { _raw: chunkLike.args }; }
317862
+ }
317863
+ if (toolName === 'todo_write' && typeof a.todos === 'string') {
317864
+ try {
317865
+ const parsed = JSON.parse(a.todos);
317866
+ if (Array.isArray(parsed)) a = Object.assign({}, a, { todos: parsed });
317867
+ } catch {}
317868
+ }
317869
+
317870
+ let inlineSummary = '';
317871
+ if (toolName === 'todo_write' && Array.isArray(a.todos)) {
317872
+ const todos = a.todos;
317873
+ let p = 0, ip = 0, c = 0, b = 0;
317874
+ for (const t of todos) {
317875
+ const s = (t && typeof t === 'object') ? t.status : '';
317876
+ if (s === 'completed') c++;
317877
+ else if (s === 'in_progress') ip++;
317878
+ else if (s === 'blocked') b++;
317879
+ else p++;
317880
+ }
317881
+ inlineSummary = ' \\u2014 ' + todos.length + ' items (' + c + '\\u25C9 ' + ip + '\\u25D0 ' + p + '\\u25CB' + (b > 0 ? ' ' + b + '\\u25CD' : '') + ')';
317882
+ } else {
317883
+ const previewParts = [];
317884
+ for (const [k, v] of Object.entries(a)) {
317885
+ let vs;
317886
+ if (v === null || v === undefined) vs = String(v);
317887
+ else if (typeof v === 'string') vs = v;
317888
+ else if (typeof v === 'number' || typeof v === 'boolean') vs = String(v);
317889
+ else { try { vs = JSON.stringify(v); } catch { vs = '[object]'; } }
317890
+ if (vs.length > 60) vs = vs.slice(0, 57) + '\\u2026';
317891
+ previewParts.push(k + '=' + vs);
317892
+ if (previewParts.length >= 3) break;
317893
+ }
317894
+ if (previewParts.length) inlineSummary = ' \\u2014 ' + previewParts.join(', ');
317895
+ }
317896
+ summary.textContent = '\\u25B8 ' + toolName + inlineSummary;
317897
+ details.appendChild(summary);
317898
+
317899
+ if (a && typeof a === 'object') {
317900
+ const argsDiv = document.createElement('div');
317901
+ argsDiv.style.cssText = 'padding:4px 8px 6px 16px;color:#888;font-size:0.65rem;border-top:1px solid #2a2a30';
317902
+ if (toolName === 'todo_write' && Array.isArray(a.todos)) {
317903
+ for (const t of a.todos) {
317904
+ if (!t || typeof t !== 'object') continue;
317905
+ const row = document.createElement('div');
317906
+ row.style.cssText = 'padding:2px 0';
317907
+ let mark = '\\u25CB';
317908
+ let color = '#666';
317909
+ if (t.status === 'completed') { mark = '\\u25C9'; color = '#4a7a4a'; }
317910
+ else if (t.status === 'in_progress') { mark = '\\u25D0'; color = '#b2920a'; }
317911
+ else if (t.status === 'blocked') { mark = '\\u25CD'; color = '#b25f5f'; }
317912
+ row.innerHTML = '<span style="color:' + color + '">' + mark + '</span> <span style="color:#b0b0b0">' + escHtml(String(t.content || '').slice(0, 300)) + '</span>' + (t.blocker ? ' <span style="color:#b25f5f">(blocked: ' + escHtml(String(t.blocker).slice(0, 100)) + ')</span>' : '');
317913
+ argsDiv.appendChild(row);
317914
+ }
317915
+ } else {
317916
+ for (const [k, v] of Object.entries(a)) {
317917
+ const row = document.createElement('div');
317918
+ row.style.cssText = 'padding:2px 0;display:flex;gap:8px;align-items:flex-start';
317919
+ let vs;
317920
+ if (v === null || v === undefined) vs = String(v);
317921
+ else if (typeof v === 'string') vs = v;
317922
+ else if (typeof v === 'number' || typeof v === 'boolean') vs = String(v);
317923
+ else { try { vs = JSON.stringify(v, null, 2); } catch { vs = '[object]'; } }
317924
+
317925
+ const keyEl = document.createElement('span');
317926
+ keyEl.style.cssText = 'color:#b2920a;min-width:60px;flex-shrink:0';
317927
+ keyEl.textContent = k;
317928
+ row.appendChild(keyEl);
317929
+
317930
+ const valWrap = document.createElement('div');
317931
+ valWrap.style.cssText = 'flex:1;min-width:0;color:#b0b0b0';
317932
+ appendExpandableContent(valWrap, vs, { truncateAt: 500, baseStyle: 'color:#b0b0b0;' });
317933
+ row.appendChild(valWrap);
317934
+
317935
+ argsDiv.appendChild(row);
317936
+ }
317937
+ }
317938
+ details.appendChild(argsDiv);
317939
+ }
317940
+ parent.appendChild(details);
317941
+ return details;
317942
+ }
317943
+
317944
+ // WO-CHAT-RESUME-TOOLS — render a single tool_result event with the
317945
+ // show-more/hide expandable helper. Used by both live SSE and restore.
317946
+ function renderToolResultEvent(parent, chunkLike) {
317947
+ const resultEl = document.createElement('div');
317948
+ const errStyle = chunkLike.success === false
317949
+ ? 'background:#2a1e1e;border-left:2px solid #b25f5f;color:#b25f5f;'
317950
+ : 'background:#1e1e22;color:#888;';
317951
+ resultEl.style.cssText = errStyle + 'padding:4px 8px 4px 18px;margin:0 0 2px 0;font-size:0.65rem';
317952
+ appendExpandableContent(resultEl, chunkLike.output || '', { truncateAt: 150, baseStyle: 'color:inherit;' });
317953
+ parent.appendChild(resultEl);
317954
+ return resultEl;
317955
+ }
317956
+
317846
317957
  async function sendMessage() {
317847
317958
  const text = input.value.trim();
317848
317959
  if (!text || streaming) return;
@@ -319835,12 +319946,54 @@ async function restoreChatSession() {
319835
319946
  if (!data || !data.id) return;
319836
319947
  chatSessionId = data.id;
319837
319948
  window.currentSessionId = data.id;
319838
- messages = (data.messages || []).map(m => ({ role: m.role, content: m.content }));
319949
+ // Keep the in-memory messages array clean (only user/assistant) so
319950
+ // the next inference call sees a valid conversation. Tool events
319951
+ // are still rendered into the DOM via the dropdown helpers below.
319952
+ const allMessages = data.messages || [];
319953
+ messages = allMessages
319954
+ .filter(m => m.role === 'user' || m.role === 'assistant')
319955
+ .map(m => ({ role: m.role, content: m.content }));
319839
319956
  const conv = document.getElementById('conversation');
319840
319957
  if (conv) {
319841
319958
  conv.innerHTML = '';
319842
- for (const m of messages) {
319843
- addMessage(m.role, m.content);
319959
+ // WO-CHAT-RESUME-TOOLS replay the FULL intermediate flow on
319960
+ // restore: user/assistant text bubbles AND tool_call/tool_result
319961
+ // dropdowns interleaved in original order. We track the current
319962
+ // assistant bubble's tools container so consecutive tool events
319963
+ // attach to the same bubble (the live SSE handler does the same).
319964
+ let currentAssistantTools = null;
319965
+ for (const m of allMessages) {
319966
+ if (m.role === 'user') {
319967
+ addMessage('user', m.content);
319968
+ currentAssistantTools = null;
319969
+ } else if (m.role === 'assistant') {
319970
+ const msgDiv = addMessage('assistant', m.content);
319971
+ // Stage a tools container for any tool events that come AFTER
319972
+ // this assistant turn (small models sometimes emit text first
319973
+ // then tool calls — we attach those tools to the most recent
319974
+ // assistant bubble for visual coherence).
319975
+ currentAssistantTools = document.createElement('div');
319976
+ currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
319977
+ msgDiv.appendChild(currentAssistantTools);
319978
+ } else if (m.role === 'tool_call') {
319979
+ // If no assistant bubble yet (tool fired before any text),
319980
+ // create one with empty content so the dropdown has a parent.
319981
+ if (!currentAssistantTools) {
319982
+ const msgDiv = addMessage('assistant', '');
319983
+ currentAssistantTools = document.createElement('div');
319984
+ currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
319985
+ msgDiv.appendChild(currentAssistantTools);
319986
+ }
319987
+ renderToolCallEvent(currentAssistantTools, { tool: m.tool, args: m.args });
319988
+ } else if (m.role === 'tool_result') {
319989
+ if (!currentAssistantTools) {
319990
+ const msgDiv = addMessage('assistant', '');
319991
+ currentAssistantTools = document.createElement('div');
319992
+ currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
319993
+ msgDiv.appendChild(currentAssistantTools);
319994
+ }
319995
+ renderToolResultEvent(currentAssistantTools, { output: m.output, success: m.success });
319996
+ }
319844
319997
  }
319845
319998
  }
319846
319999
  try { refreshTodos(data.id); } catch {}
@@ -319884,11 +320037,31 @@ function attachInFlightPoller(sessionId, initialJob) {
319884
320037
  const data = await r.json();
319885
320038
  const job = data.in_flight;
319886
320039
  if (!job) {
319887
- messages = (data.messages || []).map(m => ({ role: m.role, content: m.content }));
320040
+ // WO-CHAT-RESUME-TOOLS re-run the same restore logic so the
320041
+ // poller's terminal repaint also includes tool dropdowns.
320042
+ const allMessages = data.messages || [];
320043
+ messages = allMessages
320044
+ .filter(m => m.role === 'user' || m.role === 'assistant')
320045
+ .map(m => ({ role: m.role, content: m.content }));
319888
320046
  const conv2 = document.getElementById('conversation');
319889
320047
  if (conv2) {
319890
320048
  conv2.innerHTML = '';
319891
- for (const m of messages) addMessage(m.role, m.content);
320049
+ let curTools = null;
320050
+ for (const m of allMessages) {
320051
+ if (m.role === 'user') { addMessage('user', m.content); curTools = null; }
320052
+ else if (m.role === 'assistant') {
320053
+ const md = addMessage('assistant', m.content);
320054
+ curTools = document.createElement('div');
320055
+ curTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
320056
+ md.appendChild(curTools);
320057
+ } else if (m.role === 'tool_call') {
320058
+ if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
320059
+ renderToolCallEvent(curTools, { tool: m.tool, args: m.args });
320060
+ } else if (m.role === 'tool_result') {
320061
+ if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
320062
+ renderToolResultEvent(curTools, { output: m.output, success: m.success });
320063
+ }
320064
+ }
319892
320065
  }
319893
320066
  stopPolling();
319894
320067
  return;
@@ -319903,11 +320076,30 @@ function attachInFlightPoller(sessionId, initialJob) {
319903
320076
  } else if (job.error) {
319904
320077
  placeholder.textContent = 'Error: ' + job.error;
319905
320078
  }
319906
- messages = (data.messages || []).map(m => ({ role: m.role, content: m.content }));
320079
+ // WO-CHAT-RESUME-TOOLS same tool-replay logic on terminal repaint
320080
+ const allMessages = data.messages || [];
320081
+ messages = allMessages
320082
+ .filter(m => m.role === 'user' || m.role === 'assistant')
320083
+ .map(m => ({ role: m.role, content: m.content }));
319907
320084
  const conv3 = document.getElementById('conversation');
319908
320085
  if (conv3) {
319909
320086
  conv3.innerHTML = '';
319910
- for (const m of messages) addMessage(m.role, m.content);
320087
+ let curTools = null;
320088
+ for (const m of allMessages) {
320089
+ if (m.role === 'user') { addMessage('user', m.content); curTools = null; }
320090
+ else if (m.role === 'assistant') {
320091
+ const md = addMessage('assistant', m.content);
320092
+ curTools = document.createElement('div');
320093
+ curTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
320094
+ md.appendChild(curTools);
320095
+ } else if (m.role === 'tool_call') {
320096
+ if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
320097
+ renderToolCallEvent(curTools, { tool: m.tool, args: m.args });
320098
+ } else if (m.role === 'tool_result') {
320099
+ if (!curTools) { const md = addMessage('assistant', ''); curTools = document.createElement('div'); curTools.style.cssText = 'margin:4px 0;font-size:0.7rem'; md.appendChild(curTools); }
320100
+ renderToolResultEvent(curTools, { output: m.output, success: m.success });
320101
+ }
320102
+ }
319911
320103
  }
319912
320104
  stopPolling();
319913
320105
  }
@@ -320610,13 +320802,57 @@ function addUserMessage(session, content) {
320610
320802
  session.messages.push({ role: "user", content });
320611
320803
  session.lastActivity = Date.now();
320612
320804
  persistSession(session);
320613
- return [...session.messages];
320805
+ return session.messages.filter((m2) => m2.role === "system" || m2.role === "user" || m2.role === "assistant");
320614
320806
  }
320615
320807
  function addAssistantMessage(session, content) {
320616
320808
  session.messages.push({ role: "assistant", content });
320617
320809
  session.lastActivity = Date.now();
320618
320810
  persistSession(session);
320619
320811
  }
320812
+ function addToolCallMessage(session, tool, args) {
320813
+ let cappedArgs = args;
320814
+ try {
320815
+ const json = JSON.stringify(args);
320816
+ if (json && json.length > 4e3) {
320817
+ if (args && typeof args === "object" && !Array.isArray(args)) {
320818
+ const pruned = {};
320819
+ for (const [k, v] of Object.entries(args)) {
320820
+ if (typeof v === "string" && v.length > 1e3) {
320821
+ pruned[k] = v.slice(0, 800) + `… [+${v.length - 800} chars truncated for storage]`;
320822
+ } else {
320823
+ pruned[k] = v;
320824
+ }
320825
+ }
320826
+ cappedArgs = pruned;
320827
+ } else {
320828
+ cappedArgs = { _truncated: true, _bytes: json.length };
320829
+ }
320830
+ }
320831
+ } catch {
320832
+ }
320833
+ session.messages.push({
320834
+ role: "tool_call",
320835
+ content: "",
320836
+ tool,
320837
+ args: cappedArgs,
320838
+ ts: Date.now()
320839
+ });
320840
+ session.lastActivity = Date.now();
320841
+ persistSession(session);
320842
+ }
320843
+ function addToolResultMessage(session, tool, output, success) {
320844
+ const capped = output && output.length > 2048 ? output.slice(0, 2e3) + `… [+${output.length - 2e3} chars truncated for storage]` : output;
320845
+ session.messages.push({
320846
+ role: "tool_result",
320847
+ content: "",
320848
+ tool,
320849
+ output: capped,
320850
+ success,
320851
+ ts: Date.now()
320852
+ });
320853
+ session.lastActivity = Date.now();
320854
+ persistSession(session);
320855
+ }
320620
320856
  function listSessions2() {
320621
320857
  return Array.from(sessions.values()).map((s2) => ({
320622
320858
  id: s2.id,
@@ -323759,7 +323995,9 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
323759
323995
  try {
323760
323996
  const ans = await directChatBackend({
323761
323997
  model,
323762
- messages: session.messages.map((m2) => ({ role: m2.role, content: m2.content })),
323998
+ // Filter out tool_call/tool_result messages the upstream
323999
+ // model only accepts standard system/user/assistant turns.
324000
+ messages: session.messages.filter((m2) => m2.role === "system" || m2.role === "user" || m2.role === "assistant").map((m2) => ({ role: m2.role, content: m2.content })),
323763
324001
  stream: streamMode,
323764
324002
  res,
323765
324003
  sessionId: session.id,
@@ -323789,7 +324027,7 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
323789
324027
  }
323790
324028
  return;
323791
324029
  }
323792
- const historyLines = session.messages.filter((m2) => m2.role !== "system").slice(-10).map((m2) => m2.role === "user" ? `User: ${m2.content}` : `Assistant: ${m2.content}`).join("\n");
324030
+ const historyLines = session.messages.filter((m2) => m2.role === "user" || m2.role === "assistant").slice(-10).map((m2) => m2.role === "user" ? `User: ${m2.content}` : `Assistant: ${m2.content}`).join("\n");
323793
324031
  const memCtx = await retrieveMemoryContext(
323794
324032
  chatBody.message,
323795
324033
  session.id,
@@ -323885,6 +324123,10 @@ ${historyLines}
323885
324123
  tool: evt.tool,
323886
324124
  args: evt.args
323887
324125
  }) + "\n\n");
324126
+ try {
324127
+ addToolCallMessage(session, evt.tool, evt.args);
324128
+ } catch {
324129
+ }
323888
324130
  try {
323889
324131
  const evtType = evt.tool === "sub_agent" || evt.tool === "full_sub_agent" ? "run.started" : "tool.called";
323890
324132
  publishEvent(evtType, {
@@ -323895,6 +324137,17 @@ ${historyLines}
323895
324137
  }, { subject: session.id });
323896
324138
  } catch {
323897
324139
  }
324140
+ } else if (evt.type === "tool_result") {
324141
+ res.write("data: " + JSON.stringify({
324142
+ type: "tool_result",
324143
+ tool: evt.tool,
324144
+ output: evt.output ?? evt.content ?? "",
324145
+ success: evt.success
324146
+ }) + "\n\n");
324147
+ try {
324148
+ addToolResultMessage(session, evt.tool, evt.output ?? evt.content ?? "", evt.success);
324149
+ } catch {
324150
+ }
323898
324151
  } else {
323899
324152
  finalLines.push(line);
323900
324153
  }
@@ -324906,7 +325159,7 @@ function createTaskCompleteTool(modelTier) {
324906
325159
  const summaryDesc = modelTier === "small" || modelTier === "medium" ? "Your complete response to the user. For questions/chat: put your FULL answer here (this is what the user will see). For coding tasks: brief summary of what was accomplished." : "Brief summary of what was accomplished";
324907
325160
  return {
324908
325161
  name: "task_complete",
324909
- description: "Signal that the task is complete.",
325162
+ description: "Signal that the task is complete. GUARDED: cannot fire while the active todo list (todo_write) has pending, in_progress, or blocked items. If you're truly done, first call todo_write to mark every remaining item completed. If you're not done, continue working down the list and call this only after the last item flips to completed.",
324910
325163
  parameters: {
324911
325164
  type: "object",
324912
325165
  properties: {
@@ -324915,6 +325168,35 @@ function createTaskCompleteTool(modelTier) {
324915
325168
  required: ["summary"]
324916
325169
  },
324917
325170
  async execute(args) {
325171
+ try {
325172
+ const sessionId = getTodoSessionId();
325173
+ const todos = readTodos(sessionId);
325174
+ if (todos.length > 0) {
325175
+ const incomplete = todos.filter(
325176
+ (t2) => t2.status === "pending" || t2.status === "in_progress" || t2.status === "blocked"
325177
+ );
325178
+ if (incomplete.length > 0) {
325179
+ const incompleteList = incomplete.slice(0, 10).map((t2) => ` - [${t2.status}] ${t2.content}${t2.blocker ? ` (blocked: ${t2.blocker})` : ""}`).join("\n");
325180
+ const more = incomplete.length > 10 ? `
325181
+ ... +${incomplete.length - 10} more` : "";
325182
+ return {
325183
+ success: false,
325184
+ output: "",
325185
+ error: `task_complete BLOCKED — ${incomplete.length} todo item(s) still incomplete. You must either:
325186
+ 1. Continue working on the remaining items, OR
325187
+ 2. If they're actually done, call todo_write with status='completed' for each one, THEN call task_complete again.
325188
+
325189
+ Incomplete items:
325190
+ ${incompleteList}${more}`
325191
+ };
325192
+ }
325193
+ try {
325194
+ writeTodos(sessionId, []);
325195
+ } catch {
325196
+ }
325197
+ }
325198
+ } catch {
325199
+ }
324918
325200
  return { success: true, output: args["summary"] || "Task completed." };
324919
325201
  }
324920
325202
  };
@@ -326215,6 +326497,13 @@ ${entry.fullContent}`
326215
326497
  }
326216
326498
  break;
326217
326499
  case "tool_result": {
326500
+ if (_apiCallbacks?.onToolResult) {
326501
+ _apiCallbacks.onToolResult(
326502
+ event.toolName ?? "unknown",
326503
+ String(event.content ?? ""),
326504
+ event.success ?? false
326505
+ );
326506
+ }
326218
326507
  if (lastToolCall) {
326219
326508
  editHistory.logToolCall(lastToolCall.name, lastToolCall.args, event.success ?? false);
326220
326509
  lastToolCall = null;
@@ -330716,6 +331005,10 @@ async function runJson(task, config, repoPath) {
330716
331005
  onToolCall: (tool, args) => {
330717
331006
  toolCallLog.push({ tool, args });
330718
331007
  origWrite(JSON.stringify({ type: "tool_call", tool, args }) + "\n");
331008
+ },
331009
+ onToolResult: (tool, output, success) => {
331010
+ const capped = output && output.length > 4e3 ? output.slice(0, 3900) + `… [+${output.length - 3900} chars]` : output;
331011
+ origWrite(JSON.stringify({ type: "tool_result", tool, output: capped, success }) + "\n");
330719
331012
  }
330720
331013
  });
330721
331014
  result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.222",
3
+ "version": "0.187.224",
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",