@vicvic-im/agent-bot 0.1.2 → 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/README.md +8 -2
- package/dist/agents/acp.d.ts +2 -1
- package/dist/agents/acp.d.ts.map +1 -1
- package/dist/agents/types.d.ts +16 -0
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/bin-server.cjs +201 -71
- package/dist/bin-server.cjs.map +1 -1
- package/dist/bin-server.js +1 -1
- package/dist/{chunk-6CK4XIJB.js → chunk-CDBYOHVL.js} +2 -2
- package/dist/{chunk-X5QSWCDH.js → chunk-ELLWQDOY.js} +202 -74
- package/dist/chunk-ELLWQDOY.js.map +1 -0
- package/dist/cli.cjs +201 -73
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/core/dispatcher.d.ts +3 -1
- package/dist/core/dispatcher.d.ts.map +1 -1
- package/dist/index.cjs +201 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/server.cjs +201 -73
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +10 -10
- package/dist/chunk-X5QSWCDH.js.map +0 -1
- /package/dist/{chunk-6CK4XIJB.js.map → chunk-CDBYOHVL.js.map} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -249,6 +249,9 @@ var acp = __toESM(require("@agentclientprotocol/sdk"), 1);
|
|
|
249
249
|
// src/agents/types.ts
|
|
250
250
|
var EMPTY_USAGE = { used: 0, size: 0, cost: null };
|
|
251
251
|
|
|
252
|
+
// src/version.ts
|
|
253
|
+
var VBOT_VERSION = "0.1.3";
|
|
254
|
+
|
|
252
255
|
// src/agents/acp.ts
|
|
253
256
|
var AgentAbortError = class extends Error {
|
|
254
257
|
constructor() {
|
|
@@ -265,6 +268,14 @@ var AgentStartError = class extends Error {
|
|
|
265
268
|
stderr;
|
|
266
269
|
};
|
|
267
270
|
var STDERR_LIMIT = 4e3;
|
|
271
|
+
function flattenSelectOptions(options) {
|
|
272
|
+
return options.flatMap(
|
|
273
|
+
(entry) => "group" in entry ? entry.options.map((option) => ({ value: option.value, label: option.name })) : [{ value: entry.value, label: entry.name }]
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
function isModelOption(option) {
|
|
277
|
+
return option.type === "select" && (option.category === "model" || option.id === "model");
|
|
278
|
+
}
|
|
268
279
|
var AcpAgent = class {
|
|
269
280
|
constructor(name, config) {
|
|
270
281
|
this.name = name;
|
|
@@ -294,6 +305,14 @@ var AcpAgent = class {
|
|
|
294
305
|
sessionId() {
|
|
295
306
|
return this.session;
|
|
296
307
|
}
|
|
308
|
+
currentModel() {
|
|
309
|
+
const option = this.configOptions.find(isModelOption);
|
|
310
|
+
if (!option || option.type !== "select") return null;
|
|
311
|
+
const value = String(option.currentValue);
|
|
312
|
+
const options = flattenSelectOptions(option.options);
|
|
313
|
+
const label = options.find((entry) => entry.value === value)?.label ?? value;
|
|
314
|
+
return { value, label, options };
|
|
315
|
+
}
|
|
297
316
|
resetSession() {
|
|
298
317
|
this.session = null;
|
|
299
318
|
}
|
|
@@ -361,7 +380,7 @@ var AcpAgent = class {
|
|
|
361
380
|
const init = await this.ctx.request(acp.methods.agent.initialize, {
|
|
362
381
|
protocolVersion: 1,
|
|
363
382
|
clientCapabilities: {},
|
|
364
|
-
clientInfo: { name: "vbot", version:
|
|
383
|
+
clientInfo: { name: "vbot", version: VBOT_VERSION }
|
|
365
384
|
});
|
|
366
385
|
this.initCaps = init.agentCapabilities;
|
|
367
386
|
} catch (error) {
|
|
@@ -480,15 +499,14 @@ var AcpAgent = class {
|
|
|
480
499
|
this.pendingModel = model;
|
|
481
500
|
return true;
|
|
482
501
|
}
|
|
483
|
-
const option = this.configOptions.find(
|
|
484
|
-
(entry) => entry.type === "select" && (entry.id === "model" || entry.category === "model")
|
|
485
|
-
);
|
|
502
|
+
const option = this.configOptions.find(isModelOption);
|
|
486
503
|
if (!option) return false;
|
|
487
|
-
await this.ctx.request(acp.methods.agent.session.setConfigOption, {
|
|
504
|
+
const response = await this.ctx.request(acp.methods.agent.session.setConfigOption, {
|
|
488
505
|
sessionId: this.session,
|
|
489
506
|
configId: option.id,
|
|
490
507
|
value: model
|
|
491
508
|
});
|
|
509
|
+
if (response?.configOptions?.length) this.configOptions = response.configOptions;
|
|
492
510
|
this.pendingModel = null;
|
|
493
511
|
return true;
|
|
494
512
|
}
|
|
@@ -624,21 +642,29 @@ var BotQueue = class {
|
|
|
624
642
|
}
|
|
625
643
|
};
|
|
626
644
|
|
|
627
|
-
// src/version.ts
|
|
628
|
-
var VBOT_VERSION = "0.1.2";
|
|
629
|
-
|
|
630
645
|
// src/core/dispatcher.ts
|
|
631
|
-
var
|
|
632
|
-
|
|
633
|
-
/
|
|
634
|
-
/
|
|
635
|
-
/
|
|
636
|
-
/
|
|
637
|
-
/
|
|
638
|
-
/
|
|
639
|
-
/
|
|
640
|
-
|
|
641
|
-
|
|
646
|
+
var import_node_os2 = require("os");
|
|
647
|
+
var HELP_ROWS = [
|
|
648
|
+
["/new", "Start a new session"],
|
|
649
|
+
["/stop", "Abort the current request"],
|
|
650
|
+
["/status", "Show agent, session and queue status"],
|
|
651
|
+
["/agent [name]", "Show or switch agent backend"],
|
|
652
|
+
["/model [name]", "Show or switch model"],
|
|
653
|
+
["/cost", "Show context usage and cost"],
|
|
654
|
+
["/version", "Show vbot version"],
|
|
655
|
+
["/help", "Show this help"]
|
|
656
|
+
];
|
|
657
|
+
var HELP_FOOTER = "Other /commands are sent to the agent as prompts.";
|
|
658
|
+
var NEW_BUSY_TEXT = "Cannot reset the session while a request is running. Use /stop first.";
|
|
659
|
+
var MODEL_BUSY_TEXT = "Cannot switch model while a request is running. Use /stop first.";
|
|
660
|
+
var HELP_TEXT = [
|
|
661
|
+
table(
|
|
662
|
+
["Command", "Description"],
|
|
663
|
+
HELP_ROWS.map(([command, description]) => [command, description])
|
|
664
|
+
),
|
|
665
|
+
"",
|
|
666
|
+
HELP_FOOTER
|
|
667
|
+
].join("\n");
|
|
642
668
|
function createDispatcher(runtime) {
|
|
643
669
|
return (text, reply) => {
|
|
644
670
|
const send = (message) => {
|
|
@@ -652,11 +678,57 @@ function createDispatcher(runtime) {
|
|
|
652
678
|
});
|
|
653
679
|
};
|
|
654
680
|
}
|
|
681
|
+
function escapeCell(value) {
|
|
682
|
+
return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
683
|
+
}
|
|
684
|
+
function table(headers, rows) {
|
|
685
|
+
const head = `| ${headers.map(escapeCell).join(" | ")} |`;
|
|
686
|
+
const separator = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
687
|
+
const body = rows.map((row) => `| ${row.map(escapeCell).join(" | ")} |`);
|
|
688
|
+
return [head, separator, ...body].join("\n");
|
|
689
|
+
}
|
|
690
|
+
function formatNumber(value) {
|
|
691
|
+
if (!Number.isFinite(value)) return String(value);
|
|
692
|
+
const negative = value < 0;
|
|
693
|
+
const digits = Math.abs(Math.trunc(value)).toString();
|
|
694
|
+
let out = "";
|
|
695
|
+
for (let i = 0; i < digits.length; i += 1) {
|
|
696
|
+
if (i > 0 && (digits.length - i) % 3 === 0) out += ",";
|
|
697
|
+
out += digits[i];
|
|
698
|
+
}
|
|
699
|
+
return negative ? "-" + out : out;
|
|
700
|
+
}
|
|
701
|
+
function percentOf(used, size) {
|
|
702
|
+
if (size <= 0) return null;
|
|
703
|
+
return `${(used / size * 100).toFixed(1)}%`;
|
|
704
|
+
}
|
|
705
|
+
function usageText(runtime) {
|
|
706
|
+
const { used, size } = runtime.state().usage;
|
|
707
|
+
if (size <= 0) return "not reported";
|
|
708
|
+
const base = `${formatNumber(used)} / ${formatNumber(size)}`;
|
|
709
|
+
const percent = percentOf(used, size);
|
|
710
|
+
return percent ? `${base} (${percent})` : base;
|
|
711
|
+
}
|
|
712
|
+
function compactPath(path) {
|
|
713
|
+
const home = (0, import_node_os2.homedir)();
|
|
714
|
+
if (!home) return path;
|
|
715
|
+
if (path === home) return "~";
|
|
716
|
+
const prefix = home + "/";
|
|
717
|
+
if (path.startsWith(prefix)) return "~" + path.slice(home.length);
|
|
718
|
+
return path;
|
|
719
|
+
}
|
|
720
|
+
function modelText(runtime) {
|
|
721
|
+
const current = runtime.agent().currentModel();
|
|
722
|
+
if (current) return current.label;
|
|
723
|
+
const configured = runtime.config().agent.model;
|
|
724
|
+
if (configured) return `${configured} (configured, not applied yet)`;
|
|
725
|
+
return "unknown (session not started)";
|
|
726
|
+
}
|
|
655
727
|
function handleSlashCommand(runtime, text, send) {
|
|
656
728
|
const [rawCommand, ...args] = text.split(/\s+/);
|
|
657
729
|
switch (rawCommand.toLowerCase()) {
|
|
658
730
|
case "/new":
|
|
659
|
-
handleNew(runtime, send);
|
|
731
|
+
void handleNew(runtime, send);
|
|
660
732
|
return null;
|
|
661
733
|
case "/stop":
|
|
662
734
|
handleStop(runtime, send);
|
|
@@ -668,7 +740,7 @@ function handleSlashCommand(runtime, text, send) {
|
|
|
668
740
|
handleAgent(runtime, args, send);
|
|
669
741
|
return null;
|
|
670
742
|
case "/model":
|
|
671
|
-
handleModel(runtime, args, send);
|
|
743
|
+
void handleModel(runtime, args, send);
|
|
672
744
|
return null;
|
|
673
745
|
case "/cost":
|
|
674
746
|
send(costText(runtime));
|
|
@@ -683,12 +755,18 @@ function handleSlashCommand(runtime, text, send) {
|
|
|
683
755
|
return text.slice(1);
|
|
684
756
|
}
|
|
685
757
|
}
|
|
686
|
-
function handleNew(runtime, send) {
|
|
687
|
-
runtime.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
runtime.
|
|
758
|
+
async function handleNew(runtime, send) {
|
|
759
|
+
if (runtime.queue().status().busy) {
|
|
760
|
+
send(NEW_BUSY_TEXT);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
await runtime.serialize(async () => {
|
|
764
|
+
runtime.agent().resetSession();
|
|
765
|
+
const state = runtime.state();
|
|
766
|
+
state.acpSessionId = null;
|
|
767
|
+
state.usage = { used: 0, size: 0, cost: null };
|
|
768
|
+
runtime.saveState();
|
|
769
|
+
});
|
|
692
770
|
send("Session reset. Starting a new conversation.");
|
|
693
771
|
}
|
|
694
772
|
function handleStop(runtime, send) {
|
|
@@ -704,12 +782,18 @@ function handleStop(runtime, send) {
|
|
|
704
782
|
}
|
|
705
783
|
function handleAgent(runtime, args, send) {
|
|
706
784
|
const name = args.join(" ").trim();
|
|
785
|
+
const current = runtime.config().agent.name;
|
|
707
786
|
if (!name) {
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
787
|
+
send(
|
|
788
|
+
table(
|
|
789
|
+
["Agent", "Backend", ""],
|
|
790
|
+
AGENT_NAMES.map((entry) => [
|
|
791
|
+
entry,
|
|
792
|
+
AGENTS[entry].label,
|
|
793
|
+
entry === current ? "\u2190 current" : ""
|
|
794
|
+
])
|
|
795
|
+
)
|
|
796
|
+
);
|
|
713
797
|
return;
|
|
714
798
|
}
|
|
715
799
|
if (!isAgentName(name)) {
|
|
@@ -725,56 +809,95 @@ ${list}`);
|
|
|
725
809
|
(error) => send(`Error: ${error instanceof Error ? error.message : String(error)}`)
|
|
726
810
|
);
|
|
727
811
|
}
|
|
728
|
-
function handleModel(runtime, args, send) {
|
|
812
|
+
async function handleModel(runtime, args, send) {
|
|
729
813
|
const model = args.join(" ").trim();
|
|
730
814
|
if (!model) {
|
|
731
|
-
send(
|
|
815
|
+
send(modelReport(runtime));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (runtime.queue().status().busy) {
|
|
819
|
+
send(MODEL_BUSY_TEXT);
|
|
732
820
|
return;
|
|
733
821
|
}
|
|
734
822
|
const config = runtime.config();
|
|
735
823
|
const hadSession = runtime.agent().sessionId() !== null;
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
}
|
|
824
|
+
const supported = await runtime.serialize(async () => {
|
|
825
|
+
const accepted = await runtime.agent().setModel(model);
|
|
826
|
+
config.agent.model = model;
|
|
827
|
+
runtime.saveConfig();
|
|
828
|
+
return accepted;
|
|
829
|
+
});
|
|
830
|
+
const applied = runtime.agent().currentModel()?.label ?? model;
|
|
831
|
+
if (!supported) {
|
|
745
832
|
send(
|
|
746
|
-
|
|
833
|
+
`Model saved: ${model}. This agent does not expose a runtime model option; it will be used on the next session if supported.`
|
|
747
834
|
);
|
|
748
|
-
|
|
749
|
-
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
send(
|
|
838
|
+
hadSession ? `Model switched to: ${applied}` : `Model set to: ${applied}. It will apply when the session starts.`
|
|
750
839
|
);
|
|
751
840
|
}
|
|
841
|
+
function modelReport(runtime) {
|
|
842
|
+
const current = runtime.agent().currentModel();
|
|
843
|
+
if (!current) {
|
|
844
|
+
const configured = runtime.config().agent.model;
|
|
845
|
+
const hint = configured ? `Configured: ${configured}
|
|
846
|
+
|
|
847
|
+
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.";
|
|
848
|
+
return `Current: unknown
|
|
849
|
+
|
|
850
|
+
${hint}`;
|
|
851
|
+
}
|
|
852
|
+
const lines = [`Current: ${current.label}`];
|
|
853
|
+
if (current.options.length > 0) {
|
|
854
|
+
lines.push(
|
|
855
|
+
"",
|
|
856
|
+
table(
|
|
857
|
+
["Model", ""],
|
|
858
|
+
current.options.map((option) => [
|
|
859
|
+
option.label,
|
|
860
|
+
option.value === current.value ? "\u2190 current" : ""
|
|
861
|
+
])
|
|
862
|
+
)
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
lines.push("", "Switch: /model <name>");
|
|
866
|
+
return lines.join("\n");
|
|
867
|
+
}
|
|
752
868
|
function statusText(runtime) {
|
|
753
869
|
const config = runtime.config();
|
|
754
870
|
const agent = runtime.agent();
|
|
755
871
|
const state = runtime.state();
|
|
756
872
|
const queue = runtime.queue().status();
|
|
757
|
-
const
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
873
|
+
const { used, size } = state.usage;
|
|
874
|
+
const summary = [
|
|
875
|
+
`**${config.agent.name}**`,
|
|
876
|
+
queue.busy ? "busy" : "idle",
|
|
877
|
+
size > 0 ? `${formatNumber(used)} / ${formatNumber(size)}` : null
|
|
878
|
+
].filter(Boolean).join(" \xB7 ");
|
|
879
|
+
const rows = [
|
|
880
|
+
["Agent", `${config.agent.name} (${AGENTS[config.agent.name].label})`],
|
|
881
|
+
["Session", agent.sessionId() ?? "none"],
|
|
882
|
+
["Work dir", compactPath(config.agent.cwd)],
|
|
883
|
+
["Model", modelText(runtime)],
|
|
884
|
+
["Status", queue.busy ? "busy" : "idle"],
|
|
885
|
+
["Queue", String(queue.queued)],
|
|
886
|
+
["Usage", usageText(runtime)],
|
|
887
|
+
["Capabilities", capabilitiesText(agent.capabilities())]
|
|
766
888
|
];
|
|
767
|
-
return
|
|
889
|
+
return [summary, "", table(["Field", "Value"], rows)].join("\n");
|
|
768
890
|
}
|
|
769
891
|
function costText(runtime) {
|
|
770
892
|
const usage = runtime.state().usage;
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
893
|
+
const cost = usage.cost ? `${usage.cost.amount} ${usage.cost.currency}` : "not reported by this agent";
|
|
894
|
+
return table(
|
|
895
|
+
["Field", "Value"],
|
|
896
|
+
[
|
|
897
|
+
["Context", usageText(runtime)],
|
|
898
|
+
["Cost", cost]
|
|
899
|
+
]
|
|
900
|
+
);
|
|
778
901
|
}
|
|
779
902
|
function capabilitiesText(caps) {
|
|
780
903
|
const enabled = [
|
|
@@ -829,10 +952,10 @@ var import_node_path3 = require("path");
|
|
|
829
952
|
// src/core/state.ts
|
|
830
953
|
var import_node_crypto = require("crypto");
|
|
831
954
|
var import_node_fs2 = require("fs");
|
|
832
|
-
var
|
|
955
|
+
var import_node_os3 = require("os");
|
|
833
956
|
var import_node_path2 = require("path");
|
|
834
957
|
function vbotHome() {
|
|
835
|
-
return process.env.VBOT_HOME?.trim() || (0, import_node_path2.join)((0,
|
|
958
|
+
return process.env.VBOT_HOME?.trim() || (0, import_node_path2.join)((0, import_node_os3.homedir)(), ".vbot");
|
|
836
959
|
}
|
|
837
960
|
function stateDir() {
|
|
838
961
|
return (0, import_node_path2.join)(vbotHome(), "state");
|
|
@@ -1161,13 +1284,17 @@ async function startBot(configPath) {
|
|
|
1161
1284
|
saveConfig: () => saveConfig(config, configPath)
|
|
1162
1285
|
};
|
|
1163
1286
|
holder.queue = new BotQueue((item, signal) => gate(() => processPrompt(ctx, item, signal)));
|
|
1164
|
-
const runtime = createRuntime(
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
switching
|
|
1169
|
-
|
|
1170
|
-
|
|
1287
|
+
const runtime = createRuntime(
|
|
1288
|
+
ctx,
|
|
1289
|
+
(task) => gate(task),
|
|
1290
|
+
(name) => {
|
|
1291
|
+
if (switching) return Promise.reject(new Error("Agent switch already in progress."));
|
|
1292
|
+
switching = true;
|
|
1293
|
+
return gate(() => switchAgent(ctx, name)).finally(() => {
|
|
1294
|
+
switching = false;
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
);
|
|
1171
1298
|
const dispatch = createDispatcher(runtime);
|
|
1172
1299
|
let notifySignal = () => void 0;
|
|
1173
1300
|
const wire = new VicvicWire({
|
|
@@ -1228,7 +1355,7 @@ async function runBot(configPath) {
|
|
|
1228
1355
|
const bot = await startBot(configPath);
|
|
1229
1356
|
await bot.finished;
|
|
1230
1357
|
}
|
|
1231
|
-
function createRuntime(ctx, switchAgent2) {
|
|
1358
|
+
function createRuntime(ctx, gate, switchAgent2) {
|
|
1232
1359
|
return {
|
|
1233
1360
|
config: () => ctx.config,
|
|
1234
1361
|
agent: () => ctx.getAgent(),
|
|
@@ -1236,6 +1363,7 @@ function createRuntime(ctx, switchAgent2) {
|
|
|
1236
1363
|
state: () => ctx.state,
|
|
1237
1364
|
saveState: () => ctx.saveState(),
|
|
1238
1365
|
saveConfig: () => ctx.saveConfig(),
|
|
1366
|
+
serialize: (task) => gate(task),
|
|
1239
1367
|
switchAgent: switchAgent2
|
|
1240
1368
|
};
|
|
1241
1369
|
}
|