@guangnao/agent-cli 1.1.5 → 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 +181 -40
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2950,27 +2950,44 @@ function parseImageUrl(url) {
2950
2950
  var isDataImage = (r) => "base64" in r;
2951
2951
 
2952
2952
  // src/llm/anthropic.ts
2953
+ var noTemperature = /* @__PURE__ */ new Set();
2954
+ var TEMP_DEPRECATED = /temperature.{0,40}deprecated|deprecated.{0,40}temperature/i;
2953
2955
  var anthropic = (cfg2) => {
2954
2956
  const base2 = (cfg2.baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
2955
2957
  const headers = { "content-type": "application/json", "x-api-key": cfg2.apiKey, "anthropic-version": "2023-06-01" };
2956
2958
  return async (req) => {
2957
2959
  const system = req.messages.filter((m) => m.role === "system").map((m) => m.content).join("\n");
2958
- const body = {
2960
+ const mkBody = (withTemp) => ({
2959
2961
  model: cfg2.model,
2960
2962
  max_tokens: req.maxTokens ?? 4096,
2961
2963
  // Anthropic requires this
2962
- temperature: cfg2.temperature ?? 0,
2964
+ ...withTemp ? { temperature: cfg2.temperature ?? 0 } : {},
2965
+ // newest models deprecated it → omit (see below)
2963
2966
  ...system ? { system } : {},
2964
2967
  messages: toMsgs(req.messages),
2965
2968
  ...req.tools?.length ? { tools: req.tools.map(toTool2) } : {}
2969
+ });
2970
+ const run2 = (withTemp) => {
2971
+ const body = mkBody(withTemp);
2972
+ if (req.onDelta) return stream2(base2, headers, body, req.onDelta, req.signal);
2973
+ return blocking(base2, headers, body, req.signal);
2966
2974
  };
2967
- if (req.onDelta) return stream2(base2, headers, body, req.onDelta, req.signal);
2968
- const res = await fetch(`${base2}/messages`, { method: "POST", headers, body: JSON.stringify(body), signal: req.signal });
2969
- if (!res.ok) throw new Error(`llm ${res.status}: ${(await res.text()).slice(0, 300)}`);
2970
- const data = await res.json();
2971
- return fromReply(data);
2975
+ try {
2976
+ return await run2(!noTemperature.has(cfg2.model));
2977
+ } catch (e) {
2978
+ if (!noTemperature.has(cfg2.model) && TEMP_DEPRECATED.test(String(e.message))) {
2979
+ noTemperature.add(cfg2.model);
2980
+ return await run2(false);
2981
+ }
2982
+ throw e;
2983
+ }
2972
2984
  };
2973
2985
  };
2986
+ async function blocking(base2, headers, body, signal) {
2987
+ const res = await fetch(`${base2}/messages`, { method: "POST", headers, body: JSON.stringify(body), signal });
2988
+ if (!res.ok) throw new Error(`llm ${res.status}: ${(await res.text()).slice(0, 300)}`);
2989
+ return fromReply(await res.json());
2990
+ }
2974
2991
  function fromReply(data) {
2975
2992
  const blocks = data.content ?? [];
2976
2993
  const text2 = blocks.filter((b) => b.type === "text").map((b) => b.text).join("") || void 0;
@@ -3175,6 +3192,22 @@ var PROVIDER_LABEL = {
3175
3192
  google: "Gemini"
3176
3193
  };
3177
3194
  var labelOf = (provider2) => PROVIDER_LABEL[provider2.toLowerCase()] ?? provider2;
3195
+ var VENDOR_PREFIX = [
3196
+ ["claude", "Anthropic"],
3197
+ ["gpt", "OpenAI"],
3198
+ ["codex", "OpenAI"],
3199
+ ["chatgpt", "OpenAI"],
3200
+ ["o1", "OpenAI"],
3201
+ ["o3", "OpenAI"],
3202
+ ["o4", "OpenAI"],
3203
+ ["gemini", "Gemini"],
3204
+ ["deepseek", "DeepSeek"]
3205
+ ];
3206
+ function modelProviderLabel(modelId, fallbackProvider) {
3207
+ const id = modelId.toLowerCase();
3208
+ for (const [prefix, vendor] of VENDOR_PREFIX) if (id.startsWith(prefix)) return vendor;
3209
+ return labelOf(fallbackProvider);
3210
+ }
3178
3211
  var defaultModel = {
3179
3212
  gapi: "gmodel-pro",
3180
3213
  gmodel: "gmodel-pro",
@@ -3247,7 +3280,9 @@ async function listModels(provider2, cfg2) {
3247
3280
  return (d2.models ?? []).filter((m) => m.supportedGenerationMethods?.includes("generateContent")).map((m) => (m.name ?? "").replace(/^models\//, "")).filter(Boolean);
3248
3281
  }
3249
3282
  if (p === "anthropic" || p === "claude") {
3250
- const r2 = await fetch("https://api.anthropic.com/v1/models", { headers: { "x-api-key": cfg2.apiKey, "anthropic-version": "2023-06-01" } });
3283
+ const base3 = cfg2.baseUrl?.replace(/\/$/, "");
3284
+ const url = base3 ? `${base3}/models` : "https://api.anthropic.com/v1/models";
3285
+ const r2 = await fetch(url, { headers: { "x-api-key": cfg2.apiKey, authorization: `Bearer ${cfg2.apiKey}`, "anthropic-version": "2023-06-01" } });
3251
3286
  if (!r2.ok) return [];
3252
3287
  const d2 = await r2.json();
3253
3288
  return (d2.data ?? []).map((m) => m.id ?? "").filter(Boolean);
@@ -3378,7 +3413,7 @@ var shell = {
3378
3413
  };
3379
3414
 
3380
3415
  // src/tools/read_file.ts
3381
- import { readFileSync, statSync } from "node:fs";
3416
+ import { readFileSync } from "node:fs";
3382
3417
 
3383
3418
  // src/tools/file.ts
3384
3419
  import { resolve, isAbsolute, relative } from "node:path";
@@ -3395,17 +3430,23 @@ function safePath(p) {
3395
3430
  var MAX = 64 * 1024;
3396
3431
  var readFile = {
3397
3432
  name: "read_file",
3398
- schema: { name: "read_file", description: "read a UTF-8 text file (confined to the launch dir)", parameters: { type: "object", required: ["path"], properties: {
3399
- 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})` }
3400
3437
  } } },
3401
3438
  run: (i) => {
3402
- const { path } = i;
3439
+ const { path, offset, limit } = i;
3403
3440
  const abs = safePath(path);
3404
3441
  if (!abs) return { error: `path escapes the working directory: ${path}` };
3405
3442
  try {
3406
- const size = statSync(abs).size;
3407
3443
  const content = readFileSync(abs, "utf8");
3408
- 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 } : {} };
3409
3450
  } catch (e) {
3410
3451
  return { error: e instanceof Error ? e.message : String(e) };
3411
3452
  }
@@ -3435,7 +3476,7 @@ var listDir = {
3435
3476
  };
3436
3477
 
3437
3478
  // src/tools/grep.ts
3438
- 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";
3439
3480
  import { join } from "node:path";
3440
3481
  var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "coverage", ".cache"]);
3441
3482
  var MAX_HITS = 100;
@@ -3474,7 +3515,7 @@ var grep = {
3474
3515
  const rootLen = base().length + 1;
3475
3516
  for (const f of walk(root)) {
3476
3517
  try {
3477
- if (statSync2(f).size > MAX_FILE) continue;
3518
+ if (statSync(f).size > MAX_FILE) continue;
3478
3519
  } catch {
3479
3520
  continue;
3480
3521
  }
@@ -5401,13 +5442,99 @@ function nursery(sys) {
5401
5442
  };
5402
5443
  }
5403
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
+
5404
5531
  // ../agent-os/src/userland/agent.ts
5405
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.";
5406
5533
  var HARD_MAX = 384;
5407
5534
  var MAX_TOOL_CHARS = 8e3;
5408
5535
  var isMessage = (b) => !!b && typeof b.role === "string";
5409
- var capResult = (s) => s.length > MAX_TOOL_CHARS ? s.slice(0, MAX_TOOL_CHARS) + `
5410
- \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]`;
5411
5538
  function orderForCache(segs) {
5412
5539
  const lead = [], sums = [], rest = [];
5413
5540
  for (const s of segs) (s.pinned && !s.summary ? lead : s.summary ? sums : rest).push(s);
@@ -5435,7 +5562,7 @@ function unproductive(out) {
5435
5562
  const st = o["status"];
5436
5563
  return typeof st === "number" && (st < 200 || st >= 300);
5437
5564
  }
5438
- async function runTurn(sys, llm, tools2, spec) {
5565
+ async function runTurn(sys, llm, tools2, spec, blobs) {
5439
5566
  const ceil = Math.max(1, Math.min(spec.maxSteps ?? HARD_MAX, HARD_MAX));
5440
5567
  const base2 = Math.max(1, spec.baseSteps ?? 16);
5441
5568
  const stallMax = Math.max(1, spec.stallLimit ?? 3);
@@ -5458,6 +5585,7 @@ async function runTurn(sys, llm, tools2, spec) {
5458
5585
  }
5459
5586
  const messages = promptMessages();
5460
5587
  let offer = noTools ? void 0 : spec.tools;
5588
+ if (offer && tools2) offer = [...offer, ...BLOB_TOOLS];
5461
5589
  if (steer && offer && del) offer = offer.filter((t) => !del.withhold.includes(t.name));
5462
5590
  const reply = await sys.read(llm, { messages, tools: offer, maxTokens: spec.maxTokens });
5463
5591
  for (const s of sys.workingSet()) {
@@ -5478,13 +5606,15 @@ async function runTurn(sys, llm, tools2, spec) {
5478
5606
  }
5479
5607
  }
5480
5608
  if (fresh.length) {
5481
- const outs = await sys.readAll(tools2, fresh.map((f) => f.req));
5482
- 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++]);
5483
5613
  }
5484
5614
  let progressed = false;
5485
5615
  for (let i = 0; i < calls.length; i++) {
5486
5616
  const out = cache.get(keys[i]);
5487
- 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) });
5488
5618
  if (ran.has(keys[i]) && !unproductive(out)) progressed = true;
5489
5619
  }
5490
5620
  if (del) {
@@ -5535,7 +5665,7 @@ function reactAgent(spec) {
5535
5665
  const { llm, tools: tools2, parent } = await openDevices(sys);
5536
5666
  await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
5537
5667
  await sys.remember({ role: "user", content: spec.task ?? "" }, { pin: true });
5538
- const r = await runTurn(sys, llm, tools2, spec);
5668
+ const r = await runTurn(sys, llm, tools2, spec, new BlobStore());
5539
5669
  if (parent) await sys.send(parent, { $: "result", answer: r.answer, steps: r.steps });
5540
5670
  } };
5541
5671
  }
@@ -5553,13 +5683,14 @@ function conversationAgent(spec = {}) {
5553
5683
  } else {
5554
5684
  await sys.remember({ role: "system", content: spec.system ?? DEFAULT_SYSTEM }, { pin: true });
5555
5685
  }
5686
+ const blobs = new BlobStore();
5556
5687
  for (; ; ) {
5557
5688
  const msg = await sys.recv();
5558
5689
  const userTurn = { role: "user", content: msg.body.task, ...msg.body.images?.length ? { images: msg.body.images } : {} };
5559
5690
  await sys.remember(userTurn);
5560
5691
  let r;
5561
5692
  try {
5562
- r = await runTurn(sys, llm, tools2, spec);
5693
+ r = await runTurn(sys, llm, tools2, spec, blobs);
5563
5694
  } catch (e) {
5564
5695
  r = { answer: "", steps: 0, error: e instanceof Error ? e.message : String(e) };
5565
5696
  }
@@ -6002,7 +6133,7 @@ import { homedir } from "node:os";
6002
6133
  import { join as join3 } from "node:path";
6003
6134
 
6004
6135
  // src/skills/loader.ts
6005
- 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";
6006
6137
  import { join as join2, basename, extname } from "node:path";
6007
6138
  var FM_RE = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
6008
6139
  var HEAD_BYTES = 8192;
@@ -6048,7 +6179,7 @@ function loadSkillsDir(dir) {
6048
6179
  for (const entry of readdirSync3(dir)) {
6049
6180
  try {
6050
6181
  const p = join2(dir, entry);
6051
- if (statSync3(p).isDirectory()) {
6182
+ if (statSync2(p).isDirectory()) {
6052
6183
  const md = join2(p, "SKILL.md");
6053
6184
  if (existsSync4(md)) out.push(lazyFromFile(md, entry));
6054
6185
  } else if (extname(entry) === ".md") out.push(lazyFromFile(p, basename(entry, ".md")));
@@ -9875,7 +10006,7 @@ function codingReflect(plan) {
9875
10006
  }
9876
10007
 
9877
10008
  // src/agent/session.ts
9878
- 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";
9879
10010
  import { homedir as homedir3 } from "node:os";
9880
10011
  import { join as join7 } from "node:path";
9881
10012
  var oneLine = (s) => typeof s === "string" ? s.replace(/\s+/g, " ").trim() : "";
@@ -9933,7 +10064,7 @@ function sessionIdsByRecency() {
9933
10064
  if (!existsSync8(dir)) return [];
9934
10065
  return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
9935
10066
  try {
9936
- 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 };
9937
10068
  } catch {
9938
10069
  return { id: f.slice(0, -5), mt: 0 };
9939
10070
  }
@@ -9947,12 +10078,12 @@ function loadSessionMeta(id) {
9947
10078
  // src/ui/app.ts
9948
10079
  import { resolve as resolve3, join as join11 } from "node:path";
9949
10080
  import { homedir as homedir6, tmpdir as tmpdir3 } from "node:os";
9950
- 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";
9951
10082
 
9952
10083
  // package.json
9953
10084
  var package_default = {
9954
10085
  name: "@guangnao/agent-cli",
9955
- version: "1.1.5",
10086
+ version: "1.1.7",
9956
10087
  description: "AgentOS terminal CLI \u2014 drive a real LLM agent on the microkernel; strongest-UX shell, advanced agent core",
9957
10088
  type: "module",
9958
10089
  bin: {
@@ -10110,7 +10241,7 @@ var modeLabel = (m) => m === "auto" ? "auto" : m === "acceptEdits" ? "auto-edits
10110
10241
 
10111
10242
  // src/ui/clipboard.ts
10112
10243
  import { execFile } from "node:child_process";
10113
- 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";
10114
10245
  import { tmpdir } from "node:os";
10115
10246
  import { join as join9 } from "node:path";
10116
10247
  var MACOS_JXA = `(function(){
@@ -10214,7 +10345,7 @@ async function shrinkImage(src) {
10214
10345
  const maxEdge = dims ? Math.max(dims.w, dims.h) : Infinity;
10215
10346
  let origBytes = Infinity;
10216
10347
  try {
10217
- origBytes = statSync5(src).size;
10348
+ origBytes = statSync4(src).size;
10218
10349
  } catch {
10219
10350
  }
10220
10351
  if (maxEdge <= MAX_EDGE && origBytes <= PASSTHROUGH_BYTES) return fallback;
@@ -10230,7 +10361,7 @@ async function shrinkImage(src) {
10230
10361
  await sips([...resize, "-s", "format", "png", src, "--out", pngOut]);
10231
10362
  let pngBytes = Infinity;
10232
10363
  try {
10233
- pngBytes = statSync5(pngOut).size;
10364
+ pngBytes = statSync4(pngOut).size;
10234
10365
  } catch {
10235
10366
  }
10236
10367
  if (pngBytes > PNG_TO_JPEG_BYTES) {
@@ -11675,7 +11806,10 @@ ${c.dim(cut(p.system, 300))}`);
11675
11806
  return;
11676
11807
  }
11677
11808
  const choice = await this.dock.requestSplitSelect(
11678
- opts.map((o) => ({ label: o.model, hint: labelOf(o.provider), value: `${o.provider}|${o.model}`, detail: `provider: ${labelOf(o.provider)}${`${labelOf(o.provider)} \xB7 ${o.model}` === m.current ? " (current)" : ""}` })),
11809
+ opts.map((o) => {
11810
+ const vendor = modelProviderLabel(o.model, o.provider);
11811
+ return { label: o.model, hint: vendor, value: `${o.provider}|${o.model}`, detail: `provider: ${vendor}${`${vendor} \xB7 ${o.model}` === m.current ? " (current)" : ""}` };
11812
+ }),
11679
11813
  "switch model"
11680
11814
  );
11681
11815
  if (choice) this.switchModel(choice);
@@ -11683,12 +11817,13 @@ ${c.dim(cut(p.system, 300))}`);
11683
11817
  switchModel(v) {
11684
11818
  const sep = v.indexOf("|"), provider2 = v.slice(0, sep), model2 = v.slice(sep + 1);
11685
11819
  this.chat = this.opts.modelMenu.build(provider2, model2);
11686
- this.opts.modelMenu.current = `${labelOf(provider2)} \xB7 ${model2}`;
11820
+ this.opts.modelMenu.persist?.(provider2, model2);
11821
+ this.opts.modelMenu.current = `${modelProviderLabel(model2, provider2)} \xB7 ${model2}`;
11687
11822
  const history = this.convo?.dump();
11688
11823
  this.convo?.close();
11689
11824
  this.convo = void 0;
11690
11825
  if (history?.length) this.ensureConvo(history);
11691
- this.note(`${c.done("model")} ${c.dim("\u2192")} ${c.bold(model2)} ${c.dim("(" + labelOf(provider2) + ")")}`);
11826
+ this.note(`${c.done("model")} ${c.dim("\u2192")} ${c.bold(model2)} ${c.dim("(" + modelProviderLabel(model2, provider2) + ")")}`);
11692
11827
  this.dock.setStatusLine(this.statusLine());
11693
11828
  }
11694
11829
  /** /login — re-run the first-run setup wizard (pick provider → paste key → pick model), exactly the flow a
@@ -11915,7 +12050,7 @@ ${c.done("\u2713")} ${c.dim("Saved connection cleared. Run ")}${c.bold("agentos"
11915
12050
  static dirOk(d) {
11916
12051
  if (!d) return false;
11917
12052
  try {
11918
- return statSync6(d).isDirectory();
12053
+ return statSync5(d).isDirectory();
11919
12054
  } catch {
11920
12055
  return false;
11921
12056
  }
@@ -11943,7 +12078,7 @@ ${c.dim("set: /cwd <path> \xB7 global default: /cwd -g <path>")}`);
11943
12078
  const expanded = path === "~" || path.startsWith("~/") ? homedir6() + path.slice(1) : path;
11944
12079
  const abs = resolve3(process.env["AGENTOS_CWD"] || process.cwd(), expanded);
11945
12080
  try {
11946
- if (!statSync6(abs).isDirectory()) {
12081
+ if (!statSync5(abs).isDirectory()) {
11947
12082
  this.note(c.fail(`not a directory: ${abs}`));
11948
12083
  return;
11949
12084
  }
@@ -12257,8 +12392,14 @@ var keyOf = (p) => ({
12257
12392
  function modelMenu() {
12258
12393
  const keyed = ["gapi", "deepseek", "openai", "openai-responses", "gemini", "anthropic"].filter((p) => keyOf(p));
12259
12394
  return {
12260
- current: `${labelOf(provider)} \xB7 ${model}`,
12395
+ current: `${modelProviderLabel(model, provider)} \xB7 ${model}`,
12261
12396
  build: (p, m) => makeChat(p, { apiKey: keyOf(p) ?? apiKey, model: m, baseUrl }),
12397
+ persist: (p, m) => {
12398
+ provider = p;
12399
+ model = m;
12400
+ saveConfig({ provider: p, model: m });
12401
+ },
12402
+ // /models choice survives restart (apiKey/baseUrl already saved)
12262
12403
  async list() {
12263
12404
  const lists = await Promise.all(keyed.map(async (p) => {
12264
12405
  const ids = await listModels(p, { apiKey: keyOf(p) ?? apiKey, model: defaultModel[p] ?? model, baseUrl });
@@ -12277,7 +12418,7 @@ async function relogin() {
12277
12418
  model = r.model;
12278
12419
  baseUrl = process.env.LLM_BASE_URL ?? loadConfig().baseUrl;
12279
12420
  buildChats();
12280
- return { chat: rawChat, cheapChat, label: `${labelOf(provider)} \xB7 ${model}` };
12421
+ return { chat: rawChat, cheapChat, label: `${modelProviderLabel(model, provider)} \xB7 ${model}` };
12281
12422
  }
12282
12423
  async function main() {
12283
12424
  if (!apiKey && !process.env.AGENTOS_OFFLINE) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guangnao/agent-cli",
3
- "version": "1.1.5",
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": {