@vicvic-im/agent-bot 0.1.1 → 0.1.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.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/core/cli-path.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { execFileSync } from \"node:child_process\";\nimport { basename, resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { ensureConfig } from \"./commands/setup.js\";\nimport { findConfigPath, loadConfig } from \"./config.js\";\nimport { readPackageVersion, resolveServerEntry } from \"./core/cli-path.js\";\nimport { runBot } from \"./server.js\";\n\ninterface GlobalOptions {\n config?: string;\n}\n\nfunction processName(cwd: string): string {\n return `vbot-${basename(resolve(cwd)).replace(/[^a-zA-Z0-9._-]+/g, \"-\")}`;\n}\n\n// pm2 是可选的外部工具。通过固定的 /usr/bin/env 调用,避免把任意 PATH 条目当作可执行文件;\n// Windows 一期不承诺后台模式,检测失败时退回前台运行。\nconst ENV_BIN = \"/usr/bin/env\";\n\nfunction hasPm2(): boolean {\n try {\n execFileSync(ENV_BIN, [\"pm2\", \"--version\"], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction pm2(args: string[], capture = false): string {\n return execFileSync(ENV_BIN, [\"pm2\", ...args], {\n stdio: capture ? \"pipe\" : \"inherit\",\n encoding: \"utf8\",\n });\n}\n\nfunction resolveName(configPath?: string): string {\n const path = findConfigPath(configPath);\n if (!path) return \"vbot\";\n try {\n const { config } = loadConfig(path);\n return processName(config.agent.cwd);\n } catch {\n return \"vbot\";\n }\n}\n\nfunction cliPath(): string {\n const path = process.argv[1];\n if (!path) throw new Error(\"Cannot resolve vbot entry; run the installed binary.\");\n return path;\n}\n\nconst program = new Command();\nprogram\n .name(\"vbot\")\n .description(\"Connect local coding agents to vicvic.im\")\n .version(readPackageVersion(cliPath()));\n\nprogram\n .command(\"start\")\n .description(\"Start vbot (prompts for config on first run)\")\n .option(\"--foreground\", \"run in the foreground instead of pm2\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action(async (options: GlobalOptions & { foreground?: boolean }) => {\n const { path } = await ensureConfig(options.config);\n if (options.foreground || !hasPm2()) {\n if (!options.foreground) {\n console.log(\n pc.yellow(\"pm2 not found; running in the foreground. Install pm2 for background mode.\")\n );\n }\n await runBot(path);\n return;\n }\n const name = resolveName(path);\n try {\n pm2([\"describe\", name], true);\n pm2([\"restart\", name, \"--update-env\"]);\n } catch {\n pm2([\n \"start\",\n resolveServerEntry(cliPath()),\n \"--name\",\n name,\n \"--stop-exit-codes\",\n \"2\",\n \"--log-date-format\",\n \"YYYY-MM-DD HH:mm:ss\",\n \"--\",\n path,\n ]);\n }\n pm2([\"save\"], true);\n console.log(pc.green(`✔ ${name} started. Use \"vbot logs\" to view output.`));\n });\n\nprogram\n .command(\"stop\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"stop\", resolveName(options.config)]);\n } catch {\n console.error(pc.red(\"Failed to stop vbot. Is it running?\"));\n }\n });\n\nprogram\n .command(\"restart\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"restart\", resolveName(options.config), \"--update-env\"]);\n } catch {\n console.error(pc.red('Failed to restart vbot. Try \"vbot start\".'));\n }\n });\n\nprogram\n .command(\"status\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"describe\", resolveName(options.config)]);\n } catch {\n console.log(pc.yellow(\"vbot is not running.\"));\n }\n });\n\nprogram\n .command(\"logs\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"logs\", resolveName(options.config), \"--lines\", \"50\"]);\n } catch {\n console.error(\"Failed to show logs. Is pm2 installed?\");\n }\n });\n\nprogram\n .command(\"delete\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"delete\", resolveName(options.config)]);\n } catch {\n console.error(\"Failed to delete vbot process.\");\n }\n });\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n console.error(pc.red(error instanceof Error ? error.message : String(error)));\n process.exitCode = 1;\n});\n","import { existsSync, readFileSync, realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * 全局安装时 argv[1] 是 bin 目录的符号链接(如 ~/.nvm/.../bin/vbot),\n * 必须 realpath 后定位包内同目录的 server.js;直接 dirname(argv[1]) 会指向 bin 目录。\n */\nexport function resolveServerEntry(cliPath: string): string {\n const real = realpathSync(cliPath);\n const candidate = join(dirname(real), \"server.js\");\n if (!existsSync(candidate)) {\n throw new Error(`Server bundle not found at ${candidate}. Reinstall @vicvic-im/agent-bot.`);\n }\n return candidate;\n}\n\n/** 从真实 dist/cli.js 位置向上读取包版本,避免符号链接导致读错 package.json。 */\nexport function readPackageVersion(cliPath: string): string {\n try {\n const raw = readFileSync(join(dirname(realpathSync(cliPath)), \"..\", \"package.json\"), \"utf8\");\n return (JSON.parse(raw) as { version?: string }).version ?? \"0.1.0\";\n } catch {\n return \"0.1.0\";\n }\n}\n"],"mappings":"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","names":["cliPath"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/core/cli-path.ts","../src/core/pm2.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { execFileSync } from \"node:child_process\";\nimport { basename, resolve } from \"node:path\";\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { ensureConfig } from \"./commands/setup.js\";\nimport { findConfigPath, loadConfig } from \"./config.js\";\nimport { readPackageVersion, resolveServerEntry } from \"./core/cli-path.js\";\nimport { ensurePm2, hasPm2 } from \"./core/pm2.js\";\nimport { runBot } from \"./server.js\";\n\ninterface GlobalOptions {\n config?: string;\n}\n\nfunction processName(cwd: string): string {\n return `vbot-${basename(resolve(cwd)).replace(/[^a-zA-Z0-9._-]+/g, \"-\")}`;\n}\n\n// pm2 是可选的外部工具。通过固定的 /usr/bin/env 调用,避免把任意 PATH 条目当作可执行文件;\n// Windows 一期不承诺后台模式,检测失败时退回前台运行。\nconst ENV_BIN = \"/usr/bin/env\";\n\nfunction pm2(args: string[], capture = false): string {\n return execFileSync(ENV_BIN, [\"pm2\", ...args], {\n stdio: capture ? \"pipe\" : \"inherit\",\n encoding: \"utf8\",\n });\n}\n\nfunction resolveName(configPath?: string): string {\n const path = findConfigPath(configPath);\n if (!path) return \"vbot\";\n try {\n const { config } = loadConfig(path);\n return processName(config.agent.cwd);\n } catch {\n return \"vbot\";\n }\n}\n\nfunction cliPath(): string {\n const path = process.argv[1];\n if (!path) throw new Error(\"Cannot resolve vbot entry; run the installed binary.\");\n return path;\n}\n\nconst program = new Command();\nprogram\n .name(\"vbot\")\n .description(\"Connect local coding agents to vicvic.im\")\n .version(readPackageVersion(cliPath()));\n\nprogram\n .command(\"start\")\n .description(\"Start vbot (prompts for config on first run)\")\n .option(\"--foreground\", \"run in the foreground instead of pm2\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action(async (options: GlobalOptions & { foreground?: boolean }) => {\n const { path } = await ensureConfig(options.config);\n if (options.foreground) {\n await runBot(path);\n return;\n }\n if (!hasPm2()) console.log(pc.yellow(\"pm2 not found, installing globally...\"));\n if (!ensurePm2()) {\n console.error(pc.red(\"Failed to install pm2. Please install manually: npm i -g pm2\"));\n console.log(pc.yellow(\"Running in the foreground instead.\"));\n await runBot(path);\n return;\n }\n const name = resolveName(path);\n try {\n pm2([\"describe\", name], true);\n pm2([\"restart\", name, \"--update-env\"]);\n } catch {\n pm2([\n \"start\",\n resolveServerEntry(cliPath()),\n \"--name\",\n name,\n \"--stop-exit-codes\",\n \"2\",\n \"--log-date-format\",\n \"YYYY-MM-DD HH:mm:ss\",\n \"--\",\n path,\n ]);\n }\n pm2([\"save\"], true);\n console.log(pc.green(`✔ ${name} started. Use \"vbot logs\" to view output.`));\n });\n\nprogram\n .command(\"stop\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"stop\", resolveName(options.config)]);\n } catch {\n console.error(pc.red(\"Failed to stop vbot. Is it running?\"));\n }\n });\n\nprogram\n .command(\"restart\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"restart\", resolveName(options.config), \"--update-env\"]);\n } catch {\n console.error(pc.red('Failed to restart vbot. Try \"vbot start\".'));\n }\n });\n\nprogram\n .command(\"status\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"describe\", resolveName(options.config)]);\n } catch {\n console.log(pc.yellow(\"vbot is not running.\"));\n }\n });\n\nprogram\n .command(\"logs\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"logs\", resolveName(options.config), \"--lines\", \"50\"]);\n } catch {\n console.error(\"Failed to show logs. Is pm2 installed?\");\n }\n });\n\nprogram\n .command(\"delete\")\n .option(\"-c, --config <path>\", \"config file path\")\n .action((options: GlobalOptions) => {\n try {\n pm2([\"delete\", resolveName(options.config)]);\n } catch {\n console.error(\"Failed to delete vbot process.\");\n }\n });\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n console.error(pc.red(error instanceof Error ? error.message : String(error)));\n process.exitCode = 1;\n});\n","import { existsSync, readFileSync, realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * 全局安装时 argv[1] 是 bin 目录的符号链接(如 ~/.nvm/.../bin/vbot),\n * 必须 realpath 后定位包内同目录的 server.js;直接 dirname(argv[1]) 会指向 bin 目录。\n */\nexport function resolveServerEntry(cliPath: string): string {\n const real = realpathSync(cliPath);\n const candidate = join(dirname(real), \"bin-server.js\");\n if (!existsSync(candidate)) {\n throw new Error(`Server bundle not found at ${candidate}. Reinstall @vicvic-im/agent-bot.`);\n }\n return candidate;\n}\n\n/** 从真实 dist/cli.js 位置向上读取包版本,避免符号链接导致读错 package.json。 */\nexport function readPackageVersion(cliPath: string): string {\n try {\n const raw = readFileSync(join(dirname(realpathSync(cliPath)), \"..\", \"package.json\"), \"utf8\");\n return (JSON.parse(raw) as { version?: string }).version ?? \"0.1.0\";\n } catch {\n return \"0.1.0\";\n }\n}\n","import { execFileSync } from \"node:child_process\";\n\n// pm2 是可选的外部工具。通过固定的 /usr/bin/env 调用,避免把任意 PATH 条目当作可执行文件。\nconst ENV_BIN = \"/usr/bin/env\";\n\ntype Exec = typeof execFileSync;\n\nexport function hasPm2(exec: Exec = execFileSync): boolean {\n try {\n exec(ENV_BIN, [\"pm2\", \"--version\"], { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/** 与 ccbot 一致:pm2 缺失时自动全局安装;安装失败返回 false,由调用方退前台。 */\nexport function ensurePm2(exec: Exec = execFileSync): boolean {\n if (hasPm2(exec)) return true;\n try {\n exec(ENV_BIN, [\"npm\", \"i\", \"-g\", \"pm2\"], { stdio: \"inherit\" });\n return hasPm2(exec);\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["execFileSync","cliPath","ENV_BIN","execFileSync"]}
@@ -2,7 +2,7 @@ import type { AgentBackend, AgentName } from "../agents/types.js";
2
2
  import type { VbotConfig } from "../config.js";
3
3
  import type { BotQueue } from "./queue.js";
4
4
  import type { VbotState } from "./state.js";
5
- export declare const HELP_TEXT = "Available commands:\n/new \u2014 Start a new session\n/stop \u2014 Abort the current request\n/status \u2014 Show agent, session and queue status\n/agent [name] \u2014 Show or switch agent backend\n/model [name] \u2014 Show or switch model\n/cost \u2014 Show context usage and cost\n/version \u2014 Show vbot version\n/help \u2014 Show this help\n\nOther /commands are sent to the agent as prompts.";
5
+ export declare const HELP_TEXT: string;
6
6
  export interface Runtime {
7
7
  config(): VbotConfig;
8
8
  agent(): AgentBackend;
@@ -10,6 +10,8 @@ export interface Runtime {
10
10
  state(): VbotState;
11
11
  saveState(): void;
12
12
  saveConfig(): void;
13
+ /** 串行化对 agent 内部状态的写操作(复用 server 的 gate,避免与进行中的 turn 竞争)。 */
14
+ serialize<T>(task: () => Promise<T>): Promise<T>;
13
15
  /** 切换后端;返回给用户的提示。失败时抛错。 */
14
16
  switchAgent(name: AgentName): Promise<string>;
15
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAqB,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,eAAO,MAAM,SAAS,mZAU4B,CAAC;AAEnD,MAAM,WAAW,OAAO;IACtB,MAAM,IAAI,UAAU,CAAC;IACrB,KAAK,IAAI,YAAY,CAAC;IACtB,KAAK,IAAI,QAAQ,CAAC;IAClB,KAAK,IAAI,SAAS,CAAC;IACnB,SAAS,IAAI,IAAI,CAAC;IAClB,UAAU,IAAI,IAAI,CAAC;IACnB,2BAA2B;IAC3B,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/C;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAItF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAiB3D"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAqB,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAoB5C,eAAO,MAAM,SAAS,QAOV,CAAC;AAEb,MAAM,WAAW,OAAO;IACtB,MAAM,IAAI,UAAU,CAAC;IACrB,KAAK,IAAI,YAAY,CAAC;IACtB,KAAK,IAAI,QAAQ,CAAC;IAClB,KAAK,IAAI,SAAS,CAAC;IACnB,SAAS,IAAI,IAAI,CAAC;IAClB,UAAU,IAAI,IAAI,CAAC;IACnB,6DAA6D;IAC7D,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACjD,2BAA2B;IAC3B,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/C;AAED,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAItF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAiB3D"}
@@ -0,0 +1,7 @@
1
+ import { execFileSync } from "node:child_process";
2
+ type Exec = typeof execFileSync;
3
+ export declare function hasPm2(exec?: Exec): boolean;
4
+ /** 与 ccbot 一致:pm2 缺失时自动全局安装;安装失败返回 false,由调用方退前台。 */
5
+ export declare function ensurePm2(exec?: Exec): boolean;
6
+ export {};
7
+ //# sourceMappingURL=pm2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pm2.d.ts","sourceRoot":"","sources":["../../src/core/pm2.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKlD,KAAK,IAAI,GAAG,OAAO,YAAY,CAAC;AAEhC,wBAAgB,MAAM,CAAC,IAAI,GAAE,IAAmB,GAAG,OAAO,CAOzD;AAED,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,IAAI,GAAE,IAAmB,GAAG,OAAO,CAQ5D"}
package/dist/index.cjs CHANGED
@@ -175,11 +175,11 @@ function parseEnv(value) {
175
175
  if (value === void 0 || value === null) return void 0;
176
176
  const record = asRecord(value, "agent.env");
177
177
  const env = {};
178
- for (const [key, entry2] of Object.entries(record)) {
179
- if (typeof entry2 !== "string") {
178
+ for (const [key, entry] of Object.entries(record)) {
179
+ if (typeof entry !== "string") {
180
180
  throw new ConfigError(`Invalid "agent.env.${key}": expected a string`);
181
181
  }
182
- env[key] = entry2;
182
+ env[key] = entry;
183
183
  }
184
184
  return env;
185
185
  }
@@ -223,7 +223,7 @@ function parseAgent(root, configDir) {
223
223
  return {
224
224
  name: agentName,
225
225
  command: optionalString(agentRaw.command, "agent.command"),
226
- args: Array.isArray(argsRaw) ? argsRaw.map((entry2) => String(entry2)) : void 0,
226
+ args: Array.isArray(argsRaw) ? argsRaw.map((entry) => String(entry)) : void 0,
227
227
  cwd,
228
228
  model: optionalString(agentRaw.model, "agent.model") ?? null,
229
229
  env: parseEnv(agentRaw.env)
@@ -360,6 +360,9 @@ var acp = __toESM(require("@agentclientprotocol/sdk"), 1);
360
360
  // src/agents/types.ts
361
361
  var EMPTY_USAGE = { used: 0, size: 0, cost: null };
362
362
 
363
+ // src/version.ts
364
+ var VBOT_VERSION = "0.1.3";
365
+
363
366
  // src/agents/acp.ts
364
367
  var AgentAbortError = class extends Error {
365
368
  constructor() {
@@ -376,6 +379,14 @@ var AgentStartError = class extends Error {
376
379
  stderr;
377
380
  };
378
381
  var STDERR_LIMIT = 4e3;
382
+ function flattenSelectOptions(options) {
383
+ return options.flatMap(
384
+ (entry) => "group" in entry ? entry.options.map((option) => ({ value: option.value, label: option.name })) : [{ value: entry.value, label: entry.name }]
385
+ );
386
+ }
387
+ function isModelOption(option) {
388
+ return option.type === "select" && (option.category === "model" || option.id === "model");
389
+ }
379
390
  var AcpAgent = class {
380
391
  constructor(name, config) {
381
392
  this.name = name;
@@ -405,6 +416,14 @@ var AcpAgent = class {
405
416
  sessionId() {
406
417
  return this.session;
407
418
  }
419
+ currentModel() {
420
+ const option = this.configOptions.find(isModelOption);
421
+ if (!option || option.type !== "select") return null;
422
+ const value = String(option.currentValue);
423
+ const options = flattenSelectOptions(option.options);
424
+ const label = options.find((entry) => entry.value === value)?.label ?? value;
425
+ return { value, label, options };
426
+ }
408
427
  resetSession() {
409
428
  this.session = null;
410
429
  }
@@ -472,7 +491,7 @@ var AcpAgent = class {
472
491
  const init = await this.ctx.request(acp.methods.agent.initialize, {
473
492
  protocolVersion: 1,
474
493
  clientCapabilities: {},
475
- clientInfo: { name: "vbot", version: "0.1.0" }
494
+ clientInfo: { name: "vbot", version: VBOT_VERSION }
476
495
  });
477
496
  this.initCaps = init.agentCapabilities;
478
497
  } catch (error) {
@@ -559,7 +578,7 @@ var AcpAgent = class {
559
578
  );
560
579
  pending.catch(() => void 0);
561
580
  const response = await Promise.race([pending, aborted]);
562
- await new Promise((resolve4) => setImmediate(resolve4));
581
+ await new Promise((resolve3) => setImmediate(resolve3));
563
582
  if (response.stopReason === "cancelled" || request.signal.aborted) {
564
583
  throw new AgentAbortError();
565
584
  }
@@ -591,15 +610,14 @@ var AcpAgent = class {
591
610
  this.pendingModel = model;
592
611
  return true;
593
612
  }
594
- const option = this.configOptions.find(
595
- (entry2) => entry2.type === "select" && (entry2.id === "model" || entry2.category === "model")
596
- );
613
+ const option = this.configOptions.find(isModelOption);
597
614
  if (!option) return false;
598
- await this.ctx.request(acp.methods.agent.session.setConfigOption, {
615
+ const response = await this.ctx.request(acp.methods.agent.session.setConfigOption, {
599
616
  sessionId: this.session,
600
617
  configId: option.id,
601
618
  value: model
602
619
  });
620
+ if (response?.configOptions?.length) this.configOptions = response.configOptions;
603
621
  this.pendingModel = null;
604
622
  return true;
605
623
  }
@@ -618,8 +636,8 @@ var AcpAgent = class {
618
636
  } catch {
619
637
  }
620
638
  if (!child || child.exitCode !== null) return;
621
- const exited = new Promise((resolve4) => {
622
- child.once("exit", () => resolve4());
639
+ const exited = new Promise((resolve3) => {
640
+ child.once("exit", () => resolve3());
623
641
  });
624
642
  child.kill("SIGTERM");
625
643
  const timer = setTimeout(() => child.kill("SIGKILL"), 3e3);
@@ -735,21 +753,29 @@ var BotQueue = class {
735
753
  }
736
754
  };
737
755
 
738
- // src/version.ts
739
- var VBOT_VERSION = "0.1.1";
740
-
741
756
  // src/core/dispatcher.ts
742
- var HELP_TEXT = `Available commands:
743
- /new \u2014 Start a new session
744
- /stop \u2014 Abort the current request
745
- /status \u2014 Show agent, session and queue status
746
- /agent [name] \u2014 Show or switch agent backend
747
- /model [name] \u2014 Show or switch model
748
- /cost \u2014 Show context usage and cost
749
- /version \u2014 Show vbot version
750
- /help \u2014 Show this help
751
-
752
- Other /commands are sent to the agent as prompts.`;
757
+ var import_node_os2 = require("os");
758
+ var HELP_ROWS = [
759
+ ["/new", "Start a new session"],
760
+ ["/stop", "Abort the current request"],
761
+ ["/status", "Show agent, session and queue status"],
762
+ ["/agent [name]", "Show or switch agent backend"],
763
+ ["/model [name]", "Show or switch model"],
764
+ ["/cost", "Show context usage and cost"],
765
+ ["/version", "Show vbot version"],
766
+ ["/help", "Show this help"]
767
+ ];
768
+ var HELP_FOOTER = "Other /commands are sent to the agent as prompts.";
769
+ var NEW_BUSY_TEXT = "Cannot reset the session while a request is running. Use /stop first.";
770
+ var MODEL_BUSY_TEXT = "Cannot switch model while a request is running. Use /stop first.";
771
+ var HELP_TEXT = [
772
+ table(
773
+ ["Command", "Description"],
774
+ HELP_ROWS.map(([command, description]) => [command, description])
775
+ ),
776
+ "",
777
+ HELP_FOOTER
778
+ ].join("\n");
753
779
  function createDispatcher(runtime) {
754
780
  return (text, reply) => {
755
781
  const send = (message) => {
@@ -763,11 +789,57 @@ function createDispatcher(runtime) {
763
789
  });
764
790
  };
765
791
  }
792
+ function escapeCell(value) {
793
+ return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
794
+ }
795
+ function table(headers, rows) {
796
+ const head = `| ${headers.map(escapeCell).join(" | ")} |`;
797
+ const separator = `| ${headers.map(() => "---").join(" | ")} |`;
798
+ const body = rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`);
799
+ return [head, separator, ...body].join("\n");
800
+ }
801
+ function formatNumber(value) {
802
+ if (!Number.isFinite(value)) return String(value);
803
+ const negative = value < 0;
804
+ const digits = Math.abs(Math.trunc(value)).toString();
805
+ let out = "";
806
+ for (let i = 0; i < digits.length; i += 1) {
807
+ if (i > 0 && (digits.length - i) % 3 === 0) out += ",";
808
+ out += digits[i];
809
+ }
810
+ return negative ? "-" + out : out;
811
+ }
812
+ function percentOf(used, size) {
813
+ if (size <= 0) return null;
814
+ return `${(used / size * 100).toFixed(1)}%`;
815
+ }
816
+ function usageText(runtime) {
817
+ const { used, size } = runtime.state().usage;
818
+ if (size <= 0) return "not reported";
819
+ const base = `${formatNumber(used)} / ${formatNumber(size)}`;
820
+ const percent = percentOf(used, size);
821
+ return percent ? `${base} (${percent})` : base;
822
+ }
823
+ function compactPath(path) {
824
+ const home = (0, import_node_os2.homedir)();
825
+ if (!home) return path;
826
+ if (path === home) return "~";
827
+ const prefix = home + "/";
828
+ if (path.startsWith(prefix)) return "~" + path.slice(home.length);
829
+ return path;
830
+ }
831
+ function modelText(runtime) {
832
+ const current = runtime.agent().currentModel();
833
+ if (current) return current.label;
834
+ const configured = runtime.config().agent.model;
835
+ if (configured) return `${configured} (configured, not applied yet)`;
836
+ return "unknown (session not started)";
837
+ }
766
838
  function handleSlashCommand(runtime, text, send) {
767
839
  const [rawCommand, ...args] = text.split(/\s+/);
768
840
  switch (rawCommand.toLowerCase()) {
769
841
  case "/new":
770
- handleNew(runtime, send);
842
+ void handleNew(runtime, send);
771
843
  return null;
772
844
  case "/stop":
773
845
  handleStop(runtime, send);
@@ -779,7 +851,7 @@ function handleSlashCommand(runtime, text, send) {
779
851
  handleAgent(runtime, args, send);
780
852
  return null;
781
853
  case "/model":
782
- handleModel(runtime, args, send);
854
+ void handleModel(runtime, args, send);
783
855
  return null;
784
856
  case "/cost":
785
857
  send(costText(runtime));
@@ -794,12 +866,18 @@ function handleSlashCommand(runtime, text, send) {
794
866
  return text.slice(1);
795
867
  }
796
868
  }
797
- function handleNew(runtime, send) {
798
- runtime.agent().resetSession();
799
- const state = runtime.state();
800
- state.acpSessionId = null;
801
- state.usage = { used: 0, size: 0, cost: null };
802
- runtime.saveState();
869
+ async function handleNew(runtime, send) {
870
+ if (runtime.queue().status().busy) {
871
+ send(NEW_BUSY_TEXT);
872
+ return;
873
+ }
874
+ await runtime.serialize(async () => {
875
+ runtime.agent().resetSession();
876
+ const state = runtime.state();
877
+ state.acpSessionId = null;
878
+ state.usage = { used: 0, size: 0, cost: null };
879
+ runtime.saveState();
880
+ });
803
881
  send("Session reset. Starting a new conversation.");
804
882
  }
805
883
  function handleStop(runtime, send) {
@@ -815,12 +893,18 @@ function handleStop(runtime, send) {
815
893
  }
816
894
  function handleAgent(runtime, args, send) {
817
895
  const name = args.join(" ").trim();
896
+ const current = runtime.config().agent.name;
818
897
  if (!name) {
819
- const current = runtime.config().agent.name;
820
- const list = AGENT_NAMES.map((entry2) => `${entry2 === current ? "*" : " "} ${entry2}`).join("\n");
821
- send(`Current agent: ${current}
822
- Available agents:
823
- ${list}`);
898
+ send(
899
+ table(
900
+ ["Agent", "Backend", ""],
901
+ AGENT_NAMES.map((entry) => [
902
+ entry,
903
+ AGENTS[entry].label,
904
+ entry === current ? "\u2190 current" : ""
905
+ ])
906
+ )
907
+ );
824
908
  return;
825
909
  }
826
910
  if (!isAgentName(name)) {
@@ -836,56 +920,95 @@ ${list}`);
836
920
  (error) => send(`Error: ${error instanceof Error ? error.message : String(error)}`)
837
921
  );
838
922
  }
839
- function handleModel(runtime, args, send) {
923
+ async function handleModel(runtime, args, send) {
840
924
  const model = args.join(" ").trim();
841
925
  if (!model) {
842
- send(`Current model: ${runtime.config().agent.model ?? "default"}`);
926
+ send(modelReport(runtime));
927
+ return;
928
+ }
929
+ if (runtime.queue().status().busy) {
930
+ send(MODEL_BUSY_TEXT);
843
931
  return;
844
932
  }
845
933
  const config = runtime.config();
846
934
  const hadSession = runtime.agent().sessionId() !== null;
847
- config.agent.model = model;
848
- runtime.saveConfig();
849
- runtime.agent().setModel(model).then((supported) => {
850
- if (!supported) {
851
- send(
852
- `Model saved: ${model}. This agent does not expose a runtime model option; it will be used on the next session if supported.`
853
- );
854
- return;
855
- }
935
+ const supported = await runtime.serialize(async () => {
936
+ const accepted = await runtime.agent().setModel(model);
937
+ config.agent.model = model;
938
+ runtime.saveConfig();
939
+ return accepted;
940
+ });
941
+ const applied = runtime.agent().currentModel()?.label ?? model;
942
+ if (!supported) {
856
943
  send(
857
- hadSession ? `Model switched to: ${model}` : `Model set to: ${model}. It will apply when the session starts.`
944
+ `Model saved: ${model}. This agent does not expose a runtime model option; it will be used on the next session if supported.`
858
945
  );
859
- }).catch(
860
- (error) => send(`Error: ${error instanceof Error ? error.message : String(error)}`)
946
+ return;
947
+ }
948
+ send(
949
+ hadSession ? `Model switched to: ${applied}` : `Model set to: ${applied}. It will apply when the session starts.`
861
950
  );
862
951
  }
952
+ function modelReport(runtime) {
953
+ const current = runtime.agent().currentModel();
954
+ if (!current) {
955
+ const configured = runtime.config().agent.model;
956
+ const hint = configured ? `Configured: ${configured}
957
+
958
+ 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.";
959
+ return `Current: unknown
960
+
961
+ ${hint}`;
962
+ }
963
+ const lines = [`Current: ${current.label}`];
964
+ if (current.options.length > 0) {
965
+ lines.push(
966
+ "",
967
+ table(
968
+ ["Model", ""],
969
+ current.options.map((option) => [
970
+ option.label,
971
+ option.value === current.value ? "\u2190 current" : ""
972
+ ])
973
+ )
974
+ );
975
+ }
976
+ lines.push("", "Switch: /model <name>");
977
+ return lines.join("\n");
978
+ }
863
979
  function statusText(runtime) {
864
980
  const config = runtime.config();
865
981
  const agent = runtime.agent();
866
982
  const state = runtime.state();
867
983
  const queue = runtime.queue().status();
868
- const lines = [
869
- `Agent: ${config.agent.name} (${AGENTS[config.agent.name].label})`,
870
- `Session: ${agent.sessionId() ?? "none"}`,
871
- `Work dir: ${config.agent.cwd}`,
872
- `Model: ${config.agent.model ?? "default"}`,
873
- `Status: ${queue.busy ? "busy" : "idle"}`,
874
- `Queue: ${queue.queued}`,
875
- `Usage: ${state.usage.used}/${state.usage.size} tokens`,
876
- `Capabilities: ${capabilitiesText(agent.capabilities())}`
984
+ const { used, size } = state.usage;
985
+ const summary = [
986
+ `**${config.agent.name}**`,
987
+ queue.busy ? "busy" : "idle",
988
+ size > 0 ? `${formatNumber(used)} / ${formatNumber(size)}` : null
989
+ ].filter(Boolean).join(" \xB7 ");
990
+ const rows = [
991
+ ["Agent", `${config.agent.name} (${AGENTS[config.agent.name].label})`],
992
+ ["Session", agent.sessionId() ?? "none"],
993
+ ["Work dir", compactPath(config.agent.cwd)],
994
+ ["Model", modelText(runtime)],
995
+ ["Status", queue.busy ? "busy" : "idle"],
996
+ ["Queue", String(queue.queued)],
997
+ ["Usage", usageText(runtime)],
998
+ ["Capabilities", capabilitiesText(agent.capabilities())]
877
999
  ];
878
- return lines.join("\n");
1000
+ return [summary, "", table(["Field", "Value"], rows)].join("\n");
879
1001
  }
880
1002
  function costText(runtime) {
881
1003
  const usage = runtime.state().usage;
882
- const context = `Context: ${usage.used}/${usage.size} tokens`;
883
- if (!usage.cost) {
884
- return `${context}
885
- Cost: this agent does not report cost.`;
886
- }
887
- return `${context}
888
- Cost: ${usage.cost.amount} ${usage.cost.currency}`;
1004
+ const cost = usage.cost ? `${usage.cost.amount} ${usage.cost.currency}` : "not reported by this agent";
1005
+ return table(
1006
+ ["Field", "Value"],
1007
+ [
1008
+ ["Context", usageText(runtime)],
1009
+ ["Cost", cost]
1010
+ ]
1011
+ );
889
1012
  }
890
1013
  function capabilitiesText(caps) {
891
1014
  const enabled = [
@@ -904,10 +1027,10 @@ var import_node_path4 = require("path");
904
1027
  // src/core/state.ts
905
1028
  var import_node_crypto = require("crypto");
906
1029
  var import_node_fs3 = require("fs");
907
- var import_node_os2 = require("os");
1030
+ var import_node_os3 = require("os");
908
1031
  var import_node_path3 = require("path");
909
1032
  function vbotHome() {
910
- return process.env.VBOT_HOME?.trim() || (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".vbot");
1033
+ return process.env.VBOT_HOME?.trim() || (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".vbot");
911
1034
  }
912
1035
  function stateDir() {
913
1036
  return (0, import_node_path3.join)(vbotHome(), "state");
@@ -1074,10 +1197,10 @@ var VicvicWire = class {
1074
1197
  if (!socket || socket.readyState !== import_ws.default.OPEN) {
1075
1198
  throw new Error("Bot connection is not open");
1076
1199
  }
1077
- await new Promise((resolve4, reject) => {
1200
+ await new Promise((resolve3, reject) => {
1078
1201
  socket.send(JSON.stringify({ type: "reply", msgUid, text }), (error) => {
1079
1202
  if (error) reject(error);
1080
- else resolve4();
1203
+ else resolve3();
1081
1204
  });
1082
1205
  });
1083
1206
  }
@@ -1201,9 +1324,6 @@ function splitMessage(text, max = MAX_MESSAGE_CHARS) {
1201
1324
  return chunks;
1202
1325
  }
1203
1326
 
1204
- // src/server.ts
1205
- var import_node_path5 = require("path");
1206
-
1207
1327
  // src/core/gate.ts
1208
1328
  function createGate() {
1209
1329
  let tail = Promise.resolve();
@@ -1284,13 +1404,17 @@ async function startBot(configPath) {
1284
1404
  saveConfig: () => saveConfig(config, configPath)
1285
1405
  };
1286
1406
  holder.queue = new BotQueue((item, signal) => gate(() => processPrompt(ctx, item, signal)));
1287
- const runtime = createRuntime(ctx, (name) => {
1288
- if (switching) return Promise.reject(new Error("Agent switch already in progress."));
1289
- switching = true;
1290
- return gate(() => switchAgent(ctx, name)).finally(() => {
1291
- switching = false;
1292
- });
1293
- });
1407
+ const runtime = createRuntime(
1408
+ ctx,
1409
+ (task) => gate(task),
1410
+ (name) => {
1411
+ if (switching) return Promise.reject(new Error("Agent switch already in progress."));
1412
+ switching = true;
1413
+ return gate(() => switchAgent(ctx, name)).finally(() => {
1414
+ switching = false;
1415
+ });
1416
+ }
1417
+ );
1294
1418
  const dispatch = createDispatcher(runtime);
1295
1419
  let notifySignal = () => void 0;
1296
1420
  const wire = new VicvicWire({
@@ -1351,7 +1475,7 @@ async function runBot(configPath) {
1351
1475
  const bot = await startBot(configPath);
1352
1476
  await bot.finished;
1353
1477
  }
1354
- function createRuntime(ctx, switchAgent2) {
1478
+ function createRuntime(ctx, gate, switchAgent2) {
1355
1479
  return {
1356
1480
  config: () => ctx.config,
1357
1481
  agent: () => ctx.getAgent(),
@@ -1359,6 +1483,7 @@ function createRuntime(ctx, switchAgent2) {
1359
1483
  state: () => ctx.state,
1360
1484
  saveState: () => ctx.saveState(),
1361
1485
  saveConfig: () => ctx.saveConfig(),
1486
+ serialize: (task) => gate(task),
1362
1487
  switchAgent: switchAgent2
1363
1488
  };
1364
1489
  }
@@ -1410,19 +1535,6 @@ ${error.stderr}`);
1410
1535
  console.error("[server] agent run failed:", error);
1411
1536
  replySafe(reply, `Error: ${error instanceof Error ? error.message : String(error)}`);
1412
1537
  }
1413
- var entry = process.argv[1] ?? "";
1414
- if (/(^|[\\/])server\.(js|cjs|ts)$/.test(entry)) {
1415
- const configPath = process.argv[2];
1416
- if (!configPath) {
1417
- console.error("Usage: server.js <config-path>");
1418
- process.exit(1);
1419
- }
1420
- runBot((0, import_node_path5.resolve)(configPath)).catch((error) => {
1421
- console.error(error instanceof Error ? error.message : String(error));
1422
- const expected = error instanceof ConfigError || error instanceof LockError;
1423
- process.exit(expected ? 2 : 1);
1424
- });
1425
- }
1426
1538
  // Annotate the CommonJS export names for ESM import in node:
1427
1539
  0 && (module.exports = {
1428
1540
  AGENTS,