omnexx 0.11.1 → 0.11.3

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.
@@ -34,7 +34,7 @@ function text(c, key) {
34
34
  return v;
35
35
  }
36
36
  async function serveChat(io, runCli, opts) {
37
- const { Session } = await import("./session-A75AZD4D.js");
37
+ const { Session } = await import("./session-OORM43WG.js");
38
38
  const session = new Session(io, runCli);
39
39
  const write = (msg) => {
40
40
  println(io.stdout, JSON.stringify(msg));
@@ -299,4 +299,4 @@ async function serveChat(io, runCli, opts) {
299
299
  export {
300
300
  serveChat
301
301
  };
302
- //# sourceMappingURL=chat-server-KRKEX6ID.js.map
302
+ //# sourceMappingURL=chat-server-HFGPZ7RU.js.map
@@ -26,7 +26,7 @@ import {
26
26
  toolSpec,
27
27
  transcriptFor,
28
28
  workerTools
29
- } from "./chunk-CP53ICXW.js";
29
+ } from "./chunk-IGUI6IJP.js";
30
30
  import {
31
31
  RunStore,
32
32
  git,
@@ -36,7 +36,7 @@ import {
36
36
  } from "./chunk-LRBHQHO6.js";
37
37
  import {
38
38
  PathJail
39
- } from "./chunk-VDO3EKC4.js";
39
+ } from "./chunk-4DX4BPIF.js";
40
40
  import {
41
41
  QuotaLedger
42
42
  } from "./chunk-DGKPM32R.js";
@@ -793,4 +793,4 @@ export {
793
793
  resolveModelLenient,
794
794
  CodeChat
795
795
  };
796
- //# sourceMappingURL=chunk-32JVRGO6.js.map
796
+ //# sourceMappingURL=chunk-3Y7NVLVI.js.map
@@ -88,7 +88,8 @@ var PathJail = class {
88
88
 
89
89
  // src/tools/search.ts
90
90
  import { readdir, readFile, stat } from "fs/promises";
91
- import { basename as basename2, dirname as dirname2, join, relative as relative2 } from "path";
91
+ import { homedir } from "os";
92
+ import { basename as basename2, dirname as dirname2, join, relative as relative2, resolve as resolve2 } from "path";
92
93
  import { execa } from "execa";
93
94
  import { z } from "zod";
94
95
 
@@ -148,7 +149,33 @@ var schema = z.strictObject({
148
149
  glob: z.string().optional().describe('Only files matching this glob, e.g. "src/**/*.ts"'),
149
150
  path: z.string().optional().describe("Directory to search, relative to the repo root")
150
151
  });
151
- async function viaRg(pattern, dir, glob, target = ".") {
152
+ var NOISE_DIRS = [...SKIP_DIRS, ".cache", ".turbo", ".parcel-cache", "vendor"];
153
+ var HOME_NOISE = [
154
+ "Library",
155
+ ".Trash",
156
+ "Applications",
157
+ "Pictures",
158
+ "Movies",
159
+ "Music",
160
+ ".npm",
161
+ ".npm-cache",
162
+ ".cargo",
163
+ ".rustup",
164
+ ".gradle",
165
+ ".m2",
166
+ ".config",
167
+ ".local",
168
+ ".vscode",
169
+ ".cursor",
170
+ ".docker",
171
+ ".ollama"
172
+ ];
173
+ var PER_FILE = 20;
174
+ function excludes(atHome) {
175
+ const dirs = atHome ? [...NOISE_DIRS, ...HOME_NOISE] : NOISE_DIRS;
176
+ return dirs.flatMap((d) => ["--glob", `!**/${d}/**`]);
177
+ }
178
+ async function viaRg(pattern, dir, glob, target = ".", atHome = false) {
152
179
  const args = [
153
180
  "-n",
154
181
  "--with-filename",
@@ -164,12 +191,18 @@ async function viaRg(pattern, dir, glob, target = ".") {
164
191
  "--glob",
165
192
  "!*.pem",
166
193
  "--glob",
167
- "!*.key"
194
+ "!*.key",
195
+ "--max-count",
196
+ String(PER_FILE),
197
+ "--max-filesize",
198
+ "2M",
199
+ ...excludes(atHome)
168
200
  ];
169
201
  if (glob) args.push("--glob", glob);
170
- args.push("-e", pattern, target);
202
+ args.push("--no-messages", "-e", pattern, target);
171
203
  const r = await execa("rg", args, { cwd: dir, reject: false, stdin: "ignore", timeout: 6e4 });
172
- if (r.failed && r.exitCode !== 1) {
204
+ if (r.exitCode === 2 && r.stdout.trim()) return r.stdout.split("\n").filter(Boolean);
205
+ if (r.failed && r.exitCode !== 1 && !(r.exitCode === 2 && !r.stderr.trim())) {
173
206
  if (r.code === "ENOENT") return void 0;
174
207
  const first = r.stderr?.split("\n")[0];
175
208
  if (first) throw new Error(first);
@@ -189,7 +222,8 @@ async function viaJs(pattern, dir, glob, target = ".") {
189
222
  return out;
190
223
  }
191
224
  const walk = async (d) => {
192
- for (const entry of await readdir(d, { withFileTypes: true })) {
225
+ const entries = await readdir(d, { withFileTypes: true }).catch(() => []);
226
+ for (const entry of entries) {
193
227
  if (out.length >= MAX_HITS * 5) return;
194
228
  const abs = join(d, entry.name);
195
229
  const rel = relative2(dir, abs);
@@ -198,8 +232,14 @@ async function viaJs(pattern, dir, glob, target = ".") {
198
232
  continue;
199
233
  }
200
234
  if (!entry.isFile() || isSecretPath(rel) || globRe && !globRe.test(rel)) continue;
201
- if ((await stat(abs)).size > 2e6) continue;
202
- const lines = (await readFile(abs, "utf8")).split("\n");
235
+ const size = await stat(abs).then(
236
+ (st) => st.size,
237
+ () => Infinity
238
+ );
239
+ if (size > 2e6) continue;
240
+ const text = await readFile(abs, "utf8").catch(() => void 0);
241
+ if (text === void 0) continue;
242
+ const lines = text.split("\n");
203
243
  lines.forEach((l, i) => {
204
244
  if (re.test(l)) out.push(`${rel}:${i + 1}:${l.slice(0, 300)}`);
205
245
  });
@@ -210,7 +250,7 @@ async function viaJs(pattern, dir, glob, target = ".") {
210
250
  }
211
251
  var searchTool = {
212
252
  name: "search",
213
- description: `Search file contents with a regex (ripgrep). Returns up to ${MAX_HITS} matching lines with 2 lines of context.`,
253
+ description: `Search file contents with a regex (ripgrep). Returns up to ${MAX_HITS} matching lines with 2 lines of context (at most ${PER_FILE} per file). Scope it: pass path (a folder) and glob (e.g. "**/*.ts") whenever you know where to look; dependency, build and cache folders are skipped.`,
214
254
  schema,
215
255
  readOnly: true,
216
256
  async run(input, ctx) {
@@ -221,7 +261,7 @@ var searchTool = {
221
261
  return fail(err instanceof OmnexxError ? err.message : String(err));
222
262
  }
223
263
  if (!input.pattern.trim()) {
224
- const { listTree: listTree2 } = await import("./list-S2S26L5B.js");
264
+ const { listTree: listTree2 } = await import("./list-4L6TDLAZ.js");
225
265
  return ok(await listTree2(dir, ctx.jail.resolve(".", "read"), 3));
226
266
  }
227
267
  try {
@@ -235,7 +275,8 @@ var searchTool = {
235
275
  target = basename2(dir);
236
276
  dir = dirname2(dir);
237
277
  }
238
- const lines = await viaRg(input.pattern, dir, input.glob, target) ?? await viaJs(input.pattern, dir, input.glob, target);
278
+ const atHome = resolve2(ctx.jail.root) === resolve2(homedir());
279
+ const lines = await viaRg(input.pattern, dir, input.glob, target, atHome) ?? await viaJs(input.pattern, dir, input.glob, target);
239
280
  const hits = lines.filter((l) => /^[^:]+:\d+:/.test(l));
240
281
  if (!hits.length) return ok("no matches");
241
282
  let shown = 0;
@@ -245,14 +286,40 @@ var searchTool = {
245
286
  if (shown > MAX_HITS) break;
246
287
  kept.push(l.replace(/^\.\//, ""));
247
288
  }
248
- const note = hits.length > MAX_HITS ? `
249
- (${hits.length - MAX_HITS} more matches not shown; narrow the pattern or glob)` : "";
289
+ const capped = cappedFiles(hits);
290
+ const perFile = capped.length ? `
291
+ (${capped.slice(0, 5).join(", ")} ${capped.length === 1 ? "has" : "have"} more than ${String(PER_FILE)} matches; search one with path set to it to see them all)` : "";
292
+ const note = perFile + (hits.length > MAX_HITS ? `
293
+ (${hits.length - MAX_HITS} more matches not shown. Most are in: ${whereMatches(hits)}. Narrow with path or glob.)` : "");
250
294
  return ok(ctx.redactor.text(kept.join("\n")) + note);
251
295
  } catch (err) {
252
296
  return fail(`search failed: ${err.message}`);
253
297
  }
254
298
  }
255
299
  };
300
+ function folderOf(parts) {
301
+ if (parts.length > 2) return parts.slice(0, 2).join("/");
302
+ if (parts.length === 2) return parts[0] ?? ".";
303
+ return ".";
304
+ }
305
+ function whereMatches(hits, top = 6) {
306
+ const counts = /* @__PURE__ */ new Map();
307
+ for (const h of hits) {
308
+ const file = h.replace(/^\.\//, "").split(":")[0] ?? "";
309
+ const parts = file.split("/");
310
+ const key = folderOf(parts);
311
+ counts.set(key, (counts.get(key) ?? 0) + 1);
312
+ }
313
+ return [...counts].sort((a, b) => b[1] - a[1]).slice(0, top).map(([k, n]) => `${k} (${String(n)})`).join(", ");
314
+ }
315
+ function cappedFiles(hits) {
316
+ const counts = /* @__PURE__ */ new Map();
317
+ for (const h of hits) {
318
+ const file = h.replace(/^\.\//, "").split(":")[0] ?? "";
319
+ counts.set(file, (counts.get(file) ?? 0) + 1);
320
+ }
321
+ return [...counts].filter(([, n]) => n >= PER_FILE).map(([f]) => f);
322
+ }
256
323
 
257
324
  // src/tools/list.ts
258
325
  var MAX_ENTRIES = 400;
@@ -318,4 +385,4 @@ export {
318
385
  listTree,
319
386
  listTool
320
387
  };
321
- //# sourceMappingURL=chunk-VDO3EKC4.js.map
388
+ //# sourceMappingURL=chunk-4DX4BPIF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tools/list.ts","../src/security/paths.ts","../src/tools/search.ts","../src/security/glob.ts","../src/tools/types.ts"],"sourcesContent":["import { readdir } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport { z } from 'zod';\nimport { OmnexxError } from '../errors.js';\nimport { isSecretPath } from '../security/paths.js';\nimport { SKIP_DIRS } from './search.js';\nimport { fail, ok, type Tool } from './types.js';\n\nexport const MAX_ENTRIES = 400;\n\nconst schema = z.strictObject({\n path: z\n .string()\n .optional()\n .describe('Directory to list, relative to the repo root (default: the root)'),\n depth: z.number().int().min(1).max(8).optional().describe('How many levels down (default 3)'),\n});\n\n/** Files and folders under `root`, folders ending in \"/\", dependency and build folders left out. */\nexport async function listTree(root: string, base: string, depth: number): Promise<string> {\n const out: string[] = [];\n let more = 0;\n const walk = async (dir: string, level: number): Promise<void> => {\n const entries = (await readdir(dir, { withFileTypes: true })).sort((a, b) =>\n a.name < b.name ? -1 : 1,\n );\n for (const e of entries) {\n if (e.name === '.git' || (e.isDirectory() && SKIP_DIRS.has(e.name))) continue;\n const rel = relative(base, join(dir, e.name));\n if (isSecretPath(rel)) continue;\n if (out.length >= MAX_ENTRIES) {\n more++;\n continue;\n }\n out.push(e.isDirectory() ? `${rel}/` : rel);\n if (e.isDirectory() && level < depth) await walk(join(dir, e.name), level + 1);\n }\n };\n await walk(root, 1);\n if (!out.length) return '(empty)';\n return more ? `${out.join('\\n')}\\n… ${String(more)} more; list a subfolder` : out.join('\\n');\n}\n\nexport const listTool: Tool<typeof schema> = {\n name: 'list',\n description:\n 'List files and folders as a tree (folders end in \"/\"; dependency and build folders are left out). Use it to find your way around before reading.',\n schema,\n readOnly: true,\n async run(input, ctx) {\n let dir: string;\n try {\n dir = ctx.jail.resolve(input.path ?? '.', 'read');\n } catch (err) {\n return fail(err instanceof OmnexxError ? err.message : String(err));\n }\n try {\n return ok(await listTree(dir, ctx.jail.resolve('.', 'read'), input.depth ?? 3));\n } catch (err) {\n return fail(`cannot list ${input.path ?? '.'}: ${(err as Error).message}`);\n }\n },\n};\n","import { realpathSync } from 'node:fs';\nimport { basename, dirname, isAbsolute, relative, resolve, sep } from 'node:path';\nimport { PolicyError } from '../errors.js';\n\n/** Paths the agent may never read or write, even inside the worktree. */\nconst DENY_SEGMENTS = ['.ssh', '.aws', '.gnupg', '.git', '.docker', '.kube'];\nconst DENY_BASENAME = [\n /^\\.env(\\..*)?$/i,\n /\\.pem$/i,\n /\\.key$/i,\n /\\.p12$/i,\n /\\.pfx$/i,\n /^id_[a-z0-9]+(\\.pub)?$/i,\n /^\\.npmrc$/i,\n /^\\.netrc$/i,\n /^\\.pypirc$/i,\n /^credentials(\\.json)?$/i,\n];\n\n/** True when a path (absolute or relative) points at something that looks like a credential. */\nexport function isSecretPath(path: string): boolean {\n const parts = path.split(/[\\\\/]+/).filter(Boolean);\n if (parts.some((p) => DENY_SEGMENTS.includes(p.toLowerCase()))) return true;\n const base = basename(path);\n return DENY_BASENAME.some((re) => re.test(base));\n}\n\n/** realpath of the deepest existing ancestor, with the missing tail re-appended. */\nfunction canonical(path: string): string {\n let current = path;\n const tail: string[] = [];\n for (;;) {\n try {\n const real = realpathSync.native(current);\n return tail.length ? resolve(real, ...tail.reverse()) : real;\n } catch {\n const parent = dirname(current);\n if (parent === current) return path;\n tail.push(basename(current));\n current = parent;\n }\n }\n}\n\nconst caseInsensitive = process.platform === 'darwin' || process.platform === 'win32';\n\nexport function isInside(root: string, target: string): boolean {\n const a = caseInsensitive ? root.toLowerCase() : root;\n const b = caseInsensitive ? target.toLowerCase() : target;\n if (a === b) return true;\n const rel = relative(a, b);\n return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);\n}\n\n/** A path outside the root: the one jail refusal a person may override in interactive chat. */\nexport class OutsideRootError extends PolicyError {}\n\n/**\n * The path jail. Every file tool goes through `resolve()`: the result is a real path inside the\n * worktree, never through a symlink that escapes it, and never a secret-looking file.\n */\nexport class PathJail {\n readonly root: string;\n\n constructor(root: string) {\n this.root = realpathSync.native(root);\n }\n\n resolve(userPath: string, mode: 'read' | 'write', opts: { allowOutside?: boolean } = {}): string {\n if (userPath.includes('\\0')) throw new PolicyError('path contains a NUL byte');\n const expanded = userPath.startsWith('~')\n ? `${this.root}${sep}__home__${userPath.slice(1)}`\n : userPath;\n const abs = resolve(this.root, expanded);\n const real = canonical(abs);\n if (!isInside(this.root, real)) {\n if (opts.allowOutside && !isSecretPath(real)) return real;\n throw new OutsideRootError(\n `${userPath} is outside the worktree`,\n 'file tools only work on paths inside the repository',\n );\n }\n const rel = relative(this.root, real);\n if (isSecretPath(rel)) {\n throw new PolicyError(\n `${mode} of ${userPath} is denied (looks like a credential or git internals)`,\n );\n }\n return real;\n }\n\n relative(abs: string): string {\n return relative(this.root, abs).split(sep).join('/');\n }\n}\n","import { readdir, readFile, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, dirname, join, relative, resolve } from 'node:path';\nimport { execa } from 'execa';\nimport { z } from 'zod';\nimport { OmnexxError } from '../errors.js';\nimport { globToRegExp } from '../security/glob.js';\nimport { isSecretPath } from '../security/paths.js';\nimport { fail, ok, type Tool } from './types.js';\n\nexport const MAX_HITS = 100;\nexport const SKIP_DIRS = new Set([\n '.git',\n 'node_modules',\n 'dist',\n 'build',\n 'coverage',\n '.next',\n 'target',\n '__pycache__',\n '.venv',\n]);\n\nconst schema = z.strictObject({\n pattern: z\n .string()\n .describe('Regular expression (ripgrep syntax); empty lists the files instead'),\n glob: z.string().optional().describe('Only files matching this glob, e.g. \"src/**/*.ts\"'),\n path: z.string().optional().describe('Directory to search, relative to the repo root'),\n});\n\n/**\n * Folders that are never what a search is looking for: dependencies, builds and caches anywhere,\n * plus, when the agent works in a home folder, the system and app folders in it.\n */\nconst NOISE_DIRS = [...SKIP_DIRS, '.cache', '.turbo', '.parcel-cache', 'vendor'];\nconst HOME_NOISE = [\n 'Library',\n '.Trash',\n 'Applications',\n 'Pictures',\n 'Movies',\n 'Music',\n '.npm',\n '.npm-cache',\n '.cargo',\n '.rustup',\n '.gradle',\n '.m2',\n '.config',\n '.local',\n '.vscode',\n '.cursor',\n '.docker',\n '.ollama',\n];\n/** Matches kept per file, so one huge file can't fill the result. */\nconst PER_FILE = 20;\n\nfunction excludes(atHome: boolean): string[] {\n const dirs = atHome ? [...NOISE_DIRS, ...HOME_NOISE] : NOISE_DIRS;\n return dirs.flatMap((d) => ['--glob', `!**/${d}/**`]);\n}\n\nasync function viaRg(\n pattern: string,\n dir: string,\n glob: string | undefined,\n target = '.',\n atHome = false,\n): Promise<string[] | undefined> {\n const args = [\n '-n',\n '--with-filename',\n '--no-heading',\n '--color=never',\n '-C',\n '2',\n '--max-columns=300',\n '--glob',\n '!.git',\n '--glob',\n '!.env*',\n '--glob',\n '!*.pem',\n '--glob',\n '!*.key',\n '--max-count',\n String(PER_FILE),\n '--max-filesize',\n '2M',\n ...excludes(atHome),\n ];\n if (glob) args.push('--glob', glob);\n // Files it can't read (permission denied, a socket) are skipped, not fatal.\n args.push('--no-messages', '-e', pattern, target);\n const r = await execa('rg', args, { cwd: dir, reject: false, stdin: 'ignore', timeout: 60_000 });\n // Exit 2 with output: some files were unreadable, the rest searched fine.\n if (r.exitCode === 2 && r.stdout.trim()) return r.stdout.split('\\n').filter(Boolean);\n if (r.failed && r.exitCode !== 1 && !(r.exitCode === 2 && !r.stderr.trim())) {\n if ((r as { code?: string }).code === 'ENOENT') return undefined;\n // execa leaves stderr undefined when the spawn itself fails (e.g. cwd is a file).\n const first = (r.stderr as string | undefined)?.split('\\n')[0];\n if (first) throw new Error(first);\n throw new Error(r.message);\n }\n return r.stdout.split('\\n').filter(Boolean);\n}\n\n/** Fallback when rg isn't installed: a bounded recursive scan. */\nasync function viaJs(\n pattern: string,\n dir: string,\n glob: string | undefined,\n target = '.',\n): Promise<string[]> {\n const re = new RegExp(pattern);\n const globRe = glob ? globToRegExp(glob) : undefined;\n const out: string[] = [];\n if (target !== '.') {\n const lines = (await readFile(join(dir, target), 'utf8')).split('\\n');\n lines.forEach((l, i) => {\n if (re.test(l)) out.push(`${target}:${i + 1}:${l.slice(0, 300)}`);\n });\n return out;\n }\n const walk = async (d: string): Promise<void> => {\n // Folders and files it can't read are skipped, like rg does.\n const entries = await readdir(d, { withFileTypes: true }).catch(() => []);\n for (const entry of entries) {\n if (out.length >= MAX_HITS * 5) return;\n const abs = join(d, entry.name);\n const rel = relative(dir, abs);\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name)) await walk(abs);\n continue;\n }\n if (!entry.isFile() || isSecretPath(rel) || (globRe && !globRe.test(rel))) continue;\n const size = await stat(abs).then(\n (st) => st.size,\n () => Infinity,\n );\n if (size > 2_000_000) continue;\n const text = await readFile(abs, 'utf8').catch(() => undefined);\n if (text === undefined) continue;\n const lines = text.split('\\n');\n lines.forEach((l, i) => {\n if (re.test(l)) out.push(`${rel}:${i + 1}:${l.slice(0, 300)}`);\n });\n }\n };\n await walk(dir);\n return out;\n}\n\nexport const searchTool: Tool<typeof schema> = {\n name: 'search',\n description: `Search file contents with a regex (ripgrep). Returns up to ${MAX_HITS} matching lines with 2 lines of context (at most ${PER_FILE} per file). Scope it: pass path (a folder) and glob (e.g. \"**/*.ts\") whenever you know where to look; dependency, build and cache folders are skipped.`,\n schema,\n readOnly: true,\n async run(input, ctx) {\n let dir: string;\n try {\n dir = ctx.jail.resolve(input.path ?? '.', 'read');\n } catch (err) {\n return fail(err instanceof OmnexxError ? err.message : String(err));\n }\n // Models send an empty pattern (or \".\") when they mean \"show me the files\".\n if (!input.pattern.trim()) {\n const { listTree } = await import('./list.js');\n return ok(await listTree(dir, ctx.jail.resolve('.', 'read'), 3));\n }\n try {\n new RegExp(input.pattern);\n } catch {\n return fail(`invalid regex: ${input.pattern}`);\n }\n try {\n // A file as `path` (models do this often): search just that file, from its directory.\n let target = '.';\n if ((await stat(dir).catch(() => undefined))?.isFile()) {\n target = basename(dir);\n dir = dirname(dir);\n }\n const atHome = resolve(ctx.jail.root) === resolve(homedir());\n const lines =\n (await viaRg(input.pattern, dir, input.glob, target, atHome)) ??\n (await viaJs(input.pattern, dir, input.glob, target));\n const hits = lines.filter((l) => /^[^:]+:\\d+:/.test(l));\n if (!hits.length) return ok('no matches');\n let shown = 0;\n const kept: string[] = [];\n for (const l of lines) {\n if (/^[^:]+:\\d+:/.test(l)) shown++;\n if (shown > MAX_HITS) break;\n kept.push(l.replace(/^\\.\\//, ''));\n }\n const capped = cappedFiles(hits);\n const perFile = capped.length\n ? `\\n(${capped.slice(0, 5).join(', ')} ${capped.length === 1 ? 'has' : 'have'} more than ${String(PER_FILE)} matches; search one with path set to it to see them all)`\n : '';\n const note =\n perFile +\n (hits.length > MAX_HITS\n ? `\\n(${hits.length - MAX_HITS} more matches not shown. Most are in: ${whereMatches(hits)}. Narrow with path or glob.)`\n : '');\n return ok(ctx.redactor.text(kept.join('\\n')) + note);\n } catch (err) {\n return fail(`search failed: ${(err as Error).message}`);\n }\n },\n};\n\n/** A match's folder, two levels deep at most (\"src/api\"), or \".\" for files at the top. */\nfunction folderOf(parts: readonly string[]): string {\n if (parts.length > 2) return parts.slice(0, 2).join('/');\n if (parts.length === 2) return parts[0] ?? '.';\n return '.';\n}\n\n/** \"src/api (40), web/pages (12)\": where the matches are, by their first two folders. */\nexport function whereMatches(hits: readonly string[], top = 6): string {\n const counts = new Map<string, number>();\n for (const h of hits) {\n const file = h.replace(/^\\.\\//, '').split(':')[0] ?? '';\n const parts = file.split('/');\n const key = folderOf(parts);\n counts.set(key, (counts.get(key) ?? 0) + 1);\n }\n return [...counts]\n .sort((a, b) => b[1] - a[1])\n .slice(0, top)\n .map(([k, n]) => `${k} (${String(n)})`)\n .join(', ');\n}\n\n/** Files that reached the per-file cap, so their remaining matches weren't returned. */\nfunction cappedFiles(hits: readonly string[]): string[] {\n const counts = new Map<string, number>();\n for (const h of hits) {\n const file = h.replace(/^\\.\\//, '').split(':')[0] ?? '';\n counts.set(file, (counts.get(file) ?? 0) + 1);\n }\n return [...counts].filter(([, n]) => n >= PER_FILE).map(([f]) => f);\n}\n\nexport const _internal = { viaJs };\n","/**\n * Minimal gitignore-style glob for protected paths: `**` spans directories, `*` and `?`\n * stay within one segment. A pattern without `/` matches the basename at any depth.\n * Written here because node's path.matchesGlob is still experimental on Node 22.\n */\nexport function globToRegExp(pattern: string): RegExp {\n let p = pattern.replace(/^\\.\\//, '');\n const anchored = p.includes('/');\n if (p.endsWith('/')) p += '**';\n let re = '';\n for (let i = 0; i < p.length; i++) {\n const c = p.charAt(i);\n if (c === '*') {\n if (p[i + 1] === '*') {\n const slash = p[i + 2] === '/';\n re += slash ? '(?:.*/)?' : '.*';\n i += slash ? 2 : 1;\n } else re += '[^/]*';\n } else if (c === '?') re += '[^/]';\n else re += c.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n return new RegExp(anchored ? `^${re}$` : `^(?:.*/)?${re}$`);\n}\n\nexport function matchesAny(path: string, patterns: readonly string[]): string | undefined {\n const normalized = path.replace(/\\\\/g, '/');\n return patterns.find((p) => globToRegExp(p).test(normalized));\n}\n","import type { z } from 'zod';\nimport type { EventLog } from '../core/events.js';\nimport type { Executor } from '../core/exec.js';\nimport type { RunStore } from '../core/run-store.js';\nimport type { RunningContext } from '../core/running-context.js';\nimport type { PolicyContext } from '../security/command-policy.js';\nimport { OutsideRootError, type PathJail } from '../security/paths.js';\nimport type { Redactor } from '../security/redact.js';\n\n/** An agent card a helper plays: its instructions and the tools it asked for. */\nexport interface HelperCard {\n name: string;\n prompt: string;\n tools: readonly string[];\n}\n\nexport interface ToolContext {\n /** Where `bash` runs: the host, or the run's sandbox. */\n exec?: Executor;\n jail: PathJail;\n env: Record<string, string>;\n store: RunStore;\n events: EventLog;\n redactor: Redactor;\n policy: PolicyContext;\n cycle: number;\n maxCmdTimeoutMs: number;\n notesMaxTokens: number;\n today: string;\n signal?: AbortSignal;\n /** Counter for `cmd-<cycle>-<n>` log ids. */\n nextCommandId: () => string;\n /** Advisory pre-execution safety check (judge, log-only). Must never block. */\n onBash?: (command: string) => void;\n /** Files the agent edited this cycle (for \"no edits after K turns\" and reporting). */\n edited: Set<string>;\n /**\n * Content hashes of what `read` has already returned this cycle, by path and range. A repeat\n * read of unchanged content gets a one-line pointer instead of the same text again. Cleared\n * whenever old tool results are elided or compacted, since the earlier copy is then gone.\n */\n reads?: Map<string, string>;\n /**\n * Interactive chat only: ask the person at the keyboard to allow something the harness would\n * otherwise refuse (a path outside the repo, a command the policy denies). Unattended runs never\n * set it, so there nothing can be overridden.\n */\n ask?: (question: string) => Promise<boolean>;\n /** The current task's (or chat's) running context; unset where there is none. */\n runningContext?: RunningContext;\n /**\n * Runs a read-only child agent (the `task` and `agent` tools), optionally playing an agent\n * card. Unset inside a child: no recursion.\n */\n subagent?: (description: string, kind: SubagentKind, card?: HelperCard) => Promise<string>;\n}\n\nexport const SUBAGENT_KINDS = ['explore', 'research', 'review'] as const;\nexport type SubagentKind = (typeof SUBAGENT_KINDS)[number];\n\nexport interface ToolOutput {\n content: string;\n isError?: boolean;\n /** The tool did the loop's job (e.g. the plan is written): end the loop after this turn. */\n final?: boolean;\n}\n\nexport interface Tool<S extends z.ZodType = z.ZodType> {\n name: string;\n description: string;\n schema: S;\n /** Read-only tools are the only ones the planner gets. */\n readOnly: boolean;\n /** Tidy model input before validation (shape slips only; meaning is still validated). */\n normalize?: (input: unknown) => unknown;\n run(input: z.infer<S>, ctx: ToolContext): Promise<ToolOutput>;\n}\n\nexport const ok = (content: string): ToolOutput => ({ content });\n\n/** `jail.resolve`, but a path outside the repo can be allowed by the person, when `ask` is set. */\nexport async function resolvePath(\n ctx: ToolContext,\n path: string,\n mode: 'read' | 'write',\n): Promise<string> {\n try {\n return ctx.jail.resolve(path, mode);\n } catch (err) {\n if (\n err instanceof OutsideRootError &&\n ctx.ask &&\n (await ctx.ask(`${mode} ${path}? It is outside the repo.`))\n )\n return ctx.jail.resolve(path, mode, { allowOutside: true });\n throw err;\n }\n}\nexport const fail = (content: string): ToolOutput => ({ content, isError: true });\n"],"mappings":"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","names":["readdir","join","relative","z","basename","dirname","relative","resolve","relative","listTree","basename","dirname","resolve","schema","z","readdir","relative","join"]}
@@ -56,7 +56,7 @@ import {
56
56
  withoutVendored,
57
57
  workerTools,
58
58
  writeIntentTool
59
- } from "./chunk-CP53ICXW.js";
59
+ } from "./chunk-IGUI6IJP.js";
60
60
  import {
61
61
  RunStore,
62
62
  applyPlanUpdate,
@@ -86,7 +86,7 @@ import {
86
86
  fail,
87
87
  isSecretPath,
88
88
  ok
89
- } from "./chunk-VDO3EKC4.js";
89
+ } from "./chunk-4DX4BPIF.js";
90
90
  import {
91
91
  QuotaLedger
92
92
  } from "./chunk-DGKPM32R.js";
@@ -5864,4 +5864,4 @@ export {
5864
5864
  EventTail,
5865
5865
  LiveFeed
5866
5866
  };
5867
- //# sourceMappingURL=chunk-RZK4KYXQ.js.map
5867
+ //# sourceMappingURL=chunk-6U3WB5AL.js.map
@@ -14,7 +14,7 @@ import {
14
14
  humanize,
15
15
  pickRun,
16
16
  supervisorAlive
17
- } from "./chunk-RZK4KYXQ.js";
17
+ } from "./chunk-6U3WB5AL.js";
18
18
  import {
19
19
  connect,
20
20
  connectFromEnv,
@@ -27,14 +27,14 @@ import {
27
27
  CodeChat,
28
28
  attachImages,
29
29
  resolveModelLenient
30
- } from "./chunk-32JVRGO6.js";
30
+ } from "./chunk-3Y7NVLVI.js";
31
31
  import {
32
32
  emptyTelemetry,
33
33
  fold,
34
34
  parseFrontmatter,
35
35
  resolveRunDeps,
36
36
  todoItemsOf
37
- } from "./chunk-CP53ICXW.js";
37
+ } from "./chunk-IGUI6IJP.js";
38
38
  import {
39
39
  RunStore,
40
40
  compactPlanView,
@@ -542,6 +542,11 @@ ${args}` : cmd.template;
542
542
  }
543
543
 
544
544
  // src/tui/session.ts
545
+ function looksLikeError(line) {
546
+ const leads = /^\s*(?:error|✗|fatal|warning|refused)\b/i;
547
+ const says = /\b(?:error|failed|cannot|can't|not found|denied|invalid)\b/i;
548
+ return leads.test(line) || says.test(line);
549
+ }
545
550
  var SLASH = [
546
551
  {
547
552
  name: "model",
@@ -836,7 +841,8 @@ var Session = class {
836
841
  write: (chunk, _enc, cb) => {
837
842
  const s = chunk.toString();
838
843
  if (kind === "out") out += s;
839
- this.push(kind, s);
844
+ for (const line of s.split("\n").filter((l) => l.trim()))
845
+ this.push(kind === "err" && !looksLikeError(line) ? "system" : kind, line);
840
846
  cb();
841
847
  }
842
848
  });
@@ -1744,10 +1750,11 @@ export {
1744
1750
  timeLabel,
1745
1751
  AutoTimePicker,
1746
1752
  MarkdownLine,
1753
+ looksLikeError,
1747
1754
  SLASH,
1748
1755
  MORE_SLASH,
1749
1756
  AUTONOMOUS_DEFAULT_GOAL,
1750
1757
  Session,
1751
1758
  helpText
1752
1759
  };
1753
- //# sourceMappingURL=chunk-OGGJJABC.js.map
1760
+ //# sourceMappingURL=chunk-FLIPPDUD.js.map