@vicvic-im/agent-bot 0.1.4 → 0.1.5

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/README.md CHANGED
@@ -83,8 +83,9 @@ Unknown `/commands` are forwarded to the agent as prompts.
83
83
  Output notes:
84
84
 
85
85
  - `/status`, `/agent`, `/model`, `/cost` and `/help` render Markdown tables; the vicvic.im Web and RN clients already render GFM tables. `/status` starts with a one-line summary because conversation previews and push notifications only show the first line.
86
- - `/model` with no argument reports the model the ACP session actually selected (`currentValue` mapped to its option name) and lists the available options. It shows `unknown (session not started)` before the first message instead of a misleading `default`.
87
- - `/new` and `/model <name>` are rejected while a request is running (`Use /stop first`): resetting the session mid-turn drops the streamed reply and switches the model under a running turn.
86
+ - `/model` with no argument reports the model the ACP session actually selected (`currentValue` mapped to its option name), never a misleading `default`. Resolution order: live session → cached value from the previous run (`last known`, survives `vbot restart` and `/new`) → start the agent once to read it. `pi` warms up in ~3 s; `codex` cold start can take ~100 s, so vbot replies `Reading model from codex...` first.
87
+ - `/model list` prints the full option table (pi exposes 46 models); plain `/model` inlines options only when there are 8 or fewer. `/status` never starts the agent — it shows the cached value marked `(last known)` when no session is live.
88
+ - `/new` and `/model <name>` are rejected while a request is running (`Use /stop first`): resetting the session mid-turn drops the streamed reply and switches the model under a running turn. `/new` does not reset the model.
88
89
 
89
90
  ## Configuration
90
91
 
@@ -137,6 +138,11 @@ The pairing token is a long-lived credential. Do not commit `vbot.json`; the fil
137
138
 
138
139
  ## Development
139
140
 
