scanruler-mcp 0.5.0 → 0.6.0

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/src/tools.js CHANGED
@@ -90,14 +90,14 @@ export const agentJson = (value, exact = false) =>
90
90
  * false answers with the job at once. */
91
91
  export const waitIn = {
92
92
  type: 'boolean',
93
- description: 'false: answer at once with { queued: true, job } while the call runs on; scanruler_wait { for: "job", job } gives its answer. true (the default): answer once it is done — or, still running at the server’s answer limit (scanruler_status: server.answerWithin, seconds), with { queued: true, job, after } all the same.',
93
+ description: 'false: answer { queued: true, job } at once and run on; scanruler_wait { for: "job", job } gives the answer. true, the default: answer when done — or queued all the same past the server’s answer limit.',
94
94
  }
95
95
 
96
96
  /** The `timings` a tool that changes the session takes: the server keeps
97
97
  * what the page's answer carries of the build's steps (mcp.js). */
98
98
  export const timingsIn = {
99
99
  type: 'boolean',
100
- description: 'true: the answer adds ms (the call, start to answer) and, where the change built, steps.played — each step the build played with its ms (steps.restored counts those taken up from a kept state, not played).',
100
+ description: 'true: the answer adds ms and, where the change built, steps.played — each step with its ms.',
101
101
  }
102
102
 
103
103
  export function toolFor(command, { outDir, cwd }) {
@@ -297,11 +297,28 @@ const KINDS = {
297
297
  drawing: ['.dxf', '.svg', '.pdf'],
298
298
  text: ['.md', '.txt', '.csv', '.json', '.jsonl'],
299
299
  }
300
+ /** The extensions of the files scanruler_read reads. */
301
+ export const TEXT_FILES = KINDS.text
300
302
  export const fileKind = (name) => {
301
303
  const ext = extname(name).toLowerCase()
302
304
  return Object.keys(KINDS).find((kind) => KINDS[kind].includes(ext)) ?? 'other'
303
305
  }
304
306
 
307
+ /** How many bytes the UTF-8 character that starts with `byte` takes. */
308
+ const utf8Length = (byte) => (byte < 0x80 ? 1 : byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : 2)
309
+
310
+ /** The start of UTF-8 `bytes` up to `most` of them, ending on a character's
311
+ * end — the first character at least, when they hold it whole. */
312
+ export function wholeUtf8(bytes, most = bytes.length) {
313
+ const cut = bytes.subarray(0, Math.max(most, utf8Length(bytes[0])))
314
+ for (let back = 1; back <= Math.min(4, cut.length); back++) {
315
+ const byte = cut[cut.length - back]
316
+ if ((byte & 0xc0) === 0x80) continue
317
+ return utf8Length(byte) > back ? cut.subarray(0, cut.length - back) : cut
318
+ }
319
+ return cut
320
+ }
321
+
305
322
  /** A picture a command handed back, as MCP content: the image, and the rest
306
323
  * of the result as text — where the picture was written, if the call gave
307
324
  * a path, in place of its bytes; written and not shown with `inline:
@@ -342,16 +359,18 @@ export async function writeNew(folder, name, data) {
342
359
  }
343
360
 
344
361
  /** A picture a batched call handed back, which a batch cannot show:
345
- * written where the call asked, or into `folder` — the latest a scan or
346
- * a project came from, which a sandboxed agent can open — else, or when
347
- * that folder takes no file, into `outDir`; as saveFileResult writes any
348
- * file, numbered past one already there, so a batch of renders, or a
349
- * render from an earlier batch, is not written over. The result says
350
- * where, in place of the bytes. */
362
+ * written where the call asked, or into `folder` — the server's work
363
+ * folder in the one the scan came from, which a sandboxed agent can open,
364
+ * made when it is not there — else, or when that folder takes no file,
365
+ * into `outDir`; as saveFileResult writes any file, numbered past one
366
+ * already there, so a batch of renders, or a render from an earlier
367
+ * batch, is not written over. The result says where, in place of the
368
+ * bytes — written into `folder`, that folder too (file.folder). */
351
369
  export async function batchedPicture(result, args, { cwd, outDir, folder }) {
352
370
  if (args?.path || !folder) return saveFileResult(result, args, { cwd, outDir })
353
371
  try {
354
- return await saveFileResult(result, args, { cwd, outDir: folder })
372
+ const saved = await saveFileResult(result, args, { cwd, outDir: folder })
373
+ return saved?.file?.path ? { ...saved, file: { ...saved.file, folder } } : saved
355
374
  } catch {
356
375
  return saveFileResult(result, args, { cwd, outDir })
357
376
  }
package/src/trace.js CHANGED
@@ -3,7 +3,8 @@
3
3
  // own, for measuring a run afterwards — how long each call took, how much
4
4
  // went each way, what was refused, and whether the tab was hidden. Off
5
5
  // unless asked for (--trace <dir>, SCANRULER_MCP_TRACE); local only; an
6
- // argument's contents are written only with --trace-args, its size always.
6
+ // argument's contents, and the head of each answer, are written only with
7
+ // --trace-args, their sizes always.
7
8
  // scripts/trace-summary.mjs reads the files.
8
9
 
9
10
  import { appendFileSync, mkdirSync } from 'node:fs'
@@ -29,6 +30,16 @@ export function replyMeasure(reply) {
29
30
  return { out, error, ...(images ? { images } : {}) }
30
31
  }
31
32
 
33
+ /** How much of an answer the trace keeps, characters. */
34
+ const HEAD_CHARS = 500
35
+
36
+ /** The start of a reply's text — a refusal's message, most often whole — for
37
+ * reading a run afterwards. */
38
+ export function replyHead(reply) {
39
+ const text = reply?.content?.find((c) => c.type === 'text')?.text
40
+ return typeof text === 'string' && text.length > 0 ? text.slice(0, HEAD_CHARS) : undefined
41
+ }
42
+
32
43
  /** A trace into a new file in `dir`, or null when `dir` is empty. Each
33
44
  * line is a JSON object with `ev`: "start" (the server), "list" (the tool
34
45
  * list handed out), "call" (a tool call answered), "entry" (one call of a