@guangnao/agent-cli 1.1.6 → 1.1.7

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/cli.js +123 -27
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -3413,7 +3413,7 @@ var shell = {
3413
3413
  };
3414
3414
 
3415
3415
  // src/tools/read_file.ts
3416
- import { readFileSync, statSync } from "node:fs";
3416
+ import { readFileSync } from "node:fs";
3417
3417
 
3418
3418
  // src/tools/file.ts
3419
3419
  import { resolve, isAbsolute, relative } from "node:path";
@@ -3430,17 +3430,23 @@ function safePath(p) {
3430
3430
  var MAX = 64 * 1024;
3431
3431
  var readFile = {
3432
3432
  name: "read_file",
3433
- schema: { name: "read_file", description: "read a UTF-8 text file (confined to the launch dir)", parameters: { type: "object", required: ["path"], properties: {
3434
- path: { type: "string", description: "file path, relative to the launch dir" }
3433
+ schema: { name: "read_file", description: "read a UTF-8 text file (confined to the launch dir). Large files page: pass `offset` (and the result\u2019s `nextOffset`) to read the rest.", parameters: { type: "object", required: ["path"], properties: {
3434
+ path: { type: "string", description: "file path, relative to the launch dir" },
3435
+ offset: { type: "number", description: "char index to start at (default 0); use the previous result\u2019s `nextOffset` to continue" },
3436
+ limit: { type: "number", description: `max chars to return (default & cap ${MAX})` }
3435
3437
  } } },
3436
3438
  run: (i) => {
3437
- const { path } = i;
3439
+ const { path, offset, limit } = i;
3438
3440
  const abs = safePath(path);
3439
3441
  if (!abs) return { error: `path escapes the working directory: ${path}` };
3440
3442
  try {
3441
- const size = statSync(abs).size;
3442
3443
  const content = readFileSync(abs, "utf8");
3443
- return size > MAX ? { path, truncated: true, content: content.slice(0, MAX) } : { path, content };
3444
+ const total = content.length;
3445
+ const start = Math.max(0, Math.floor(offset ?? 0));
3446
+ if (start >= total) return total === 0 ? { path, content: "" } : { path, content: "", total, note: `offset ${start} is at/past end of file (${total} chars)` };
3447
+ const slice = content.slice(start, start + Math.min(limit ?? MAX, MAX));
3448
+ const end = start + slice.length;
3449
+ return end < total ? { path, content: slice, truncated: true, total, offset: start, nextOffset: end, note: `showing chars ${start}\u2013${end} of ${total}; call read_file again with offset=${end} for the rest` } : { path, content: slice, ...start > 0 ? { offset: start, total } : {} };
3444
3450
  } catch (e) {
3445
3451
  return { error: e instanceof Error ? e.message : String(e) };
3446
3452
  }
@@ -3470,7 +3476,7 @@ var listDir = {
3470
3476
  };
3471
3477
 
3472
3478
  // src/tools/grep.ts
3473
- import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
3479
+ import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "node:fs";
3474
3480
  import { join } from "node:path";
3475
3481
  var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "coverage", ".cache"]);
3476
3482
  var MAX_HITS = 100;
@@ -3509,7 +3515,7 @@ var grep = {
3509
3515
  const rootLen = base().length + 1;
3510
3516
  for (const f of walk(root)) {
3511
3517
  try {
3512
- if (statSync2(f).size > MAX_FILE) continue;
3518
+ if (statSync(f).size > MAX_FILE) continue;
3513
3519
  } catch {
3514
3520
  continue;
3515
3521
  }
@@ -5436,13 +5442,99 @@ function nursery(sys) {
5436
5442
  };
5437
5443
  }
5438
5444
 
5445
+ // ../agent-os/src/kernel/blob.ts
5446
+ var PAGE = 8 * 1024;
5447
+ var GREP_MAX = 50;
5448
+ var BlobStore = class {
5449
+ constructor(cap = 4 * 1024 * 1024) {
5450
+ this.cap = cap;
5451
+ }
5452
+ cap;
5453
+ map = /* @__PURE__ */ new Map();
5454
+ // insertion order = LRU age (re-set on touch → newest)
5455
+ seq = 0;
5456
+ bytes = 0;
5457
+ // 4 MB total
5458
+ /** Store content; return a fresh handle. Evict the oldest blobs while over the char cap. */
5459
+ put(content) {
5460
+ const handle = `blob_${(++this.seq).toString(36)}`;
5461
+ this.map.set(handle, content);
5462
+ this.bytes += content.length;
5463
+ while (this.bytes > this.cap && this.map.size > 1) {
5464
+ const [old, v] = this.map.entries().next().value;
5465
+ this.map.delete(old);
5466
+ this.bytes -= v.length;
5467
+ }
5468
+ return handle;
5469
+ }
5470
+ touch(handle) {
5471
+ const v = this.map.get(handle);
5472
+ if (v === void 0) return void 0;
5473
+ this.map.delete(handle);
5474
+ this.map.set(handle, v);
5475
+ return v;
5476
+ }
5477
+ /** A character window of a blob (paged like read_file). */
5478
+ read(handle, offset = 0, limit = PAGE) {
5479
+ const v = this.touch(handle);
5480
+ if (v === void 0) return { error: `unknown blob handle: ${handle}` };
5481
+ const total = v.length;
5482
+ const start = Math.max(0, Math.floor(offset));
5483
+ if (start >= total) return { handle, content: "", total, note: `offset ${start} is at/past end (${total} chars)` };
5484
+ const slice = v.slice(start, start + Math.min(Math.max(1, Math.floor(limit)), PAGE));
5485
+ const end = start + slice.length;
5486
+ return end < total ? { handle, content: slice, truncated: true, total, offset: start, nextOffset: end, note: `chars ${start}\u2013${end} of ${total}; call blob_read with offset=${end} for more` } : { handle, content: slice, total, offset: start };
5487
+ }
5488
+ /** Search a blob for a JS regex; return a short SNIPPET (char offset + surrounding window) per match. Snippet
5489
+ * rather than whole line because a blob is often one long line (JSON.stringify escapes newlines) — a line view
5490
+ * would return the entire blob and just overflow again. The offset lets the model blob_read around it. */
5491
+ grep(handle, pattern, max = GREP_MAX) {
5492
+ const v = this.touch(handle);
5493
+ if (v === void 0) return { error: `unknown blob handle: ${handle}` };
5494
+ let re;
5495
+ try {
5496
+ re = new RegExp(pattern, "g");
5497
+ } catch {
5498
+ return { error: `invalid pattern: ${pattern}` };
5499
+ }
5500
+ const PAD = 80, matches = [];
5501
+ let m;
5502
+ while ((m = re.exec(v)) && matches.length < max) {
5503
+ const at = m.index, from = Math.max(0, at - PAD), to = Math.min(v.length, at + m[0].length + PAD);
5504
+ matches.push(`@${at}: ${from > 0 ? "\u2026" : ""}${v.slice(from, to)}${to < v.length ? "\u2026" : ""}`);
5505
+ if (re.lastIndex === at) re.lastIndex++;
5506
+ }
5507
+ return { handle, total: v.length, matches, ...matches.length >= max ? { truncated: true } : {} };
5508
+ }
5509
+ };
5510
+ var BLOB_TOOLS = [
5511
+ { name: "blob_read", description: "read a window of a large stored tool output by its blob handle (paged like read_file; use when a result said it was stored as blob_\u2026)", parameters: { type: "object", required: ["handle"], properties: {
5512
+ handle: { type: "string", description: "the blob_\u2026 handle from a truncated result" },
5513
+ offset: { type: "number", description: "char index to start at (default 0; use the previous result\u2019s nextOffset)" },
5514
+ limit: { type: "number", description: `max chars (default & cap ${PAGE})` }
5515
+ } } },
5516
+ { name: "blob_grep", description: "find lines matching a JS regex inside a large stored tool output, by blob handle (search without re-reading it all)", parameters: { type: "object", required: ["handle", "pattern"], properties: {
5517
+ handle: { type: "string", description: "the blob_\u2026 handle" },
5518
+ pattern: { type: "string", description: "JS regex, matched per line" }
5519
+ } } }
5520
+ ];
5521
+ var BLOB_NAMES = new Set(BLOB_TOOLS.map((t) => t.name));
5522
+ var isBlobTool = (name) => BLOB_NAMES.has(name);
5523
+ function execBlobTool(store, name, input) {
5524
+ const a = input ?? {};
5525
+ if (!a.handle) return { error: "handle is required" };
5526
+ if (name === "blob_read") return store.read(a.handle, a.offset, a.limit);
5527
+ if (name === "blob_grep") return a.pattern ? store.grep(a.handle, a.pattern) : { error: "pattern is required" };
5528
+ return { error: `not a blob tool: ${name}` };
5529
+ }
5530
+
5439
5531
  // ../agent-os/src/userland/agent.ts
5440
5532
  var DEFAULT_SYSTEM = "You are a capable autonomous agent. Break the task into steps and keep going \u2014 investigate with tools, then act \u2014 until it is fully done; never stop at a plan or a partial result. Verify before answering, then reply concisely.";
5441
5533
  var HARD_MAX = 384;
5442
5534
  var MAX_TOOL_CHARS = 8e3;
5443
5535
  var isMessage = (b) => !!b && typeof b.role === "string";
5444
- var capResult = (s) => s.length > MAX_TOOL_CHARS ? s.slice(0, MAX_TOOL_CHARS) + `
5445
- \u2026[truncated ${s.length - MAX_TOOL_CHARS} chars]` : s;
5536
+ var capResult = (s, blobs) => s.length <= MAX_TOOL_CHARS ? s : s.slice(0, MAX_TOOL_CHARS) + `
5537
+ \u2026[+${s.length - MAX_TOOL_CHARS} chars \u2014 full result stored as ${blobs.put(s)}; call blob_read(handle, offset=${MAX_TOOL_CHARS}) or blob_grep(handle, pattern) for the rest]`;
5446
5538
  function orderForCache(segs) {
5447
5539
  const lead = [], sums = [], rest = [];
5448
5540
  for (const s of segs) (s.pinned && !s.summary ? lead : s.summary ? sums : rest).push(s);
@@ -5470,7 +5562,7 @@ function unproductive(out) {
5470
5562
  const st = o["status"];
5471
5563
  return typeof st === "number" && (st < 200 || st >= 300);
5472
5564
  }
5473
- async function runTurn(sys, llm, tools2, spec) {
5565
+ async function runTurn(sys, llm, tools2, spec, blobs) {
5474
5566
  const ceil = Math.max(1, Math.min(spec.maxSteps ?? HARD_MAX, HARD_MAX));
5475
5567
  const base2 = Math.max(1, spec.baseSteps ?? 16);
5476
5568
  const stallMax = Math.max(1, spec.stallLimit ?? 3);
@@ -5493,6 +5585,7 @@ async function runTurn(sys, llm, tools2, spec) {
5493
5585
  }
5494
5586
  const messages = promptMessages();
5495
5587
  let offer = noTools ? void 0 : spec.tools;
5588
+ if (offer && tools2) offer = [...offer, ...BLOB_TOOLS];
5496
5589
  if (steer && offer && del) offer = offer.filter((t) => !del.withhold.includes(t.name));
5497
5590
  const reply = await sys.read(llm, { messages, tools: offer, maxTokens: spec.maxTokens });
5498
5591
  for (const s of sys.workingSet()) {
@@ -5513,13 +5606,15 @@ async function runTurn(sys, llm, tools2, spec) {
5513
5606
  }
5514
5607
  }
5515
5608
  if (fresh.length) {
5516
- const outs = await sys.readAll(tools2, fresh.map((f) => f.req));
5517
- fresh.forEach((f, j) => cache.set(f.key, outs[j]));
5609
+ const viaDevice = fresh.filter((f) => !isBlobTool(f.req.tool));
5610
+ const devOuts = viaDevice.length ? await sys.readAll(tools2, viaDevice.map((f) => f.req)) : [];
5611
+ let di = 0;
5612
+ for (const f of fresh) cache.set(f.key, isBlobTool(f.req.tool) ? execBlobTool(blobs, f.req.tool, f.req.input) : devOuts[di++]);
5518
5613
  }
5519
5614
  let progressed = false;
5520
5615
  for (let i = 0; i < calls.length; i++) {
5521
5616
  const out = cache.get(keys[i]);
5522
- await sys.remember({ role: "tool", toolCallId: calls[i].id, name: calls[i].name, content: capResult(JSON.stringify(out)) });
5617
+ await sys.remember({ role: "tool", toolCallId: calls[i].id, name: calls[i].name, content: capResult(JSON.stringify(out), blobs) });
5523
5618
  if (ran.has(keys[i]) && !unproductive(out)) progressed = true;
5524
5619
  }
5525
5620
  if (del) {
@@ -5570,7 +5665,7 @@ function reactAgent(spec) {
5570
5665
  const { llm, tools: tools2, parent } = await openDevices(sys);
5571
5666
  await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
5572
5667
  await sys.remember({ role: "user", content: spec.task ?? "" }, { pin: true });
5573
- const r = await runTurn(sys, llm, tools2, spec);
5668
+ const r = await runTurn(sys, llm, tools2, spec, new BlobStore());
5574
5669
  if (parent) await sys.send(parent, { $: "result", answer: r.answer, steps: r.steps });
5575
5670
  } };
5576
5671
  }
@@ -5588,13 +5683,14 @@ function conversationAgent(spec = {}) {
5588
5683
  } else {
5589
5684
  await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
5590
5685
  }
5686
+ const blobs = new BlobStore();
5591
5687
  for (; ; ) {
5592
5688
  const msg = await sys.recv();
5593
5689
  const userTurn = { role: "user", content: msg.body.task, ...msg.body.images?.length ? { images: msg.body.images } : {} };
5594
5690
  await sys.remember(userTurn);
5595
5691
  let r;
5596
5692
  try {
5597
- r = await runTurn(sys, llm, tools2, spec);
5693
+ r = await runTurn(sys, llm, tools2, spec, blobs);
5598
5694
  } catch (e) {
5599
5695
  r = { answer: "", steps: 0, error: e instanceof Error ? e.message : String(e) };
5600
5696
  }
@@ -6037,7 +6133,7 @@ import { homedir } from "node:os";
6037
6133
  import { join as join3 } from "node:path";
6038
6134
 
6039
6135
  // src/skills/loader.ts
6040
- import { readdirSync as readdirSync3, readFileSync as readFileSync6, existsSync as existsSync4, statSync as statSync3, openSync, readSync, closeSync } from "node:fs";
6136
+ import { readdirSync as readdirSync3, readFileSync as readFileSync6, existsSync as existsSync4, statSync as statSync2, openSync, readSync, closeSync } from "node:fs";
6041
6137
  import { join as join2, basename, extname } from "node:path";
6042
6138
  var FM_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
6043
6139
  var HEAD_BYTES = 8192;
@@ -6083,7 +6179,7 @@ function loadSkillsDir(dir) {
6083
6179
  for (const entry of readdirSync3(dir)) {
6084
6180
  try {
6085
6181
  const p = join2(dir, entry);
6086
- if (statSync3(p).isDirectory()) {
6182
+ if (statSync2(p).isDirectory()) {
6087
6183
  const md = join2(p, "SKILL.md");
6088
6184
  if (existsSync4(md)) out.push(lazyFromFile(md, entry));
6089
6185
  } else if (extname(entry) === ".md") out.push(lazyFromFile(p, basename(entry, ".md")));
@@ -9910,7 +10006,7 @@ function codingReflect(plan) {
9910
10006
  }
9911
10007
 
9912
10008
  // src/agent/session.ts
9913
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5, statSync as statSync4, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "node:fs";
10009
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5, statSync as statSync3, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "node:fs";
9914
10010
  import { homedir as homedir3 } from "node:os";
9915
10011
  import { join as join7 } from "node:path";
9916
10012
  var oneLine = (s) => typeof s === "string" ? s.replace(/\s+/g, " ").trim() : "";
@@ -9968,7 +10064,7 @@ function sessionIdsByRecency() {
9968
10064
  if (!existsSync8(dir)) return [];
9969
10065
  return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
9970
10066
  try {
9971
- return { id: f.slice(0, -5), mt: statSync4(join7(dir, f)).mtimeMs };
10067
+ return { id: f.slice(0, -5), mt: statSync3(join7(dir, f)).mtimeMs };
9972
10068
  } catch {
9973
10069
  return { id: f.slice(0, -5), mt: 0 };
9974
10070
  }
@@ -9982,12 +10078,12 @@ function loadSessionMeta(id) {
9982
10078
  // src/ui/app.ts
9983
10079
  import { resolve as resolve3, join as join11 } from "node:path";
9984
10080
  import { homedir as homedir6, tmpdir as tmpdir3 } from "node:os";
9985
- import { statSync as statSync6, readFileSync as readFileSync14, unlinkSync as unlinkSync6 } from "node:fs";
10081
+ import { statSync as statSync5, readFileSync as readFileSync14, unlinkSync as unlinkSync6 } from "node:fs";
9986
10082
 
9987
10083
  // package.json
9988
10084
  var package_default = {
9989
10085
  name: "@guangnao/agent-cli",
9990
- version: "1.1.6",
10086
+ version: "1.1.7",
9991
10087
  description: "AgentOS terminal CLI \u2014 drive a real LLM agent on the microkernel; strongest-UX shell, advanced agent core",
9992
10088
  type: "module",
9993
10089
  bin: {
@@ -10145,7 +10241,7 @@ var modeLabel = (m) => m === "auto" ? "auto" : m === "acceptEdits" ? "auto-edits
10145
10241
 
10146
10242
  // src/ui/clipboard.ts
10147
10243
  import { execFile } from "node:child_process";
10148
- import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync5, unlinkSync as unlinkSync5 } from "node:fs";
10244
+ import { existsSync as existsSync10, readFileSync as readFileSync12, statSync as statSync4, unlinkSync as unlinkSync5 } from "node:fs";
10149
10245
  import { tmpdir } from "node:os";
10150
10246
  import { join as join9 } from "node:path";
10151
10247
  var MACOS_JXA = `(function(){
@@ -10249,7 +10345,7 @@ async function shrinkImage(src) {
10249
10345
  const maxEdge = dims ? Math.max(dims.w, dims.h) : Infinity;
10250
10346
  let origBytes = Infinity;
10251
10347
  try {
10252
- origBytes = statSync5(src).size;
10348
+ origBytes = statSync4(src).size;
10253
10349
  } catch {
10254
10350
  }
10255
10351
  if (maxEdge <= MAX_EDGE && origBytes <= PASSTHROUGH_BYTES) return fallback;
@@ -10265,7 +10361,7 @@ async function shrinkImage(src) {
10265
10361
  await sips([...resize, "-s", "format", "png", src, "--out", pngOut]);
10266
10362
  let pngBytes = Infinity;
10267
10363
  try {
10268
- pngBytes = statSync5(pngOut).size;
10364
+ pngBytes = statSync4(pngOut).size;
10269
10365
  } catch {
10270
10366
  }
10271
10367
  if (pngBytes > PNG_TO_JPEG_BYTES) {
@@ -11954,7 +12050,7 @@ ${c.done("\u2713")} ${c.dim("Saved connection cleared. Run ")}${c.bold("agentos"
11954
12050
  static dirOk(d) {
11955
12051
  if (!d) return false;
11956
12052
  try {
11957
- return statSync6(d).isDirectory();
12053
+ return statSync5(d).isDirectory();
11958
12054
  } catch {
11959
12055
  return false;
11960
12056
  }
@@ -11982,7 +12078,7 @@ ${c.dim("set: /cwd <path> \xB7 global default: /cwd -g <path>")}`);
11982
12078
  const expanded = path === "~" || path.startsWith("~/") ? homedir6() + path.slice(1) : path;
11983
12079
  const abs = resolve3(process.env["AGENTOS_CWD"] || process.cwd(), expanded);
11984
12080
  try {
11985
- if (!statSync6(abs).isDirectory()) {
12081
+ if (!statSync5(abs).isDirectory()) {
11986
12082
  this.note(c.fail(`not a directory: ${abs}`));
11987
12083
  return;
11988
12084
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guangnao/agent-cli",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "AgentOS terminal CLI — drive a real LLM agent on the microkernel; strongest-UX shell, advanced agent core",
5
5
  "type": "module",
6
6
  "bin": {