141
+ Release: bump `package.json` **and** `src/version.ts` (`VBOT_VERSION`), build, then publish from this
142
+ folder (`npx -y npm@11 publish --access public`). `npm publish` returns before the version is
143
+ fetchable — poll `npm view @vicvic-im/agent-bot version` until it matches, and do not republish the
144
+ same version (409 staged). See `docs/techs/current/npm-package-release.md`.
145
+
140
146
  ```bash
141
147
  pnpm --filter @vicvic-im/agent-bot build
142
148
  pnpm --filter @vicvic-im/agent-bot test
@@ -26,6 +26,7 @@ export declare class AcpAgent implements AgentBackend {
26
26
  capabilities(): AgentCapabilities;
27
27
  sessionId(): string | null;
28
28
  currentModel(): AgentModelState | null;
29
+ warmup(savedSessionId?: string): Promise<void>;
29
30
  resetSession(): void;
30
31
  start(): Promise<void>;
31
32
  private doStart;
@@ -1 +1 @@
1
- {"version":3,"file":"acp.d.ts","sourceRoot":"","sources":["../../src/agents/acp.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EAEjB,eAAe,EACf,SAAS,EACT,eAAe,EACf,cAAc,EAEf,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,qBAAa,eAAgB,SAAQ,KAAK;;CAKzC;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAGtC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM;CAK1B;AAyBD,0CAA0C;AAC1C,qBAAa,QAAS,YAAW,YAAY;IAczC,QAAQ,CAAC,IAAI,EAAE,SAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAdzB,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,UAAU,CAAqC;IACvD,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAuB;gBAGhC,IAAI,EAAE,SAAS,EACP,MAAM,EAAE,WAAW;IAGtC,YAAY,IAAI,iBAAiB;IASjC,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B,YAAY,IAAI,eAAe,GAAG,IAAI;IAStC,YAAY,IAAI,IAAI;IAId,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,OAAO;YA8EP,aAAa;IA+CrB,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAkEtD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IASvB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,UAAU;CAWnB"}
1
+ {"version":3,"file":"acp.d.ts","sourceRoot":"","sources":["../../src/agents/acp.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EAEjB,eAAe,EACf,SAAS,EACT,eAAe,EACf,cAAc,EAEf,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,qBAAa,eAAgB,SAAQ,KAAK;;CAKzC;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAGtC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADvB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM;CAK1B;AAyBD,0CAA0C;AAC1C,qBAAa,QAAS,YAAW,YAAY;IAczC,QAAQ,CAAC,IAAI,EAAE,SAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAdzB,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,UAAU,CAAqC;IACvD,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAuB;gBAGhC,IAAI,EAAE,SAAS,EACP,MAAM,EAAE,WAAW;IAGtC,YAAY,IAAI,iBAAiB;IASjC,SAAS,IAAI,MAAM,GAAG,IAAI;IAI1B,YAAY,IAAI,eAAe,GAAG,IAAI;IAUhC,MAAM,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpD,YAAY,IAAI,IAAI;IAId,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YASd,OAAO;YA8EP,aAAa;IA+CrB,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC;IAkEtD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IASvB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAmBzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,cAAc;IAyBtB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,UAAU;CAWnB"}
@@ -84,5 +84,10 @@ export interface AgentBackend {
84
84
  sessionId(): string | null;
85
85
  /** 当前模型;后端未暴露模型配置项或会话未建立时返回 null。 */
86
86
  currentModel(): AgentModelState | null;
87
+ /**
88
+ * 启动进程并建立/恢复会话(不跑 prompt),用于在未收到消息时读取真实模型。
89
+ * 失败时抛错,由调用方决定如何降级。
90
+ */
91
+ warmup(savedSessionId?: string): Promise<void>;
87
92
  }
88
93
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,WAAW,EAAE,KAAwC,CAAC;AAEnE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,2CAA2C;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sCAAsC;IACtC,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACvD,mBAAmB;IACnB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,sCAAsC;IACtC,YAAY,IAAI,IAAI,CAAC;IACrB,kBAAkB;IAClB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,YAAY,IAAI,iBAAiB,CAAC;IAClC,+BAA+B;IAC/B,SAAS,IAAI,MAAM,GAAG,IAAI,CAAC;IAC3B,qCAAqC;IACrC,YAAY,IAAI,eAAe,GAAG,IAAI,CAAC;CACxC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,KAAK;IACpB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,WAAW,EAAE,KAAwC,CAAC;AAEnE,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,mCAAmC;IACnC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,2CAA2C;IAC3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sCAAsC;IACtC,GAAG,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACvD,mBAAmB;IACnB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,sCAAsC;IACtC,YAAY,IAAI,IAAI,CAAC;IACrB,kBAAkB;IAClB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,YAAY,IAAI,iBAAiB,CAAC;IAClC,+BAA+B;IAC/B,SAAS,IAAI,MAAM,GAAG,IAAI,CAAC;IAC3B,qCAAqC;IACrC,YAAY,IAAI,eAAe,GAAG,IAAI,CAAC;IACvC;;;OAGG;IACH,MAAM,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD"}
@@ -266,9 +266,16 @@ function emptyState(token, agent, cwd) {
266
266
  cwd,
267
267
  createdAt: now,
268
268
  lastUsedAt: now,
269
- usage: { ...EMPTY_USAGE }
269
+ usage: { ...EMPTY_USAGE },
270
+ model: null
270
271
  };
271
272
  }
273
+ function parseCachedModel(value) {
274
+ if (!value || typeof value !== "object") return null;
275
+ const record = value;
276
+ if (typeof record.value !== "string" || typeof record.label !== "string") return null;
277
+ return { value: record.value, label: record.label };
278
+ }
272
279
  function loadState(token) {
273
280
  const path = statePath(token);
274
281
  if (!(0, import_node_fs2.existsSync)(path)) return null;
@@ -283,7 +290,8 @@ function loadState(token) {
283
290
  cwd: typeof parsed.cwd === "string" ? parsed.cwd : process.cwd(),
284
291
  createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
285
292
  lastUsedAt: typeof parsed.lastUsedAt === "string" ? parsed.lastUsedAt : (/* @__PURE__ */ new Date()).toISOString(),
286
- usage: parsed.usage && typeof parsed.usage === "object" ? { ...EMPTY_USAGE, ...parsed.usage } : { ...EMPTY_USAGE }
293
+ usage: parsed.usage && typeof parsed.usage === "object" ? { ...EMPTY_USAGE, ...parsed.usage } : { ...EMPTY_USAGE },
294
+ model: parseCachedModel(parsed.model)
287
295
  };
288
296
  } catch {
289
297
  return null;
@@ -365,7 +373,7 @@ var import_node_stream = require("stream");
365
373
  var acp = __toESM(require("@agentclientprotocol/sdk"), 1);
366
374
 
367
375
  // src/version.ts
368
- var VBOT_VERSION = "0.1.4";
376
+ var VBOT_VERSION = "0.1.5";
369
377
 
370
378
  // src/agents/acp.ts
371
379
  var AgentAbortError = class extends Error {
@@ -428,6 +436,11 @@ var AcpAgent = class {
428
436
  const label = options.find((entry) => entry.value === value)?.label ?? value;
429
437
  return { value, label, options };
430
438
  }
439
+ // 不跑 prompt 也能读模型:session/new|resume|load 的 configOptions 里带 model 项。
440
+ async warmup(savedSessionId) {
441
+ await this.start();
442
+ await this.ensureSession(savedSessionId);
443
+ }
431
444
  resetSession() {
432
445
  this.session = null;
433
446
  }
@@ -764,7 +777,7 @@ var HELP_ROWS = [
764
777
  ["/stop", "Abort the current request"],
765
778
  ["/status", "Show agent, session and queue status"],
766
779
  ["/agent [name]", "Show or switch agent backend"],
767
- ["/model [name]", "Show or switch model"],
780
+ ["/model [name]", "Show or switch model (/model list for all)"],
768
781
  ["/cost", "Show context usage and cost"],
769
782
  ["/version", "Show vbot version"],
770
783
  ["/help", "Show this help"]
@@ -772,6 +785,7 @@ var HELP_ROWS = [
772
785
  var HELP_FOOTER = "Other /commands are sent to the agent as prompts.";
773
786
  var NEW_BUSY_TEXT = "Cannot reset the session while a request is running. Use /stop first.";
774
787
  var MODEL_BUSY_TEXT = "Cannot switch model while a request is running. Use /stop first.";
788
+ var INLINE_OPTION_LIMIT = 8;
775
789
  var HELP_TEXT = [
776
790
  table(
777
791
  ["Command", "Description"],
@@ -835,9 +849,11 @@ function compactPath(path) {
835
849
  function modelText(runtime) {
836
850
  const current = runtime.agent().currentModel();
837
851
  if (current) return current.label;
852
+ const cached = runtime.state().model;
853
+ if (cached) return `${cached.label} (last known)`;
838
854
  const configured = runtime.config().agent.model;
839
855
  if (configured) return `${configured} (configured, not applied yet)`;
840
- return "unknown (session not started)";
856
+ return "unknown (send /model list)";
841
857
  }
842
858
  function handleSlashCommand(runtime, text, send) {
843
859
  const [rawCommand, ...args] = text.split(/\s+/);
@@ -927,7 +943,11 @@ function handleAgent(runtime, args, send) {
927
943
  async function handleModel(runtime, args, send) {
928
944
  const model = args.join(" ").trim();
929
945
  if (!model) {
930
- send(modelReport(runtime));
946
+ await reportModel(runtime, send, false);
947
+ return;
948
+ }
949
+ if (model.toLowerCase() === "list") {
950
+ await reportModel(runtime, send, true);
931
951
  return;
932
952
  }
933
953
  if (runtime.queue().status().busy) {
@@ -942,7 +962,12 @@ async function handleModel(runtime, args, send) {
942
962
  runtime.saveConfig();
943
963
  return accepted;
944
964
  });
945
- const applied = runtime.agent().currentModel()?.label ?? model;
965
+ const applied = runtime.agent().currentModel();
966
+ if (applied) {
967
+ runtime.state().model = { value: applied.value, label: applied.label };
968
+ runtime.saveState();
969
+ }
970
+ const label = applied?.label ?? model;
946
971
  if (!supported) {
947
972
  send(
948
973
  `Model saved: ${model}. This agent does not expose a runtime model option; it will be used on the next session if supported.`
@@ -950,34 +975,83 @@ async function handleModel(runtime, args, send) {
950
975
  return;
951
976
  }
952
977
  send(
953
- hadSession ? `Model switched to: ${applied}` : `Model set to: ${applied}. It will apply when the session starts.`
978
+ hadSession ? `Model switched to: ${label}` : `Model set to: ${label}. It will apply when the session starts.`
954
979
  );
955
980
  }
956
- function modelReport(runtime) {
957
- const current = runtime.agent().currentModel();
958
- if (!current) {
959
- const configured = runtime.config().agent.model;
960
- const hint = configured ? `Configured: ${configured}
961
-
962
- The agent has not reported a model yet; it becomes visible after the first message.` : "This agent does not expose a runtime model option, or the session has not started.";
963
- return `Current: unknown
964
-
965
- ${hint}`;
981
+ async function reportModel(runtime, send, wantList) {
982
+ const live = runtime.agent().currentModel();
983
+ if (live) {
984
+ send(renderModel(live, wantList));
985
+ return;
986
+ }
987
+ const cached = runtime.state().model;
988
+ if (cached && !wantList) {
989
+ send(renderCachedModel(cached));
990
+ return;
991
+ }
992
+ const name = runtime.config().agent.name;
993
+ send(`Reading ${wantList ? "model list" : "model"} from ${name}... (starting the agent)`);
994
+ try {
995
+ await runtime.serialize(
996
+ () => runtime.agent().warmup(runtime.state().acpSessionId ?? void 0)
997
+ );
998
+ } catch (error) {
999
+ const reason = error instanceof Error ? error.message : String(error);
1000
+ send(`Could not read the model from ${name}: ${reason}`);
1001
+ if (cached) send(renderCachedModel(cached));
1002
+ return;
966
1003
  }
967
- const lines = [`Current: ${current.label}`];
968
- if (current.options.length > 0) {
1004
+ const refreshed = runtime.agent().currentModel();
1005
+ if (refreshed) {
1006
+ runtime.state().model = { value: refreshed.value, label: refreshed.label };
1007
+ runtime.saveState();
1008
+ send(renderModel(refreshed, wantList));
1009
+ return;
1010
+ }
1011
+ send(cached ? renderCachedModel(cached) : unknownModelText(runtime));
1012
+ }
1013
+ function renderModel(model, wantList) {
1014
+ const lines = [`Current: ${model.label}`];
1015
+ const total = model.options.length;
1016
+ if (total === 0) {
1017
+ lines.push("", "This agent does not expose a model list.");
1018
+ return lines.join("\n");
1019
+ }
1020
+ if (wantList || total <= INLINE_OPTION_LIMIT) {
969
1021
  lines.push(
970
1022
  "",
971
1023
  table(
972
1024
  ["Model", ""],
973
- current.options.map((option) => [
1025
+ model.options.map((option) => [
974
1026
  option.label,
975
- option.value === current.value ? "\u2190 current" : ""
1027
+ option.value === model.value ? "\u2190 current" : ""
976
1028
  ])
977
1029
  )
978
1030
  );
979
- }
980
- lines.push("", "Switch: /model <name>");
1031
+ lines.push("", "Switch: /model <name>");
1032
+ return lines.join("\n");
1033
+ }
1034
+ lines.push(
1035
+ "",
1036
+ table(["Model", ""], [[model.label, "\u2190 current"]]),
1037
+ "",
1038
+ `${total} models available \xB7 Switch: /model <name> \xB7 List: /model list`
1039
+ );
1040
+ return lines.join("\n");
1041
+ }
1042
+ function renderCachedModel(cached) {
1043
+ return [
1044
+ `Current: ${cached.label} (last known)`,
1045
+ "",
1046
+ "Cached from the last session; the agent has not started in this process yet.",
1047
+ "Send /model list to read the live value from the agent."
1048
+ ].join("\n");
1049
+ }
1050
+ function unknownModelText(runtime) {
1051
+ const configured = runtime.config().agent.model;
1052
+ const lines = ["Current: unknown"];
1053
+ if (configured) lines.push("", `Configured: ${configured}`);
1054
+ lines.push("", "This agent does not expose a runtime model option.");
981
1055
  return lines.join("\n");
982
1056
  }
983
1057
  function statusText(runtime) {
@@ -1054,6 +1128,7 @@ async function switchAgent(ctx, name) {
1054
1128
  ctx.state.agent = name;
1055
1129
  ctx.state.acpSessionId = null;
1056
1130
  ctx.state.usage = { ...EMPTY_USAGE };
1131
+ ctx.state.model = null;
1057
1132
  ctx.saveConfig();
1058
1133
  ctx.saveState();
1059
1134
  await previous.close().catch(() => void 0);
@@ -1380,6 +1455,8 @@ async function processPrompt(ctx, item, signal) {
1380
1455
  });
1381
1456
  ctx.state.acpSessionId = result.sessionId;
1382
1457
  ctx.state.usage = result.usage;
1458
+ const model = ctx.getAgent().currentModel();
1459
+ if (model) ctx.state.model = { value: model.value, label: model.label };
1383
1460
  ctx.saveState();
1384
1461
  for (const chunk of splitMessage(result.text || "(no output)")) {
1385
1462
  await item.reply(chunk);