@noobdemon/noob-cli 1.5.3 → 1.5.4

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/package.json +1 -1
  2. package/src/agent.js +36 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noobdemon/noob-cli",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/agent.js CHANGED
@@ -175,24 +175,48 @@ function buildPrompt(history) {
175
175
  }
176
176
 
177
177
  // Extract a single tool call from an assistant message, if present.
178
+ // NOTE: we do NOT match up to a closing ``` fence — write_file content routinely
179
+ // contains its own ```code``` fences (e.g. a README), and the first inner fence
180
+ // would close the block early and break the JSON. Instead, find the ```tool (or
181
+ // ```json) opener and brace-match the first balanced JSON object after it.
178
182
  export function parseToolCall(text) {
179
- // Preferred: ```tool { ... } ```
180
- let m = text.match(/```tool\s*\n([\s\S]*?)```/);
181
- // Fallback: a ```json block that contains a "name" field.
182
- if (!m) {
183
- const j = text.match(/```json\s*\n([\s\S]*?)```/);
184
- if (j && /"name"\s*:/.test(j[1])) m = j;
185
- }
186
- if (!m) return null;
187
- try {
188
- const obj = JSON.parse(m[1].trim());
183
+ for (const fence of ["tool", "json"]) {
184
+ const open = text.match(new RegExp("```" + fence + "[ \\t]*\\n"));
185
+ if (!open) continue;
186
+ const obj = extractJsonObject(text, open.index + open[0].length);
189
187
  if (obj && typeof obj.name === "string") return { name: obj.name, input: obj.input || {} };
190
- } catch {
191
- /* malformed — treat as prose */
192
188
  }
193
189
  return null;
194
190
  }
195
191
 
192
+ // Parse the first balanced {…} at/after `from`, tracking string literals and
193
+ // escapes so braces or backticks INSIDE string values don't throw off the depth
194
+ // count. Returns the parsed object, or null if malformed/truncated (unbalanced).
195
+ function extractJsonObject(s, from) {
196
+ const start = s.indexOf("{", from);
197
+ if (start === -1) return null;
198
+ let depth = 0, inStr = false, esc = false;
199
+ for (let i = start; i < s.length; i++) {
200
+ const ch = s[i];
201
+ if (inStr) {
202
+ if (esc) esc = false;
203
+ else if (ch === "\\") esc = true;
204
+ else if (ch === '"') inStr = false;
205
+ continue;
206
+ }
207
+ if (ch === '"') inStr = true;
208
+ else if (ch === "{") depth++;
209
+ else if (ch === "}" && --depth === 0) {
210
+ try {
211
+ return JSON.parse(s.slice(start, i + 1));
212
+ } catch {
213
+ return null; // malformed JSON — treat as prose
214
+ }
215
+ }
216
+ }
217
+ return null; // unbalanced (e.g. stream cut mid-block) — auto-continue finishes it
218
+ }
219
+
196
220
  /**
197
221
  * Run one agent turn (which may span several tool steps).
198
222
  *