prior-cli 1.3.4 → 1.3.6

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.
package/bin/prior.js CHANGED
@@ -660,7 +660,13 @@ async function startChat(opts = {}) {
660
660
  }
661
661
 
662
662
  if (key.name === 'return' || key.name === 'enter' || (key.ctrl && key.name === 'c')) {
663
- clearSuggestions();
663
+ // Clear suggestions NOW — before readline moves the cursor to the next line
664
+ if (_suggCount > 0) {
665
+ process.stdout.write('\x1b[s');
666
+ for (let i = 0; i < _suggCount; i++) process.stdout.write('\x1b[B\r\x1b[2K');
667
+ process.stdout.write('\x1b[u');
668
+ _suggCount = 0;
669
+ }
664
670
  return;
665
671
  }
666
672
 
package/lib/agent.js CHANGED
@@ -77,6 +77,22 @@ function parseToolCalls(text) {
77
77
  } catch { /* skip */ }
78
78
  }
79
79
 
80
+ // Primary variant: <tool name="X">{"args"}</tool> (name as attribute — used by some models)
81
+ const reAttr = /<tool\s+name="([^"]+)"[^>]*>([\s\S]*?)<\/tool>/g;
82
+ while ((m = reAttr.exec(text)) !== null) {
83
+ const alreadyCaptured = calls.some(c => m.index >= c.offset && m.index < c.offset + c.raw.length);
84
+ if (alreadyCaptured) continue;
85
+ const toolName = m[1];
86
+ try {
87
+ const body = m[2].trim();
88
+ const parsed = body ? JSON.parse(fixJsonLiterals(body)) : {};
89
+ const { args, ...rest } = parsed;
90
+ calls.push({ raw: m[0], offset: m.index, name: toolName, args: args || (Object.keys(rest).length > 0 ? rest : {}) });
91
+ } catch {
92
+ calls.push({ raw: m[0], offset: m.index, name: toolName, args: {} });
93
+ }
94
+ }
95
+
80
96
  // Fallback 1: <tool_name>{...}</tool_name>
81
97
  for (const name of TOOL_NAMES) {
82
98
  const fbRe = new RegExp(`<${name}>([\\s\\S]*?)<\\/${name}>`, 'g');
@@ -109,6 +125,21 @@ function parseToolCalls(text) {
109
125
  }
110
126
  }
111
127
 
128
+ // Fallback 3: <tool_name key="val" ...>...</tool_name> (HTML attribute style — used by dolphin)
129
+ for (const name of TOOL_NAMES) {
130
+ const fbRe = new RegExp(`<${name}((?:\\s+[a-zA-Z_]\\w*="[^"]*")+)\\s*(?:>[\\s\\S]*?<\\/${name}>|/>)`, 'g');
131
+ let fm;
132
+ while ((fm = fbRe.exec(text)) !== null) {
133
+ const alreadyCaptured = calls.some(c => fm.index >= c.offset && fm.index < c.offset + c.raw.length);
134
+ if (alreadyCaptured) continue;
135
+ const args = {};
136
+ const attrRe = /([a-zA-Z_]\w*)="([^"]*)"/g;
137
+ let am;
138
+ while ((am = attrRe.exec(fm[1])) !== null) args[am[1]] = am[2];
139
+ calls.push({ raw: fm[0], offset: fm.index, name, args });
140
+ }
141
+ }
142
+
112
143
  return calls;
113
144
  }
114
145
 
@@ -137,13 +168,31 @@ function stripThink(text) {
137
168
  return text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
138
169
  }
139
170
 
171
+ // Some models (dolphin) hallucinate fake conversation turns after their response.
172
+ // Truncate at common boundary markers to prevent those being parsed as real tool calls.
173
+ function truncateAtFakeTurn(text) {
174
+ const MARKERS = [
175
+ /\n[-─]{3,}\s*\n+(?:user|human)\b/i,
176
+ /\n+(?:user|human)\s*:\s/i,
177
+ /\n+(?:assistant|prior)\s*:\s/i,
178
+ ];
179
+ let cut = text.length;
180
+ for (const re of MARKERS) {
181
+ const m = re.exec(text);
182
+ if (m && m.index < cut) cut = m.index;
183
+ }
184
+ return text.slice(0, cut);
185
+ }
186
+
140
187
  // Strip any residual tool-call tags the model echoes in its text output
141
188
  function stripToolTags(text) {
142
189
  // <tool>...</tool>
143
190
  let out = text.replace(/<tool>[\s\S]*?<\/tool>/gi, '');
144
- // <tool_name {...}>...</tool_name> AND <tool_name>...</tool_name>
145
191
  const namesPattern = TOOL_NAMES.join('|');
192
+ // <tool_name ...>...</tool_name> (with or without attributes/JSON)
146
193
  out = out.replace(new RegExp(`<(?:${namesPattern})[^>]*>[\\s\\S]*?<\\/(?:${namesPattern})>`, 'gi'), '');
194
+ // Self-closing or unclosed: <tool_name attr="val" /> or <tool_name attr="val">
195
+ out = out.replace(new RegExp(`<(?:${namesPattern})(?:\\s[^>]*)?\\s*/?>`, 'gi'), '');
147
196
  return out.trim();
148
197
  }
149
198
 
@@ -187,8 +236,8 @@ async function runAgent({ messages, model, uncensored, cwd, projectContext, send
187
236
  totalCompletionTokens += result.completionTokens || 0;
188
237
 
189
238
  const raw = result.content;
190
- const cleaned = stripThink(raw)
191
- .replace(/<tool_result[\s\S]*?<\/tool_result>/gi, '')
239
+ const cleaned = truncateAtFakeTurn(stripThink(raw)
240
+ .replace(/<tool_result[\s\S]*?<\/tool_result>/gi, ''))
192
241
  .trim();
193
242
 
194
243
  const calls = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prior-cli",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "Prior Network AI — command-line interface",
5
5
  "bin": {
6
6
  "prior": "bin/prior.js"