@vicvic-im/agent-bot 0.1.2 → 0.1.4

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.
@@ -8,7 +8,7 @@ import {
8
8
  loadConfig,
9
9
  normalizeConfig,
10
10
  saveConfig
11
- } from "./chunk-X5QSWCDH.js";
11
+ } from "./chunk-3FUDOPAV.js";
12
12
 
13
13
  // src/commands/setup.ts
14
14
  import { existsSync } from "fs";
@@ -89,4 +89,4 @@ async function ensureConfig(explicit) {
89
89
  export {
90
90
  ensureConfig
91
91
  };
92
- //# sourceMappingURL=chunk-6CK4XIJB.js.map
92
+ //# sourceMappingURL=chunk-X65ZARFF.js.map
package/dist/cli.cjs CHANGED
@@ -362,6 +362,9 @@ var acp = __toESM(require("@agentclientprotocol/sdk"), 1);
362
362
  // src/agents/types.ts
363
363
  var EMPTY_USAGE = { used: 0, size: 0, cost: null };
364
364
 
365
+ // src/version.ts
366
+ var VBOT_VERSION = "0.1.4";
367
+
365
368
  // src/agents/acp.ts
366
369
  var AgentAbortError = class extends Error {
367
370
  constructor() {
@@ -378,6 +381,14 @@ var AgentStartError = class extends Error {
378
381
  stderr;
379
382
  };
380
383
  var STDERR_LIMIT = 4e3;
384
+ function flattenSelectOptions(options) {
385
+ return options.flatMap(
386
+ (entry) => "group" in entry ? entry.options.map((option) => ({ value: option.value, label: option.name })) : [{ value: entry.value, label: entry.name }]
387
+ );
388
+ }
389
+ function isModelOption(option) {
390
+ return option.type === "select" && (option.category === "model" || option.id === "model");
391
+ }
381
392
  var AcpAgent = class {
382
393
  constructor(name, config) {
383
394
  this.name = name;
@@ -407,6 +418,14 @@ var AcpAgent = class {
407
418
  sessionId() {
408
419
  return this.session;
409
420
  }
421
+ currentModel() {
422
+ const option = this.configOptions.find(isModelOption);
423
+ if (!option || option.type !== "select") return null;
424
+ const value = String(option.currentValue);
425
+ const options = flattenSelectOptions(option.options);
426
+ const label = options.find((entry) => entry.value === value)?.label ?? value;
427
+ return { value, label, options };
428
+ }
410
429
  resetSession() {
411
430
  this.session = null;
412
431
  }
@@ -474,7 +493,7 @@ var AcpAgent = class {
474
493
  const init = await this.ctx.request(acp.methods.agent.initialize, {
475
494
  protocolVersion: 1,
476
495
  clientCapabilities: {},
477
- clientInfo: { name: "vbot", version: "0.1.0" }
496
+ clientInfo: { name: "vbot", version: VBOT_VERSION }
478
497
  });
479
498
  this.initCaps = init.agentCapabilities;
480
499
  } catch (error) {
@@ -593,15 +612,14 @@ var AcpAgent = class {
593
612
  this.pendingModel = model;
594
613
  return true;
595
614
  }
596
- const option = this.configOptions.find(
597
- (entry) => entry.type === "select" && (entry.id === "model" || entry.category === "model")
598
- );
615
+ const option = this.configOptions.find(isModelOption);
599
616
  if (!option) return false;
600
- await this.ctx.request(acp.methods.agent.session.setConfigOption, {
617
+ const response = await this.ctx.request(acp.methods.agent.session.setConfigOption, {
601
618
  sessionId: this.session,
602
619
  configId: option.id,
603
620
  value: model
604
621
  });
622
+ if (response?.configOptions?.length) this.configOptions = response.configOptions;
605
623
  this.pendingModel = null;
606
624
  return true;
607
625
  }
@@ -737,21 +755,29 @@ var BotQueue = class {
737
755
  }
738
756
  };
739
757
 
740
- // src/version.ts
741
- var VBOT_VERSION = "0.1.2";
742
-
743
758
  // src/core/dispatcher.ts
744
- var HELP_TEXT = `Available commands:
745
- /new \u2014 Start a new session
746
- /stop \u2014 Abort the current request
747
- /status \u2014 Show agent, session and queue status
748
- /agent [name] \u2014 Show or switch agent backend
749
- /model [name] \u2014 Show or switch model
750
- /cost \u2014 Show context usage and cost
751
- /version \u2014 Show vbot version
752
- /help \u2014 Show this help
753
-
754
- Other /commands are sent to the agent as prompts.`;
759
+ var import_node_os2 = require("os");
760
+ var HELP_ROWS = [
761
+ ["/new", "Start a new session"],
762
+ ["/stop", "Abort the current request"],
763
+ ["/status", "Show agent, session and queue status"],
764
+ ["/agent [name]", "Show or switch agent backend"],
765
+ ["/model [name]", "Show or switch model"],
766
+ ["/cost", "Show context usage and cost"],
767
+ ["/version", "Show vbot version"],
768
+ ["/help", "Show this help"]
769
+ ];
770
+ var HELP_FOOTER = "Other /commands are sent to the agent as prompts.";
771
+ var NEW_BUSY_TEXT = "Cannot reset the session while a request is running. Use /stop first.";
772
+ var MODEL_BUSY_TEXT = "Cannot switch model while a request is running. Use /stop first.";
773
+ var HELP_TEXT = [
774
+ table(
775
+ ["Command", "Description"],
776
+ HELP_ROWS.map(([command, description]) => [command, description])
777
+ ),
778
+ "",
779
+ HELP_FOOTER
780
+ ].join("\n");
755
781
  function createDispatcher(runtime) {
756
782
  return (text, reply) => {
757
783
  const send = (message) => {
@@ -765,11 +791,57 @@ function createDispatcher(runtime) {
765
791
  });
766
792
  };
767
793
  }
794
+ function escapeCell(value) {
795
+ return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
796
+ }
797
+ function table(headers, rows) {
798
+ const head = `| ${headers.map(escapeCell).join(" | ")} |`;
799
+ const separator = `| ${headers.map(() => "---").join(" | ")} |`;
800
+ const body = rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`);
801
+ return [head, separator, ...body].join("\n");
802
+ }
803
+ function formatNumber(value) {
804
+ if (!Number.isFinite(value)) return String(value);
805
+ const negative = value < 0;
806
+ const digits = Math.abs(Math.trunc(value)).toString();
807
+ let out = "";
808
+ for (let i = 0; i < digits.length; i += 1) {
809
+ if (i > 0 && (digits.length - i) % 3 === 0) out += ",";
810
+ out += digits[i];
811
+ }
812
+ return negative ? "-" + out : out;
813
+ }
814
+ function percentOf(used, size) {
815
+ if (size <= 0) return null;
816
+ return `${(used / size * 100).toFixed(1)}%`;
817
+ }
818
+ function usageText(runtime) {
819
+ const { used, size } = runtime.state().usage;
820
+ if (size <= 0) return "not reported";
821
+ const base = `${formatNumber(used)} / ${formatNumber(size)}`;
822
+ const percent = percentOf(used, size);
823
+ return percent ? `${base} (${percent})` : base;
824
+ }
825
+ function compactPath(path) {
826
+ const home = (0, import_node_os2.homedir)();
827
+ if (!home) return path;
828
+ if (path === home) return "~";
829
+ const prefix = home + "/";
830
+ if (path.startsWith(prefix)) return "~" + path.slice(home.length);
831
+ return path;
832
+ }
833
+ function modelText(runtime) {
834
+ const current = runtime.agent().currentModel();
835
+ if (current) return current.label;
836
+ const configured = runtime.config().agent.model;
837
+ if (configured) return `${configured} (configured, not applied yet)`;
838
+ return "unknown (session not started)";
839
+ }
768
840
  function handleSlashCommand(runtime, text, send) {
769
841
  const [rawCommand, ...args] = text.split(/\s+/);
770
842
  switch (rawCommand.toLowerCase()) {
771
843
  case "/new":
772
- handleNew(runtime, send);
844
+ void handleNew(runtime, send);
773
845
  return null;
774
846
  case "/stop":
775
847
  handleStop(runtime, send);
@@ -781,7 +853,7 @@ function handleSlashCommand(runtime, text, send) {
781
853
  handleAgent(runtime, args, send);
782
854
  return null;
783
855
  case "/model":
784
- handleModel(runtime, args, send);
856
+ void handleModel(runtime, args, send);
785
857
  return null;
786
858
  case "/cost":
787
859
  send(costText(runtime));
@@ -796,12 +868,18 @@ function handleSlashCommand(runtime, text, send) {
796
868
  return text.slice(1);
797
869
  }
798
870
  }
799
- function handleNew(runtime, send) {
800
- runtime.agent().resetSession();
801
- const state = runtime.state();
802
- state.acpSessionId = null;
803
- state.usage = { used: 0, size: 0, cost: null };
804
- runtime.saveState();
871
+ async function handleNew(runtime, send) {
872
+ if (runtime.queue().status().busy) {
873
+ send(NEW_BUSY_TEXT);
874
+ return;
875
+ }
876
+ await runtime.serialize(async () => {
877
+ runtime.agent().resetSession();
878
+ const state = runtime.state();
879
+ state.acpSessionId = null;
880
+ state.usage = { used: 0, size: 0, cost: null };
881
+ runtime.saveState();
882
+ });
805
883
  send("Session reset. Starting a new conversation.");
806
884
  }
807
885
  function handleStop(runtime, send) {
@@ -817,12 +895,18 @@ function handleStop(runtime, send) {
817
895
  }
818
896
  function handleAgent(runtime, args, send) {
819
897
  const name = args.join(" ").trim();
898
+ const current = runtime.config().agent.name;
820
899
  if (!name) {
821
- const current = runtime.config().agent.name;
822
- const list = AGENT_NAMES.map((entry) => `${entry === current ? "*" : " "} ${entry}`).join("\n");
823
- send(`Current agent: ${current}
824
- Available agents:
825
- ${list}`);
900
+ send(
901
+ table(
902
+ ["Agent", "Backend", ""],
903
+ AGENT_NAMES.map((entry) => [
904
+ entry,
905
+ AGENTS[entry].label,
906
+ entry === current ? "\u2190 current" : ""
907
+ ])
908
+ )
909
+ );
826
910
  return;
827
911
  }
828
912
  if (!isAgentName(name)) {
@@ -838,56 +922,95 @@ ${list}`);
838
922
  (error) => send(`Error: ${error instanceof Error ? error.message : String(error)}`)
839
923
  );
840
924
  }
841
- function handleModel(runtime, args, send) {
925
+ async function handleModel(runtime, args, send) {
842
926
  const model = args.join(" ").trim();
843
927
  if (!model) {
844
- send(`Current model: ${runtime.config().agent.model ?? "default"}`);
928
+ send(modelReport(runtime));
929
+ return;
930
+ }
931
+ if (runtime.queue().status().busy) {
932
+ send(MODEL_BUSY_TEXT);
845
933
  return;
846
934
  }
847
935
  const config = runtime.config();
848
936
  const hadSession = runtime.agent().sessionId() !== null;
849
- config.agent.model = model;
850
- runtime.saveConfig();
851
- runtime.agent().setModel(model).then((supported) => {
852
- if (!supported) {
853
- send(
854
- `Model saved: ${model}. This agent does not expose a runtime model option; it will be used on the next session if supported.`
855
- );
856
- return;
857
- }
937
+ const supported = await runtime.serialize(async () => {
938
+ const accepted = await runtime.agent().setModel(model);
939
+ config.agent.model = model;
940
+ runtime.saveConfig();
941
+ return accepted;
942
+ });
943
+ const applied = runtime.agent().currentModel()?.label ?? model;
944
+ if (!supported) {
858
945
  send(
859
- hadSession ? `Model switched to: ${model}` : `Model set to: ${model}. It will apply when the session starts.`
946
+ `Model saved: ${model}. This agent does not expose a runtime model option; it will be used on the next session if supported.`
860
947
  );
861
- }).catch(
862
- (error) => send(`Error: ${error instanceof Error ? error.message : String(error)}`)
948
+ return;
949
+ }
950
+ send(
951
+ hadSession ? `Model switched to: ${applied}` : `Model set to: ${applied}. It will apply when the session starts.`
863
952
  );
864
953
  }
954
+ function modelReport(runtime) {
955
+ const current = runtime.agent().currentModel();
956
+ if (!current) {
957
+ const configured = runtime.config().agent.model;
958
+ const hint = configured ? `Configured: ${configured}
959
+
960
+ 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.";
961
+ return `Current: unknown
962
+
963
+ ${hint}`;
964
+ }
965
+ const lines = [`Current: ${current.label}`];
966
+ if (current.options.length > 0) {
967
+ lines.push(
968
+ "",
969
+ table(
970
+ ["Model", ""],
971
+ current.options.map((option) => [
972
+ option.label,
973
+ option.value === current.value ? "\u2190 current" : ""
974
+ ])
975
+ )
976
+ );
977
+ }
978
+ lines.push("", "Switch: /model <name>");
979
+ return lines.join("\n");
980
+ }
865
981
  function statusText(runtime) {
866
982
  const config = runtime.config();
867
983
  const agent = runtime.agent();
868
984
  const state = runtime.state();
869
985
  const queue = runtime.queue().status();
870
- const lines = [
871
- `Agent: ${config.agent.name} (${AGENTS[config.agent.name].label})`,
872
- `Session: ${agent.sessionId() ?? "none"}`,
873
- `Work dir: ${config.agent.cwd}`,
874
- `Model: ${config.agent.model ?? "default"}`,
875
- `Status: ${queue.busy ? "busy" : "idle"}`,
876
- `Queue: ${queue.queued}`,
877
- `Usage: ${state.usage.used}/${state.usage.size} tokens`,
878
- `Capabilities: ${capabilitiesText(agent.capabilities())}`
986
+ const { used, size } = state.usage;
987
+ const summary = [
988
+ `**${config.agent.name}**`,
989
+ queue.busy ? "busy" : "idle",
990
+ size > 0 ? `${formatNumber(used)} / ${formatNumber(size)}` : null
991
+ ].filter(Boolean).join(" \xB7 ");
992
+ const rows = [
993
+ ["Agent", `${config.agent.name} (${AGENTS[config.agent.name].label})`],
994
+ ["Session", agent.sessionId() ?? "none"],
995
+ ["Work dir", compactPath(config.agent.cwd)],
996
+ ["Model", modelText(runtime)],
997
+ ["Status", queue.busy ? "busy" : "idle"],
998
+ ["Queue", String(queue.queued)],
999
+ ["Usage", usageText(runtime)],
1000
+ ["Capabilities", capabilitiesText(agent.capabilities())]
879
1001
  ];
880
- return lines.join("\n");
1002
+ return [summary, "", table(["Field", "Value"], rows)].join("\n");
881
1003
  }
882
1004
  function costText(runtime) {
883
1005
  const usage = runtime.state().usage;
884
- const context = `Context: ${usage.used}/${usage.size} tokens`;
885
- if (!usage.cost) {
886
- return `${context}
887
- Cost: this agent does not report cost.`;
888
- }
889
- return `${context}
890
- Cost: ${usage.cost.amount} ${usage.cost.currency}`;
1006
+ const cost = usage.cost ? `${usage.cost.amount} ${usage.cost.currency}` : "not reported by this agent";
1007
+ return table(
1008
+ ["Field", "Value"],
1009
+ [
1010
+ ["Context", usageText(runtime)],
1011
+ ["Cost", cost]
1012
+ ]
1013
+ );
891
1014
  }
892
1015
  function capabilitiesText(caps) {
893
1016
  const enabled = [
@@ -942,10 +1065,10 @@ var import_node_path5 = require("path");
942
1065
  // src/core/state.ts
943
1066
  var import_node_crypto = require("crypto");
944
1067
  var import_node_fs4 = require("fs");
945
- var import_node_os2 = require("os");
1068
+ var import_node_os3 = require("os");
946
1069
  var import_node_path4 = require("path");
947
1070
  function vbotHome() {
948
- return process.env.VBOT_HOME?.trim() || (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".vbot");
1071
+ return process.env.VBOT_HOME?.trim() || (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".vbot");
949
1072
  }
950
1073
  function stateDir() {
951
1074
  return (0, import_node_path4.join)(vbotHome(), "state");
@@ -1274,13 +1397,17 @@ async function startBot(configPath) {
1274
1397
  saveConfig: () => saveConfig(config, configPath)
1275
1398
  };
1276
1399
  holder.queue = new BotQueue((item, signal) => gate(() => processPrompt(ctx, item, signal)));
1277
- const runtime = createRuntime(ctx, (name) => {
1278
- if (switching) return Promise.reject(new Error("Agent switch already in progress."));
1279
- switching = true;
1280
- return gate(() => switchAgent(ctx, name)).finally(() => {
1281
- switching = false;
1282
- });
1283
- });
1400
+ const runtime = createRuntime(
1401
+ ctx,
1402
+ (task) => gate(task),
1403
+ (name) => {
1404
+ if (switching) return Promise.reject(new Error("Agent switch already in progress."));
1405
+ switching = true;
1406
+ return gate(() => switchAgent(ctx, name)).finally(() => {
1407
+ switching = false;
1408
+ });
1409
+ }
1410
+ );
1284
1411
  const dispatch = createDispatcher(runtime);
1285
1412
  let notifySignal = () => void 0;
1286
1413
  const wire = new VicvicWire({
@@ -1341,7 +1468,7 @@ async function runBot(configPath) {
1341
1468
  const bot = await startBot(configPath);
1342
1469
  await bot.finished;
1343
1470
  }
1344
- function createRuntime(ctx, switchAgent2) {
1471
+ function createRuntime(ctx, gate, switchAgent2) {
1345
1472
  return {
1346
1473
  config: () => ctx.config,
1347
1474
  agent: () => ctx.getAgent(),
@@ -1349,6 +1476,7 @@ function createRuntime(ctx, switchAgent2) {
1349
1476
  state: () => ctx.state,
1350
1477
  saveState: () => ctx.saveState(),
1351
1478
  saveConfig: () => ctx.saveConfig(),
1479
+ serialize: (task) => gate(task),
1352
1480
  switchAgent: switchAgent2
1353
1481
  };
1354
1482
  }