@locusai/locus-telegram 0.22.12 → 0.22.14
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/bin/locus-telegram.js +583 -182
- package/package.json +5 -4
package/bin/locus-telegram.js
CHANGED
|
@@ -9480,19 +9480,11 @@ var init_config = __esm(() => {
|
|
|
9480
9480
|
init_dist();
|
|
9481
9481
|
});
|
|
9482
9482
|
|
|
9483
|
-
//
|
|
9483
|
+
// ../pm2/dist/index.js
|
|
9484
9484
|
import { execSync } from "node:child_process";
|
|
9485
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
9486
9485
|
import { dirname, join as join2 } from "node:path";
|
|
9487
9486
|
import { fileURLToPath } from "node:url";
|
|
9488
9487
|
function getPm2Bin() {
|
|
9489
|
-
try {
|
|
9490
|
-
const currentFile = fileURLToPath(import.meta.url);
|
|
9491
|
-
const packageRoot = dirname(dirname(currentFile));
|
|
9492
|
-
const localPm2 = join2(packageRoot, "..", "..", ".bin", "pm2");
|
|
9493
|
-
if (existsSync2(localPm2))
|
|
9494
|
-
return localPm2;
|
|
9495
|
-
} catch {}
|
|
9496
9488
|
try {
|
|
9497
9489
|
const result = execSync("which pm2", {
|
|
9498
9490
|
encoding: "utf-8",
|
|
@@ -9516,51 +9508,47 @@ function pm2Exec(args) {
|
|
|
9516
9508
|
throw new Error(err.stderr?.trim() || err.message || "PM2 command failed");
|
|
9517
9509
|
}
|
|
9518
9510
|
}
|
|
9519
|
-
function
|
|
9520
|
-
const
|
|
9521
|
-
const packageRoot = dirname(dirname(currentFile));
|
|
9522
|
-
return join2(packageRoot, "bin", "locus-telegram.js");
|
|
9523
|
-
}
|
|
9524
|
-
function pm2Start() {
|
|
9525
|
-
const script = getBotScriptPath();
|
|
9511
|
+
function pm2Start(config) {
|
|
9512
|
+
const { processName, scriptPath, scriptArgs = [] } = config;
|
|
9526
9513
|
const pm2 = getPm2Bin();
|
|
9527
9514
|
try {
|
|
9528
9515
|
const list = pm2Exec("jlist");
|
|
9529
9516
|
const processes = JSON.parse(list);
|
|
9530
|
-
const existing = processes.find((p) => p.name ===
|
|
9517
|
+
const existing = processes.find((p) => p.name === processName);
|
|
9531
9518
|
if (existing) {
|
|
9532
|
-
pm2Exec(`restart ${
|
|
9533
|
-
return `Restarted ${
|
|
9519
|
+
pm2Exec(`restart ${processName}`);
|
|
9520
|
+
return `Restarted ${processName}`;
|
|
9534
9521
|
}
|
|
9535
9522
|
} catch {}
|
|
9536
|
-
|
|
9523
|
+
const argsStr = scriptArgs.length > 0 ? ` -- ${scriptArgs.join(" ")}` : "";
|
|
9524
|
+
execSync(`${pm2} start ${JSON.stringify(scriptPath)} --name ${processName}${argsStr}`, {
|
|
9537
9525
|
encoding: "utf-8",
|
|
9538
9526
|
stdio: "inherit",
|
|
9539
9527
|
env: process.env
|
|
9540
9528
|
});
|
|
9541
|
-
return `Started ${
|
|
9529
|
+
return `Started ${processName}`;
|
|
9542
9530
|
}
|
|
9543
|
-
function pm2Stop() {
|
|
9544
|
-
pm2Exec(`stop ${
|
|
9545
|
-
return `Stopped ${
|
|
9531
|
+
function pm2Stop(config) {
|
|
9532
|
+
pm2Exec(`stop ${config.processName}`);
|
|
9533
|
+
return `Stopped ${config.processName}`;
|
|
9546
9534
|
}
|
|
9547
|
-
function pm2Restart() {
|
|
9548
|
-
pm2Exec(`restart ${
|
|
9549
|
-
return `Restarted ${
|
|
9535
|
+
function pm2Restart(config) {
|
|
9536
|
+
pm2Exec(`restart ${config.processName}`);
|
|
9537
|
+
return `Restarted ${config.processName}`;
|
|
9550
9538
|
}
|
|
9551
|
-
function pm2Delete() {
|
|
9552
|
-
pm2Exec(`delete ${
|
|
9553
|
-
return `Deleted ${
|
|
9539
|
+
function pm2Delete(config) {
|
|
9540
|
+
pm2Exec(`delete ${config.processName}`);
|
|
9541
|
+
return `Deleted ${config.processName}`;
|
|
9554
9542
|
}
|
|
9555
|
-
function pm2Status() {
|
|
9543
|
+
function pm2Status(config) {
|
|
9556
9544
|
try {
|
|
9557
9545
|
const list = pm2Exec("jlist");
|
|
9558
9546
|
const processes = JSON.parse(list);
|
|
9559
|
-
const proc = processes.find((p) => p.name ===
|
|
9547
|
+
const proc = processes.find((p) => p.name === config.processName);
|
|
9560
9548
|
if (!proc)
|
|
9561
9549
|
return null;
|
|
9562
9550
|
return {
|
|
9563
|
-
name:
|
|
9551
|
+
name: config.processName,
|
|
9564
9552
|
status: proc.pm2_env?.status ?? "unknown",
|
|
9565
9553
|
pid: proc.pid ?? null,
|
|
9566
9554
|
uptime: proc.pm2_env?.pm_uptime ?? null,
|
|
@@ -9571,17 +9559,285 @@ function pm2Status() {
|
|
|
9571
9559
|
return null;
|
|
9572
9560
|
}
|
|
9573
9561
|
}
|
|
9574
|
-
function pm2Logs(lines = 50) {
|
|
9562
|
+
function pm2Logs(config, lines = 50) {
|
|
9575
9563
|
try {
|
|
9576
|
-
return pm2Exec(`logs ${
|
|
9564
|
+
return pm2Exec(`logs ${config.processName} --nostream --lines ${lines}`);
|
|
9577
9565
|
} catch {
|
|
9578
9566
|
return "No logs available.";
|
|
9579
9567
|
}
|
|
9580
9568
|
}
|
|
9581
|
-
|
|
9582
|
-
|
|
9569
|
+
function resolvePackageScript(importMetaUrl, binName) {
|
|
9570
|
+
const currentFile = fileURLToPath(importMetaUrl);
|
|
9571
|
+
const packageRoot = dirname(dirname(currentFile));
|
|
9572
|
+
return join2(packageRoot, "bin", `${binName}.js`);
|
|
9573
|
+
}
|
|
9574
|
+
var init_dist2 = () => {};
|
|
9583
9575
|
|
|
9584
|
-
// src/
|
|
9576
|
+
// src/pm2.ts
|
|
9577
|
+
function getConfig() {
|
|
9578
|
+
return {
|
|
9579
|
+
processName: "locus-telegram",
|
|
9580
|
+
scriptPath: resolvePackageScript(import.meta.url, "locus-telegram"),
|
|
9581
|
+
scriptArgs: ["bot"]
|
|
9582
|
+
};
|
|
9583
|
+
}
|
|
9584
|
+
function pm2Start2() {
|
|
9585
|
+
return pm2Start(getConfig());
|
|
9586
|
+
}
|
|
9587
|
+
function pm2Stop2() {
|
|
9588
|
+
return pm2Stop(getConfig());
|
|
9589
|
+
}
|
|
9590
|
+
function pm2Restart2() {
|
|
9591
|
+
return pm2Restart(getConfig());
|
|
9592
|
+
}
|
|
9593
|
+
function pm2Delete2() {
|
|
9594
|
+
return pm2Delete(getConfig());
|
|
9595
|
+
}
|
|
9596
|
+
function pm2Status2() {
|
|
9597
|
+
return pm2Status(getConfig());
|
|
9598
|
+
}
|
|
9599
|
+
function pm2Logs2(lines = 50) {
|
|
9600
|
+
return pm2Logs(getConfig(), lines);
|
|
9601
|
+
}
|
|
9602
|
+
var init_pm2 = __esm(() => {
|
|
9603
|
+
init_dist2();
|
|
9604
|
+
});
|
|
9605
|
+
|
|
9606
|
+
// ../gateway/dist/index.js
|
|
9607
|
+
import { exec as execCb } from "node:child_process";
|
|
9608
|
+
import { promisify } from "node:util";
|
|
9609
|
+
import { spawn as spawn2, spawnSync as spawnSync2 } from "node:child_process";
|
|
9610
|
+
function getCommandDefinition(command) {
|
|
9611
|
+
return COMMAND_REGISTRY[command] ?? null;
|
|
9612
|
+
}
|
|
9613
|
+
function invokeLocusStream2(args, cwd) {
|
|
9614
|
+
return spawn2("locus", args, {
|
|
9615
|
+
cwd: cwd ?? process.cwd(),
|
|
9616
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
9617
|
+
env: process.env,
|
|
9618
|
+
shell: false
|
|
9619
|
+
});
|
|
9620
|
+
}
|
|
9621
|
+
function formatData2(data) {
|
|
9622
|
+
if (!data || Object.keys(data).length === 0)
|
|
9623
|
+
return "";
|
|
9624
|
+
return ` ${dim2(JSON.stringify(data))}`;
|
|
9625
|
+
}
|
|
9626
|
+
function createLogger2(name) {
|
|
9627
|
+
const prefix = dim2(`[${name}]`);
|
|
9628
|
+
return {
|
|
9629
|
+
info(msg, data) {
|
|
9630
|
+
process.stderr.write(`${bold2(cyan2("●"))} ${prefix} ${msg}${formatData2(data)}
|
|
9631
|
+
`);
|
|
9632
|
+
},
|
|
9633
|
+
warn(msg, data) {
|
|
9634
|
+
process.stderr.write(`${bold2(yellow2("⚠"))} ${prefix} ${yellow2(msg)}${formatData2(data)}
|
|
9635
|
+
`);
|
|
9636
|
+
},
|
|
9637
|
+
error(msg, data) {
|
|
9638
|
+
process.stderr.write(`${bold2(red2("✗"))} ${prefix} ${red2(msg)}${formatData2(data)}
|
|
9639
|
+
`);
|
|
9640
|
+
},
|
|
9641
|
+
debug(msg, data) {
|
|
9642
|
+
if (!process.env.LOCUS_DEBUG)
|
|
9643
|
+
return;
|
|
9644
|
+
process.stderr.write(`${gray2("⋯")} ${prefix} ${gray2(msg)}${formatData2(data)}
|
|
9645
|
+
`);
|
|
9646
|
+
}
|
|
9647
|
+
};
|
|
9648
|
+
}
|
|
9649
|
+
|
|
9650
|
+
class CommandExecutor {
|
|
9651
|
+
tracker;
|
|
9652
|
+
constructor(tracker) {
|
|
9653
|
+
this.tracker = tracker;
|
|
9654
|
+
}
|
|
9655
|
+
getTracker() {
|
|
9656
|
+
return this.tracker;
|
|
9657
|
+
}
|
|
9658
|
+
async executeLocusCommand(sessionId, command, args, callbacks) {
|
|
9659
|
+
const definition = getCommandDefinition(command);
|
|
9660
|
+
if (!definition) {
|
|
9661
|
+
return {
|
|
9662
|
+
text: `Unknown command: /${command}`,
|
|
9663
|
+
format: "plain",
|
|
9664
|
+
exitCode: 1
|
|
9665
|
+
};
|
|
9666
|
+
}
|
|
9667
|
+
if (definition.requiresArgs && args.length === 0) {
|
|
9668
|
+
return {
|
|
9669
|
+
text: definition.requiresArgs,
|
|
9670
|
+
format: "plain",
|
|
9671
|
+
exitCode: 1
|
|
9672
|
+
};
|
|
9673
|
+
}
|
|
9674
|
+
const conflict = this.tracker.checkExclusiveConflict(sessionId, command);
|
|
9675
|
+
if (conflict) {
|
|
9676
|
+
return {
|
|
9677
|
+
text: formatConflictText(command, conflict),
|
|
9678
|
+
format: "plain",
|
|
9679
|
+
exitCode: 1
|
|
9680
|
+
};
|
|
9681
|
+
}
|
|
9682
|
+
const fullArgs = [...definition.cliArgs, ...args];
|
|
9683
|
+
if (definition.streaming && callbacks) {
|
|
9684
|
+
return this.executeStreaming(sessionId, command, args, fullArgs, callbacks);
|
|
9685
|
+
}
|
|
9686
|
+
return this.executeBuffered(sessionId, command, args, fullArgs);
|
|
9687
|
+
}
|
|
9688
|
+
async executeGit(sessionId, command, args, gitArgs) {
|
|
9689
|
+
const conflict = this.tracker.checkExclusiveConflict(sessionId, command);
|
|
9690
|
+
if (conflict) {
|
|
9691
|
+
return {
|
|
9692
|
+
text: formatConflictText(command, conflict),
|
|
9693
|
+
format: "plain",
|
|
9694
|
+
exitCode: 1
|
|
9695
|
+
};
|
|
9696
|
+
}
|
|
9697
|
+
const trackingId = this.tracker.track(sessionId, command, args);
|
|
9698
|
+
try {
|
|
9699
|
+
const { stdout } = await exec(`git ${gitArgs}`, { cwd: process.cwd() });
|
|
9700
|
+
return {
|
|
9701
|
+
text: stdout,
|
|
9702
|
+
format: "plain",
|
|
9703
|
+
exitCode: 0
|
|
9704
|
+
};
|
|
9705
|
+
} catch (error) {
|
|
9706
|
+
const errStr = String(error);
|
|
9707
|
+
return {
|
|
9708
|
+
text: errStr,
|
|
9709
|
+
format: "plain",
|
|
9710
|
+
exitCode: 1
|
|
9711
|
+
};
|
|
9712
|
+
} finally {
|
|
9713
|
+
this.tracker.untrack(sessionId, trackingId);
|
|
9714
|
+
}
|
|
9715
|
+
}
|
|
9716
|
+
async executeStreaming(sessionId, command, args, fullArgs, callbacks) {
|
|
9717
|
+
const child = invokeLocusStream2(fullArgs);
|
|
9718
|
+
const trackingId = this.tracker.track(sessionId, command, args, child);
|
|
9719
|
+
let output = "";
|
|
9720
|
+
let lastUpdateTime = 0;
|
|
9721
|
+
let updateTimer = null;
|
|
9722
|
+
let messageId = "";
|
|
9723
|
+
const displayCmd = `locus ${fullArgs.join(" ")}`;
|
|
9724
|
+
const startResult = await callbacks.onStart(formatStreamingText(displayCmd, "", false));
|
|
9725
|
+
if (startResult)
|
|
9726
|
+
messageId = startResult;
|
|
9727
|
+
const pushUpdate = async () => {
|
|
9728
|
+
const now = Date.now();
|
|
9729
|
+
if (now - lastUpdateTime < STREAM_UPDATE_INTERVAL)
|
|
9730
|
+
return;
|
|
9731
|
+
lastUpdateTime = now;
|
|
9732
|
+
try {
|
|
9733
|
+
await callbacks.onUpdate(messageId, formatStreamingText(displayCmd, output, false));
|
|
9734
|
+
} catch {}
|
|
9735
|
+
};
|
|
9736
|
+
child.stdout?.on("data", (chunk) => {
|
|
9737
|
+
output += chunk.toString();
|
|
9738
|
+
if (updateTimer)
|
|
9739
|
+
clearTimeout(updateTimer);
|
|
9740
|
+
updateTimer = setTimeout(pushUpdate, STREAM_UPDATE_INTERVAL);
|
|
9741
|
+
});
|
|
9742
|
+
child.stderr?.on("data", (chunk) => {
|
|
9743
|
+
output += chunk.toString();
|
|
9744
|
+
});
|
|
9745
|
+
return new Promise((resolve) => {
|
|
9746
|
+
child.on("close", async (exitCode) => {
|
|
9747
|
+
this.tracker.untrack(sessionId, trackingId);
|
|
9748
|
+
if (updateTimer)
|
|
9749
|
+
clearTimeout(updateTimer);
|
|
9750
|
+
const code = exitCode ?? 0;
|
|
9751
|
+
await callbacks.onComplete(messageId, formatStreamingText(displayCmd, output, true), code);
|
|
9752
|
+
resolve({
|
|
9753
|
+
text: output,
|
|
9754
|
+
format: "plain",
|
|
9755
|
+
streaming: true,
|
|
9756
|
+
exitCode: code
|
|
9757
|
+
});
|
|
9758
|
+
});
|
|
9759
|
+
});
|
|
9760
|
+
}
|
|
9761
|
+
async executeBuffered(sessionId, command, args, fullArgs) {
|
|
9762
|
+
const child = invokeLocusStream2(fullArgs);
|
|
9763
|
+
const trackingId = this.tracker.track(sessionId, command, args, child);
|
|
9764
|
+
let output = "";
|
|
9765
|
+
child.stdout?.on("data", (chunk) => {
|
|
9766
|
+
output += chunk.toString();
|
|
9767
|
+
});
|
|
9768
|
+
child.stderr?.on("data", (chunk) => {
|
|
9769
|
+
output += chunk.toString();
|
|
9770
|
+
});
|
|
9771
|
+
return new Promise((resolve) => {
|
|
9772
|
+
child.on("close", (exitCode) => {
|
|
9773
|
+
this.tracker.untrack(sessionId, trackingId);
|
|
9774
|
+
const code = exitCode ?? 0;
|
|
9775
|
+
resolve({
|
|
9776
|
+
text: output,
|
|
9777
|
+
format: "plain",
|
|
9778
|
+
exitCode: code
|
|
9779
|
+
});
|
|
9780
|
+
});
|
|
9781
|
+
});
|
|
9782
|
+
}
|
|
9783
|
+
}
|
|
9784
|
+
function formatConflictText(blockedCommand, conflict) {
|
|
9785
|
+
const running = conflict.runningCommand;
|
|
9786
|
+
const runningLabel = `/${running.command}${running.args.length ? ` ${running.args.join(" ")}` : ""}`;
|
|
9787
|
+
return `/${blockedCommand} cannot start — ${runningLabel} is already running.
|
|
9788
|
+
|
|
9789
|
+
Send /cancel to abort it, or wait for it to finish.`;
|
|
9790
|
+
}
|
|
9791
|
+
function formatStreamingText(command, output, isComplete) {
|
|
9792
|
+
const status = isComplete ? "[DONE]" : "[RUNNING]";
|
|
9793
|
+
const header = `${status} ${command}`;
|
|
9794
|
+
if (!output.trim()) {
|
|
9795
|
+
return isComplete ? `${header}
|
|
9796
|
+
|
|
9797
|
+
Completed.` : `${header}
|
|
9798
|
+
|
|
9799
|
+
Running...
|
|
9800
|
+
|
|
9801
|
+
Send /cancel to abort`;
|
|
9802
|
+
}
|
|
9803
|
+
const lines = output.trim().split(`
|
|
9804
|
+
`);
|
|
9805
|
+
const lastLines = lines.slice(-30).join(`
|
|
9806
|
+
`);
|
|
9807
|
+
const hint = isComplete ? "" : `
|
|
9808
|
+
|
|
9809
|
+
Send /cancel to abort`;
|
|
9810
|
+
return `${header}
|
|
9811
|
+
|
|
9812
|
+
${lastLines}${hint}`;
|
|
9813
|
+
}
|
|
9814
|
+
|
|
9815
|
+
class CommandRouter {
|
|
9816
|
+
prefix;
|
|
9817
|
+
constructor(prefix = "/") {
|
|
9818
|
+
this.prefix = prefix;
|
|
9819
|
+
}
|
|
9820
|
+
parse(text) {
|
|
9821
|
+
const trimmed = text.trim();
|
|
9822
|
+
if (!trimmed.startsWith(this.prefix)) {
|
|
9823
|
+
return { type: "freetext", text: trimmed };
|
|
9824
|
+
}
|
|
9825
|
+
const withoutPrefix = trimmed.slice(this.prefix.length);
|
|
9826
|
+
const parts = withoutPrefix.split(/\s+/);
|
|
9827
|
+
const rawCommand = parts[0] ?? "";
|
|
9828
|
+
const command = rawCommand.replace(/@\S+$/, "").toLowerCase();
|
|
9829
|
+
if (!command) {
|
|
9830
|
+
return { type: "freetext", text: trimmed };
|
|
9831
|
+
}
|
|
9832
|
+
const args = parts.slice(1);
|
|
9833
|
+
return {
|
|
9834
|
+
type: "command",
|
|
9835
|
+
command,
|
|
9836
|
+
args,
|
|
9837
|
+
raw: trimmed
|
|
9838
|
+
};
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
9585
9841
|
function getExclusivityGroup(command) {
|
|
9586
9842
|
if (WORKSPACE_EXCLUSIVE.has(command))
|
|
9587
9843
|
return "workspace";
|
|
@@ -9592,7 +9848,7 @@ function getExclusivityGroup(command) {
|
|
|
9592
9848
|
|
|
9593
9849
|
class CommandTracker {
|
|
9594
9850
|
active = new Map;
|
|
9595
|
-
track(
|
|
9851
|
+
track(sessionId, command, args, childProcess = null) {
|
|
9596
9852
|
const id = String(nextId++);
|
|
9597
9853
|
const entry = {
|
|
9598
9854
|
id,
|
|
@@ -9601,32 +9857,32 @@ class CommandTracker {
|
|
|
9601
9857
|
childProcess,
|
|
9602
9858
|
startedAt: new Date
|
|
9603
9859
|
};
|
|
9604
|
-
const list = this.active.get(
|
|
9860
|
+
const list = this.active.get(sessionId);
|
|
9605
9861
|
if (list) {
|
|
9606
9862
|
list.push(entry);
|
|
9607
9863
|
} else {
|
|
9608
|
-
this.active.set(
|
|
9864
|
+
this.active.set(sessionId, [entry]);
|
|
9609
9865
|
}
|
|
9610
9866
|
return id;
|
|
9611
9867
|
}
|
|
9612
|
-
untrack(
|
|
9613
|
-
const list = this.active.get(
|
|
9868
|
+
untrack(sessionId, id) {
|
|
9869
|
+
const list = this.active.get(sessionId);
|
|
9614
9870
|
if (!list)
|
|
9615
9871
|
return;
|
|
9616
9872
|
const idx = list.findIndex((c) => c.id === id);
|
|
9617
9873
|
if (idx !== -1)
|
|
9618
9874
|
list.splice(idx, 1);
|
|
9619
9875
|
if (list.length === 0)
|
|
9620
|
-
this.active.delete(
|
|
9876
|
+
this.active.delete(sessionId);
|
|
9621
9877
|
}
|
|
9622
|
-
getActive(
|
|
9623
|
-
return this.active.get(
|
|
9878
|
+
getActive(sessionId) {
|
|
9879
|
+
return this.active.get(sessionId) ?? [];
|
|
9624
9880
|
}
|
|
9625
|
-
checkExclusiveConflict(
|
|
9881
|
+
checkExclusiveConflict(sessionId, command) {
|
|
9626
9882
|
const group = getExclusivityGroup(command);
|
|
9627
9883
|
if (!group)
|
|
9628
9884
|
return null;
|
|
9629
|
-
const list = this.active.get(
|
|
9885
|
+
const list = this.active.get(sessionId);
|
|
9630
9886
|
if (!list)
|
|
9631
9887
|
return null;
|
|
9632
9888
|
for (const entry of list) {
|
|
@@ -9636,8 +9892,8 @@ class CommandTracker {
|
|
|
9636
9892
|
}
|
|
9637
9893
|
return null;
|
|
9638
9894
|
}
|
|
9639
|
-
kill(
|
|
9640
|
-
const list = this.active.get(
|
|
9895
|
+
kill(sessionId, id) {
|
|
9896
|
+
const list = this.active.get(sessionId);
|
|
9641
9897
|
if (!list)
|
|
9642
9898
|
return false;
|
|
9643
9899
|
const entry = list.find((c) => c.id === id);
|
|
@@ -9646,11 +9902,11 @@ class CommandTracker {
|
|
|
9646
9902
|
if (entry.childProcess && !entry.childProcess.killed) {
|
|
9647
9903
|
entry.childProcess.kill("SIGTERM");
|
|
9648
9904
|
}
|
|
9649
|
-
this.untrack(
|
|
9905
|
+
this.untrack(sessionId, id);
|
|
9650
9906
|
return true;
|
|
9651
9907
|
}
|
|
9652
|
-
killAll(
|
|
9653
|
-
const list = this.active.get(
|
|
9908
|
+
killAll(sessionId) {
|
|
9909
|
+
const list = this.active.get(sessionId);
|
|
9654
9910
|
if (!list)
|
|
9655
9911
|
return 0;
|
|
9656
9912
|
let killed = 0;
|
|
@@ -9660,25 +9916,191 @@ class CommandTracker {
|
|
|
9660
9916
|
}
|
|
9661
9917
|
killed++;
|
|
9662
9918
|
}
|
|
9663
|
-
this.active.delete(
|
|
9919
|
+
this.active.delete(sessionId);
|
|
9664
9920
|
return killed;
|
|
9665
9921
|
}
|
|
9666
9922
|
}
|
|
9667
|
-
|
|
9923
|
+
|
|
9924
|
+
class Gateway {
|
|
9925
|
+
adapters = new Map;
|
|
9926
|
+
router;
|
|
9927
|
+
executor;
|
|
9928
|
+
tracker;
|
|
9929
|
+
onEvent;
|
|
9930
|
+
constructor(options = {}) {
|
|
9931
|
+
this.router = new CommandRouter;
|
|
9932
|
+
this.tracker = new CommandTracker;
|
|
9933
|
+
this.executor = new CommandExecutor(this.tracker);
|
|
9934
|
+
this.onEvent = options.onEvent;
|
|
9935
|
+
}
|
|
9936
|
+
register(adapter) {
|
|
9937
|
+
if (this.adapters.has(adapter.platform)) {
|
|
9938
|
+
throw new Error(`Adapter already registered for platform: ${adapter.platform}`);
|
|
9939
|
+
}
|
|
9940
|
+
this.adapters.set(adapter.platform, adapter);
|
|
9941
|
+
logger.info(`Registered adapter: ${adapter.platform}`);
|
|
9942
|
+
}
|
|
9943
|
+
getAdapter(platform) {
|
|
9944
|
+
return this.adapters.get(platform);
|
|
9945
|
+
}
|
|
9946
|
+
getRouter() {
|
|
9947
|
+
return this.router;
|
|
9948
|
+
}
|
|
9949
|
+
getExecutor() {
|
|
9950
|
+
return this.executor;
|
|
9951
|
+
}
|
|
9952
|
+
getTracker() {
|
|
9953
|
+
return this.tracker;
|
|
9954
|
+
}
|
|
9955
|
+
async handleMessage(message) {
|
|
9956
|
+
this.emit({ type: "message_received", message });
|
|
9957
|
+
const adapter = this.adapters.get(message.platform);
|
|
9958
|
+
if (!adapter) {
|
|
9959
|
+
logger.warn(`No adapter registered for platform: ${message.platform}`);
|
|
9960
|
+
return;
|
|
9961
|
+
}
|
|
9962
|
+
const parsed = this.router.parse(message.text);
|
|
9963
|
+
if (parsed.type === "freetext") {
|
|
9964
|
+
await this.executeCommand(adapter, message.sessionId, "exec", [parsed.text], message);
|
|
9965
|
+
return;
|
|
9966
|
+
}
|
|
9967
|
+
await this.executeCommand(adapter, message.sessionId, parsed.command, parsed.args, message);
|
|
9968
|
+
}
|
|
9969
|
+
async start() {
|
|
9970
|
+
const platforms = Array.from(this.adapters.keys());
|
|
9971
|
+
logger.info(`Starting gateway with adapters: ${platforms.join(", ")}`);
|
|
9972
|
+
for (const [platform, adapter] of this.adapters) {
|
|
9973
|
+
try {
|
|
9974
|
+
await adapter.start();
|
|
9975
|
+
logger.info(`Started adapter: ${platform}`);
|
|
9976
|
+
} catch (error) {
|
|
9977
|
+
logger.error(`Failed to start adapter: ${platform}`, {
|
|
9978
|
+
error: String(error)
|
|
9979
|
+
});
|
|
9980
|
+
throw error;
|
|
9981
|
+
}
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
async stop() {
|
|
9985
|
+
for (const [platform, adapter] of this.adapters) {
|
|
9986
|
+
try {
|
|
9987
|
+
await adapter.stop();
|
|
9988
|
+
logger.info(`Stopped adapter: ${platform}`);
|
|
9989
|
+
} catch (error) {
|
|
9990
|
+
logger.warn(`Error stopping adapter: ${platform}`, {
|
|
9991
|
+
error: String(error)
|
|
9992
|
+
});
|
|
9993
|
+
}
|
|
9994
|
+
}
|
|
9995
|
+
}
|
|
9996
|
+
async executeCommand(adapter, sessionId, command, args, _originalMessage) {
|
|
9997
|
+
this.emit({
|
|
9998
|
+
type: "command_started",
|
|
9999
|
+
sessionId,
|
|
10000
|
+
command,
|
|
10001
|
+
args
|
|
10002
|
+
});
|
|
10003
|
+
let streamCallbacks;
|
|
10004
|
+
if (adapter.capabilities.supportsStreaming && adapter.edit) {
|
|
10005
|
+
const adapterRef = adapter;
|
|
10006
|
+
const sentMessageId = "";
|
|
10007
|
+
streamCallbacks = {
|
|
10008
|
+
async onStart(text) {
|
|
10009
|
+
await adapterRef.send(sessionId, {
|
|
10010
|
+
text,
|
|
10011
|
+
format: "plain"
|
|
10012
|
+
});
|
|
10013
|
+
return sentMessageId;
|
|
10014
|
+
},
|
|
10015
|
+
async onUpdate(messageId, text) {
|
|
10016
|
+
if (adapterRef.edit) {
|
|
10017
|
+
await adapterRef.edit(sessionId, messageId, {
|
|
10018
|
+
text,
|
|
10019
|
+
format: "plain"
|
|
10020
|
+
});
|
|
10021
|
+
}
|
|
10022
|
+
},
|
|
10023
|
+
async onComplete(messageId, text, _exitCode) {
|
|
10024
|
+
if (adapterRef.edit) {
|
|
10025
|
+
await adapterRef.edit(sessionId, messageId, {
|
|
10026
|
+
text,
|
|
10027
|
+
format: "plain"
|
|
10028
|
+
});
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
10031
|
+
};
|
|
10032
|
+
}
|
|
10033
|
+
const result = await this.executor.executeLocusCommand(sessionId, command, args, streamCallbacks);
|
|
10034
|
+
this.emit({
|
|
10035
|
+
type: "command_completed",
|
|
10036
|
+
sessionId,
|
|
10037
|
+
command,
|
|
10038
|
+
exitCode: result.exitCode
|
|
10039
|
+
});
|
|
10040
|
+
if (!result.streaming) {
|
|
10041
|
+
await adapter.send(sessionId, {
|
|
10042
|
+
text: result.text,
|
|
10043
|
+
format: result.format,
|
|
10044
|
+
actions: result.actions
|
|
10045
|
+
});
|
|
10046
|
+
}
|
|
10047
|
+
}
|
|
10048
|
+
emit(event) {
|
|
10049
|
+
if (this.onEvent) {
|
|
10050
|
+
try {
|
|
10051
|
+
this.onEvent(event);
|
|
10052
|
+
} catch (error) {
|
|
10053
|
+
logger.warn("Event handler error", { error: String(error) });
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
10056
|
+
}
|
|
10057
|
+
}
|
|
10058
|
+
var COMMAND_REGISTRY, STREAMING_COMMANDS, colorEnabled2 = () => process.stderr.isTTY === true && process.env.NO_COLOR === undefined, wrap2 = (open, close) => (text) => colorEnabled2() ? `${open}${text}${close}` : text, bold2, dim2, red2, yellow2, cyan2, gray2, exec, STREAM_UPDATE_INTERVAL = 2000, WORKSPACE_EXCLUSIVE, GIT_EXCLUSIVE, nextId = 1, logger;
|
|
10059
|
+
var init_dist3 = __esm(() => {
|
|
10060
|
+
COMMAND_REGISTRY = {
|
|
10061
|
+
run: { cliArgs: ["run"], streaming: true },
|
|
10062
|
+
status: { cliArgs: ["status"], streaming: false },
|
|
10063
|
+
issues: { cliArgs: ["issue", "list"], streaming: false },
|
|
10064
|
+
issue: { cliArgs: ["issue", "show"], streaming: false },
|
|
10065
|
+
sprint: { cliArgs: ["sprint"], streaming: false },
|
|
10066
|
+
plan: { cliArgs: ["plan"], streaming: true },
|
|
10067
|
+
review: { cliArgs: ["review"], streaming: true },
|
|
10068
|
+
iterate: { cliArgs: ["iterate"], streaming: true },
|
|
10069
|
+
discuss: {
|
|
10070
|
+
cliArgs: ["discuss"],
|
|
10071
|
+
streaming: true,
|
|
10072
|
+
requiresArgs: `Please provide a discussion topic.
|
|
10073
|
+
|
|
10074
|
+
Example: /discuss Should we use Redis or in-memory caching?`
|
|
10075
|
+
},
|
|
10076
|
+
exec: {
|
|
10077
|
+
cliArgs: ["exec"],
|
|
10078
|
+
streaming: true,
|
|
10079
|
+
requiresArgs: `Please provide a prompt.
|
|
10080
|
+
|
|
10081
|
+
Example: /exec Add error handling to the API`
|
|
10082
|
+
},
|
|
10083
|
+
logs: { cliArgs: ["logs"], streaming: false },
|
|
10084
|
+
config: { cliArgs: ["config"], streaming: false },
|
|
10085
|
+
artifacts: { cliArgs: ["artifacts"], streaming: false }
|
|
10086
|
+
};
|
|
10087
|
+
STREAMING_COMMANDS = new Set(Object.entries(COMMAND_REGISTRY).filter(([, def]) => def.streaming).map(([name]) => name));
|
|
10088
|
+
bold2 = wrap2("\x1B[1m", "\x1B[22m");
|
|
10089
|
+
dim2 = wrap2("\x1B[2m", "\x1B[22m");
|
|
10090
|
+
red2 = wrap2("\x1B[31m", "\x1B[39m");
|
|
10091
|
+
yellow2 = wrap2("\x1B[33m", "\x1B[39m");
|
|
10092
|
+
cyan2 = wrap2("\x1B[36m", "\x1B[39m");
|
|
10093
|
+
gray2 = wrap2("\x1B[90m", "\x1B[39m");
|
|
10094
|
+
exec = promisify(execCb);
|
|
10095
|
+
WORKSPACE_EXCLUSIVE = new Set(["run", "plan", "iterate", "exec"]);
|
|
10096
|
+
GIT_EXCLUSIVE = new Set(["stage", "commit", "checkout", "stash", "pr"]);
|
|
10097
|
+
logger = createLogger2("gateway");
|
|
10098
|
+
});
|
|
10099
|
+
|
|
10100
|
+
// src/tracker.ts
|
|
10101
|
+
var commandTracker;
|
|
9668
10102
|
var init_tracker = __esm(() => {
|
|
9669
|
-
|
|
9670
|
-
"run",
|
|
9671
|
-
"plan",
|
|
9672
|
-
"iterate",
|
|
9673
|
-
"exec"
|
|
9674
|
-
]);
|
|
9675
|
-
GIT_EXCLUSIVE = new Set([
|
|
9676
|
-
"stage",
|
|
9677
|
-
"commit",
|
|
9678
|
-
"checkout",
|
|
9679
|
-
"stash",
|
|
9680
|
-
"pr"
|
|
9681
|
-
]);
|
|
10103
|
+
init_dist3();
|
|
9682
10104
|
commandTracker = new CommandTracker;
|
|
9683
10105
|
});
|
|
9684
10106
|
|
|
@@ -9690,7 +10112,7 @@ function codeBlock(text, language = "") {
|
|
|
9690
10112
|
const truncated = truncate(text, MAX_CODE_LENGTH);
|
|
9691
10113
|
return `<pre><code${language ? ` class="language-${language}"` : ""}>${escapeHtml(truncated)}</code></pre>`;
|
|
9692
10114
|
}
|
|
9693
|
-
function
|
|
10115
|
+
function bold3(text) {
|
|
9694
10116
|
return `<b>${text}</b>`;
|
|
9695
10117
|
}
|
|
9696
10118
|
function italic(text) {
|
|
@@ -9705,7 +10127,7 @@ function truncate(text, maxLength = MAX_MESSAGE_LENGTH) {
|
|
|
9705
10127
|
}
|
|
9706
10128
|
function formatCommandResult(command, output, exitCode) {
|
|
9707
10129
|
const status = exitCode === 0 ? "✅" : "❌";
|
|
9708
|
-
const header = `${status} ${
|
|
10130
|
+
const header = `${status} ${bold3(escapeHtml(command))}`;
|
|
9709
10131
|
if (!output.trim()) {
|
|
9710
10132
|
return exitCode === 0 ? `${header}
|
|
9711
10133
|
|
|
@@ -9719,7 +10141,7 @@ ${codeBlock(output.trim())}`;
|
|
|
9719
10141
|
}
|
|
9720
10142
|
function formatStreamingMessage(command, output, isComplete) {
|
|
9721
10143
|
const status = isComplete ? "✅" : "⏳";
|
|
9722
|
-
const header = `${status} ${
|
|
10144
|
+
const header = `${status} ${bold3(escapeHtml(command))}`;
|
|
9723
10145
|
if (!output.trim()) {
|
|
9724
10146
|
return `${header}
|
|
9725
10147
|
|
|
@@ -9740,12 +10162,12 @@ ${codeBlock(lastLines)}${hint}`;
|
|
|
9740
10162
|
}
|
|
9741
10163
|
function formatConflictMessage(blockedCommand, running) {
|
|
9742
10164
|
const runningLabel = `/${escapeHtml(running.command)}${running.args.length ? ` ${escapeHtml(running.args.join(" "))}` : ""}`;
|
|
9743
|
-
return `⚠️ ${
|
|
10165
|
+
return `⚠️ ${bold3(escapeHtml(`/${blockedCommand}`))} cannot start — ${bold3(runningLabel)} is already running.
|
|
9744
10166
|
|
|
9745
10167
|
Send /cancel to abort it, or wait for it to finish.`;
|
|
9746
10168
|
}
|
|
9747
10169
|
function formatError(message, detail) {
|
|
9748
|
-
let text = `❌ ${
|
|
10170
|
+
let text = `❌ ${bold3("Error")}
|
|
9749
10171
|
|
|
9750
10172
|
${escapeHtml(message)}`;
|
|
9751
10173
|
if (detail) {
|
|
@@ -9768,7 +10190,8 @@ async function handleCancel(ctx) {
|
|
|
9768
10190
|
const chatId = ctx.chat?.id;
|
|
9769
10191
|
if (!chatId)
|
|
9770
10192
|
return;
|
|
9771
|
-
const
|
|
10193
|
+
const sessionId = String(chatId);
|
|
10194
|
+
const active = commandTracker.getActive(sessionId);
|
|
9772
10195
|
if (active.length === 0) {
|
|
9773
10196
|
await ctx.reply(`ℹ️ No commands are currently running.`, {
|
|
9774
10197
|
parse_mode: "HTML"
|
|
@@ -9777,8 +10200,8 @@ async function handleCancel(ctx) {
|
|
|
9777
10200
|
}
|
|
9778
10201
|
if (active.length === 1) {
|
|
9779
10202
|
const cmd = active[0];
|
|
9780
|
-
commandTracker.kill(
|
|
9781
|
-
await ctx.reply(`\uD83D\uDED1 Cancelled ${
|
|
10203
|
+
commandTracker.kill(sessionId, cmd.id);
|
|
10204
|
+
await ctx.reply(`\uD83D\uDED1 Cancelled ${bold3(`/${escapeHtml(cmd.command)}`)}${cmd.args.length ? ` ${escapeHtml(cmd.args.join(" "))}` : ""}`, { parse_mode: "HTML" });
|
|
9782
10205
|
return;
|
|
9783
10206
|
}
|
|
9784
10207
|
const kb = new import_grammy.InlineKeyboard;
|
|
@@ -9787,7 +10210,7 @@ async function handleCancel(ctx) {
|
|
|
9787
10210
|
kb.text(`\uD83D\uDED1 ${label}`, `${CANCEL_CMD_PREFIX}${cmd.id}`).row();
|
|
9788
10211
|
}
|
|
9789
10212
|
kb.text("\uD83D\uDED1 Cancel All", CANCEL_ALL);
|
|
9790
|
-
await ctx.reply(`${
|
|
10213
|
+
await ctx.reply(`${bold3("Multiple commands running:")}
|
|
9791
10214
|
|
|
9792
10215
|
${italic("Select which to cancel:")}`, { parse_mode: "HTML", reply_markup: kb });
|
|
9793
10216
|
}
|
|
@@ -9798,17 +10221,18 @@ async function handleCancelCallback(ctx) {
|
|
|
9798
10221
|
const chatId = ctx.chat?.id;
|
|
9799
10222
|
if (!chatId)
|
|
9800
10223
|
return;
|
|
10224
|
+
const sessionId = String(chatId);
|
|
9801
10225
|
if (data === CANCEL_ALL) {
|
|
9802
|
-
const count = commandTracker.killAll(
|
|
10226
|
+
const count = commandTracker.killAll(sessionId);
|
|
9803
10227
|
await ctx.answerCallbackQuery({ text: `Cancelled ${count} command(s)` });
|
|
9804
|
-
await ctx.editMessageText(`\uD83D\uDED1 Cancelled ${
|
|
10228
|
+
await ctx.editMessageText(`\uD83D\uDED1 Cancelled ${bold3(`${count} command(s)`)}.`, {
|
|
9805
10229
|
parse_mode: "HTML"
|
|
9806
10230
|
});
|
|
9807
10231
|
return;
|
|
9808
10232
|
}
|
|
9809
10233
|
if (data.startsWith(CANCEL_CMD_PREFIX)) {
|
|
9810
10234
|
const id = data.slice(CANCEL_CMD_PREFIX.length);
|
|
9811
|
-
const killed = commandTracker.kill(
|
|
10235
|
+
const killed = commandTracker.kill(sessionId, id);
|
|
9812
10236
|
if (killed) {
|
|
9813
10237
|
await ctx.answerCallbackQuery({ text: "Command cancelled" });
|
|
9814
10238
|
await ctx.editMessageText(`\uD83D\uDED1 Command cancelled.`, {
|
|
@@ -9877,11 +10301,11 @@ var init_keyboards = __esm(() => {
|
|
|
9877
10301
|
// src/ui/messages.ts
|
|
9878
10302
|
function welcomeMessage() {
|
|
9879
10303
|
return [
|
|
9880
|
-
`${
|
|
10304
|
+
`${bold3("\uD83E\uDD16 Locus Telegram Remote Control")}`,
|
|
9881
10305
|
"",
|
|
9882
10306
|
"Control your Locus agent directly from Telegram.",
|
|
9883
10307
|
"",
|
|
9884
|
-
`${
|
|
10308
|
+
`${bold3("Locus Commands")}`,
|
|
9885
10309
|
"/run [issue#...] — Execute issues",
|
|
9886
10310
|
"/status — Dashboard view",
|
|
9887
10311
|
"/issues [filters] — List issues",
|
|
@@ -9896,7 +10320,7 @@ function welcomeMessage() {
|
|
|
9896
10320
|
"/config [path] — View config",
|
|
9897
10321
|
"/artifacts — View artifacts",
|
|
9898
10322
|
"",
|
|
9899
|
-
`${
|
|
10323
|
+
`${bold3("Git Commands")}`,
|
|
9900
10324
|
"/gitstatus — Git status",
|
|
9901
10325
|
"/stage [files|.] — Stage files",
|
|
9902
10326
|
"/commit <message> — Commit changes",
|
|
@@ -9906,10 +10330,10 @@ function welcomeMessage() {
|
|
|
9906
10330
|
"/diff — Show diff",
|
|
9907
10331
|
"/pr <title> — Create pull request",
|
|
9908
10332
|
"",
|
|
9909
|
-
`${
|
|
10333
|
+
`${bold3("Control")}`,
|
|
9910
10334
|
"/cancel — Abort running commands",
|
|
9911
10335
|
"",
|
|
9912
|
-
`${
|
|
10336
|
+
`${bold3("Service Commands")}`,
|
|
9913
10337
|
"/service start|stop|restart|status|logs",
|
|
9914
10338
|
"",
|
|
9915
10339
|
"/help — Show this message"
|
|
@@ -9917,14 +10341,14 @@ function welcomeMessage() {
|
|
|
9917
10341
|
`);
|
|
9918
10342
|
}
|
|
9919
10343
|
function runStartedMessage(target) {
|
|
9920
|
-
return `\uD83D\uDE80 ${
|
|
10344
|
+
return `\uD83D\uDE80 ${bold3("Run Started")} — ${escapeHtml(target)}`;
|
|
9921
10345
|
}
|
|
9922
10346
|
function reviewStartedMessage(prNumber) {
|
|
9923
|
-
return `\uD83D\uDD0D ${
|
|
10347
|
+
return `\uD83D\uDD0D ${bold3("Reviewing")} PR #${prNumber}...`;
|
|
9924
10348
|
}
|
|
9925
10349
|
function gitCommitMessage(message, hash) {
|
|
9926
10350
|
return [
|
|
9927
|
-
`✅ ${
|
|
10351
|
+
`✅ ${bold3("Committed")}`,
|
|
9928
10352
|
"",
|
|
9929
10353
|
`Message: ${escapeHtml(message)}`,
|
|
9930
10354
|
`Hash: ${escapeHtml(hash)}`
|
|
@@ -9932,17 +10356,17 @@ function gitCommitMessage(message, hash) {
|
|
|
9932
10356
|
`);
|
|
9933
10357
|
}
|
|
9934
10358
|
function gitBranchCreatedMessage(name) {
|
|
9935
|
-
return `✅ Branch ${
|
|
10359
|
+
return `✅ Branch ${bold3(escapeHtml(name))} created.`;
|
|
9936
10360
|
}
|
|
9937
10361
|
function gitCheckoutMessage(branch) {
|
|
9938
|
-
return `✅ Switched to branch ${
|
|
10362
|
+
return `✅ Switched to branch ${bold3(escapeHtml(branch))}.`;
|
|
9939
10363
|
}
|
|
9940
10364
|
function gitStashMessage(action) {
|
|
9941
|
-
return `✅ ${
|
|
10365
|
+
return `✅ ${bold3("Stash")} — ${escapeHtml(action)} completed.`;
|
|
9942
10366
|
}
|
|
9943
10367
|
function prCreatedMessage(prNumber, url) {
|
|
9944
10368
|
return [
|
|
9945
|
-
`✅ ${
|
|
10369
|
+
`✅ ${bold3("Pull Request Created")}`,
|
|
9946
10370
|
"",
|
|
9947
10371
|
`PR #${prNumber}: ${escapeHtml(url)}`
|
|
9948
10372
|
].join(`
|
|
@@ -9952,9 +10376,9 @@ function serviceStatusMessage(status, pid, uptime, memory, restarts) {
|
|
|
9952
10376
|
const uptimeStr = uptime ? formatUptime(uptime) : "N/A";
|
|
9953
10377
|
const memoryStr = memory ? formatMemory(memory) : "N/A";
|
|
9954
10378
|
return [
|
|
9955
|
-
`\uD83E\uDD16 ${
|
|
10379
|
+
`\uD83E\uDD16 ${bold3("Locus Telegram Bot")}`,
|
|
9956
10380
|
"",
|
|
9957
|
-
`Status: ${
|
|
10381
|
+
`Status: ${bold3(status)}`,
|
|
9958
10382
|
`PID: ${pid ?? "N/A"}`,
|
|
9959
10383
|
`Uptime: ${uptimeStr}`,
|
|
9960
10384
|
`Memory: ${memoryStr}`,
|
|
@@ -9964,10 +10388,10 @@ function serviceStatusMessage(status, pid, uptime, memory, restarts) {
|
|
|
9964
10388
|
}
|
|
9965
10389
|
function serviceNotRunningMessage() {
|
|
9966
10390
|
return [
|
|
9967
|
-
`⚠️ ${
|
|
10391
|
+
`⚠️ ${bold3("Bot Not Running")}`,
|
|
9968
10392
|
"",
|
|
9969
10393
|
"Start the bot with:",
|
|
9970
|
-
`${
|
|
10394
|
+
`${bold3("locus pkg telegram start")}`
|
|
9971
10395
|
].join(`
|
|
9972
10396
|
`);
|
|
9973
10397
|
}
|
|
@@ -9992,10 +10416,10 @@ function formatMemory(bytes) {
|
|
|
9992
10416
|
var init_messages = () => {};
|
|
9993
10417
|
|
|
9994
10418
|
// src/commands/git.ts
|
|
9995
|
-
import { exec as
|
|
9996
|
-
import { promisify } from "node:util";
|
|
10419
|
+
import { exec as execCb2 } from "node:child_process";
|
|
10420
|
+
import { promisify as promisify2 } from "node:util";
|
|
9997
10421
|
async function git(args) {
|
|
9998
|
-
const { stdout } = await
|
|
10422
|
+
const { stdout } = await exec2(`git ${args}`, { cwd: process.cwd() });
|
|
9999
10423
|
return stdout;
|
|
10000
10424
|
}
|
|
10001
10425
|
async function gitSafe(args) {
|
|
@@ -10009,18 +10433,19 @@ async function tracked(ctx, command, args, fn) {
|
|
|
10009
10433
|
const chatId = ctx.chat?.id;
|
|
10010
10434
|
if (!chatId)
|
|
10011
10435
|
return;
|
|
10012
|
-
const
|
|
10436
|
+
const sessionId = String(chatId);
|
|
10437
|
+
const conflict = commandTracker.checkExclusiveConflict(sessionId, command);
|
|
10013
10438
|
if (conflict) {
|
|
10014
10439
|
await ctx.reply(formatConflictMessage(command, conflict.runningCommand), {
|
|
10015
10440
|
parse_mode: "HTML"
|
|
10016
10441
|
});
|
|
10017
10442
|
return;
|
|
10018
10443
|
}
|
|
10019
|
-
const id = commandTracker.track(
|
|
10444
|
+
const id = commandTracker.track(sessionId, command, args);
|
|
10020
10445
|
try {
|
|
10021
10446
|
await fn();
|
|
10022
10447
|
} finally {
|
|
10023
|
-
commandTracker.untrack(
|
|
10448
|
+
commandTracker.untrack(sessionId, id);
|
|
10024
10449
|
}
|
|
10025
10450
|
}
|
|
10026
10451
|
async function handleGitStatus(ctx) {
|
|
@@ -10034,11 +10459,13 @@ async function handleGitStatus(ctx) {
|
|
|
10034
10459
|
return;
|
|
10035
10460
|
}
|
|
10036
10461
|
const branch = (await git("branch --show-current")).trim();
|
|
10037
|
-
await ctx.reply(`${
|
|
10462
|
+
await ctx.reply(`${bold3("Branch:")} ${escapeHtml(branch)}
|
|
10038
10463
|
|
|
10039
10464
|
${codeBlock(status)}`, { parse_mode: "HTML" });
|
|
10040
10465
|
} catch (error) {
|
|
10041
|
-
await ctx.reply(formatError("Failed to get git status", String(error)), {
|
|
10466
|
+
await ctx.reply(formatError("Failed to get git status", String(error)), {
|
|
10467
|
+
parse_mode: "HTML"
|
|
10468
|
+
});
|
|
10042
10469
|
}
|
|
10043
10470
|
});
|
|
10044
10471
|
}
|
|
@@ -10149,7 +10576,7 @@ async function handleBranch(ctx, args) {
|
|
|
10149
10576
|
if (args.length === 0) {
|
|
10150
10577
|
const branches = await git("branch -a --format='%(refname:short)'");
|
|
10151
10578
|
const current = (await git("branch --show-current")).trim();
|
|
10152
|
-
await ctx.reply(`${
|
|
10579
|
+
await ctx.reply(`${bold3("Current:")} ${escapeHtml(current)}
|
|
10153
10580
|
|
|
10154
10581
|
${codeBlock(branches)}`, { parse_mode: "HTML" });
|
|
10155
10582
|
} else {
|
|
@@ -10196,14 +10623,16 @@ async function handleDiff(ctx) {
|
|
|
10196
10623
|
parse_mode: "HTML"
|
|
10197
10624
|
});
|
|
10198
10625
|
} else {
|
|
10199
|
-
await ctx.reply(`${
|
|
10626
|
+
await ctx.reply(`${bold3("Staged changes:")}
|
|
10200
10627
|
|
|
10201
10628
|
${codeBlock(staged)}`, { parse_mode: "HTML" });
|
|
10202
10629
|
}
|
|
10203
10630
|
} else {
|
|
10204
|
-
await ctx.reply(`${
|
|
10631
|
+
await ctx.reply(`${bold3("Unstaged changes:")}
|
|
10205
10632
|
|
|
10206
|
-
${codeBlock(diff)}`, {
|
|
10633
|
+
${codeBlock(diff)}`, {
|
|
10634
|
+
parse_mode: "HTML"
|
|
10635
|
+
});
|
|
10207
10636
|
}
|
|
10208
10637
|
} catch (error) {
|
|
10209
10638
|
await ctx.reply(formatError("Diff failed", String(error)), {
|
|
@@ -10226,7 +10655,7 @@ async function handlePR(ctx, args) {
|
|
|
10226
10655
|
try {
|
|
10227
10656
|
await git(`push -u origin ${branch}`);
|
|
10228
10657
|
} catch {}
|
|
10229
|
-
const { stdout: result } = await
|
|
10658
|
+
const { stdout: result } = await exec2(`gh pr create --title ${JSON.stringify(title)} --body "Created via Locus Telegram Bot" --head ${branch}`, { cwd: process.cwd() });
|
|
10230
10659
|
const prMatch = result.match(/\/pull\/(\d+)/);
|
|
10231
10660
|
const prNumber = prMatch ? Number(prMatch[1]) : 0;
|
|
10232
10661
|
await ctx.reply(prCreatedMessage(prNumber, result.trim()), {
|
|
@@ -10239,37 +10668,37 @@ async function handlePR(ctx, args) {
|
|
|
10239
10668
|
}
|
|
10240
10669
|
});
|
|
10241
10670
|
}
|
|
10242
|
-
var
|
|
10671
|
+
var exec2;
|
|
10243
10672
|
var init_git = __esm(() => {
|
|
10244
10673
|
init_tracker();
|
|
10245
10674
|
init_keyboards();
|
|
10246
10675
|
init_messages();
|
|
10247
|
-
|
|
10676
|
+
exec2 = promisify2(execCb2);
|
|
10248
10677
|
});
|
|
10249
10678
|
|
|
10250
10679
|
// src/commands/locus.ts
|
|
10251
10680
|
async function handleLocusCommand(ctx, command, args) {
|
|
10252
|
-
const
|
|
10253
|
-
if (!
|
|
10681
|
+
const definition = getCommandDefinition(command);
|
|
10682
|
+
if (!definition) {
|
|
10254
10683
|
await ctx.reply(`Unknown command: /${command}`);
|
|
10255
10684
|
return;
|
|
10256
10685
|
}
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
await ctx.reply(requiresArgsMsg);
|
|
10686
|
+
if (definition.requiresArgs && args.length === 0) {
|
|
10687
|
+
await ctx.reply(definition.requiresArgs);
|
|
10260
10688
|
return;
|
|
10261
10689
|
}
|
|
10262
10690
|
const chatId = ctx.chat?.id;
|
|
10263
10691
|
if (!chatId)
|
|
10264
10692
|
return;
|
|
10265
|
-
const
|
|
10693
|
+
const sessionId = String(chatId);
|
|
10694
|
+
const conflict = commandTracker.checkExclusiveConflict(sessionId, command);
|
|
10266
10695
|
if (conflict) {
|
|
10267
10696
|
await ctx.reply(formatConflictMessage(command, conflict.runningCommand), {
|
|
10268
10697
|
parse_mode: "HTML"
|
|
10269
10698
|
});
|
|
10270
10699
|
return;
|
|
10271
10700
|
}
|
|
10272
|
-
const fullArgs = [...cliArgs, ...args];
|
|
10701
|
+
const fullArgs = [...definition.cliArgs, ...args];
|
|
10273
10702
|
const displayCmd = `locus ${fullArgs.join(" ")}`;
|
|
10274
10703
|
const isStreaming = STREAMING_COMMANDS.has(command);
|
|
10275
10704
|
if (command === "run" && args.length > 0) {
|
|
@@ -10289,8 +10718,9 @@ async function handleStreamingCommand(ctx, displayCmd, fullArgs, command, args)
|
|
|
10289
10718
|
const chatId = ctx.chat?.id;
|
|
10290
10719
|
if (!chatId)
|
|
10291
10720
|
return;
|
|
10721
|
+
const sessionId = String(chatId);
|
|
10292
10722
|
const child = invokeLocusStream(fullArgs);
|
|
10293
|
-
const trackingId = commandTracker.track(
|
|
10723
|
+
const trackingId = commandTracker.track(sessionId, command, args, child);
|
|
10294
10724
|
let output = "";
|
|
10295
10725
|
let lastEditTime = 0;
|
|
10296
10726
|
let editTimer = null;
|
|
@@ -10317,7 +10747,7 @@ async function handleStreamingCommand(ctx, displayCmd, fullArgs, command, args)
|
|
|
10317
10747
|
});
|
|
10318
10748
|
await new Promise((resolve) => {
|
|
10319
10749
|
child.on("close", async (exitCode) => {
|
|
10320
|
-
commandTracker.untrack(
|
|
10750
|
+
commandTracker.untrack(sessionId, trackingId);
|
|
10321
10751
|
if (editTimer)
|
|
10322
10752
|
clearTimeout(editTimer);
|
|
10323
10753
|
try {
|
|
@@ -10335,8 +10765,9 @@ async function handleBufferedCommand(ctx, displayCmd, fullArgs, command) {
|
|
|
10335
10765
|
const chatId = ctx.chat?.id;
|
|
10336
10766
|
if (!chatId)
|
|
10337
10767
|
return;
|
|
10768
|
+
const sessionId = String(chatId);
|
|
10338
10769
|
const child = invokeLocusStream(fullArgs);
|
|
10339
|
-
const trackingId = commandTracker.track(
|
|
10770
|
+
const trackingId = commandTracker.track(sessionId, command, [], child);
|
|
10340
10771
|
let output = "";
|
|
10341
10772
|
child.stdout?.on("data", (chunk) => {
|
|
10342
10773
|
output += chunk.toString();
|
|
@@ -10346,7 +10777,7 @@ async function handleBufferedCommand(ctx, displayCmd, fullArgs, command) {
|
|
|
10346
10777
|
});
|
|
10347
10778
|
await new Promise((resolve) => {
|
|
10348
10779
|
child.on("close", async (exitCode) => {
|
|
10349
|
-
commandTracker.untrack(
|
|
10780
|
+
commandTracker.untrack(sessionId, trackingId);
|
|
10350
10781
|
const result = formatCommandResult(displayCmd, output, exitCode ?? 0);
|
|
10351
10782
|
const keyboard = getPostCommandKeyboard(command, [], exitCode ?? 0);
|
|
10352
10783
|
await ctx.reply(result, {
|
|
@@ -10379,43 +10810,13 @@ function getPostCommandKeyboard(command, args, exitCode) {
|
|
|
10379
10810
|
return null;
|
|
10380
10811
|
}
|
|
10381
10812
|
}
|
|
10382
|
-
var
|
|
10813
|
+
var EDIT_INTERVAL = 2000;
|
|
10383
10814
|
var init_locus = __esm(() => {
|
|
10815
|
+
init_dist3();
|
|
10384
10816
|
init_dist();
|
|
10385
10817
|
init_tracker();
|
|
10386
10818
|
init_keyboards();
|
|
10387
10819
|
init_messages();
|
|
10388
|
-
COMMAND_MAP = {
|
|
10389
|
-
run: ["run"],
|
|
10390
|
-
status: ["status"],
|
|
10391
|
-
issues: ["issue", "list"],
|
|
10392
|
-
issue: ["issue", "show"],
|
|
10393
|
-
sprint: ["sprint"],
|
|
10394
|
-
plan: ["plan"],
|
|
10395
|
-
review: ["review"],
|
|
10396
|
-
iterate: ["iterate"],
|
|
10397
|
-
discuss: ["discuss"],
|
|
10398
|
-
exec: ["exec"],
|
|
10399
|
-
logs: ["logs"],
|
|
10400
|
-
config: ["config"],
|
|
10401
|
-
artifacts: ["artifacts"]
|
|
10402
|
-
};
|
|
10403
|
-
STREAMING_COMMANDS = new Set([
|
|
10404
|
-
"run",
|
|
10405
|
-
"plan",
|
|
10406
|
-
"review",
|
|
10407
|
-
"iterate",
|
|
10408
|
-
"discuss",
|
|
10409
|
-
"exec"
|
|
10410
|
-
]);
|
|
10411
|
-
REQUIRES_ARGS = {
|
|
10412
|
-
exec: `Please provide a prompt.
|
|
10413
|
-
|
|
10414
|
-
Example: /exec Add error handling to the API`,
|
|
10415
|
-
discuss: `Please provide a discussion topic.
|
|
10416
|
-
|
|
10417
|
-
Example: /discuss Should we use Redis or in-memory caching?`
|
|
10418
|
-
};
|
|
10419
10820
|
});
|
|
10420
10821
|
|
|
10421
10822
|
// src/commands/service.ts
|
|
@@ -10424,27 +10825,27 @@ async function handleService(ctx, args) {
|
|
|
10424
10825
|
try {
|
|
10425
10826
|
switch (subcommand) {
|
|
10426
10827
|
case "start": {
|
|
10427
|
-
const result =
|
|
10828
|
+
const result = pm2Start2();
|
|
10428
10829
|
await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
|
|
10429
10830
|
break;
|
|
10430
10831
|
}
|
|
10431
10832
|
case "stop": {
|
|
10432
|
-
const result =
|
|
10833
|
+
const result = pm2Stop2();
|
|
10433
10834
|
await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
|
|
10434
10835
|
break;
|
|
10435
10836
|
}
|
|
10436
10837
|
case "restart": {
|
|
10437
|
-
const result =
|
|
10838
|
+
const result = pm2Restart2();
|
|
10438
10839
|
await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
|
|
10439
10840
|
break;
|
|
10440
10841
|
}
|
|
10441
10842
|
case "delete": {
|
|
10442
|
-
const result =
|
|
10843
|
+
const result = pm2Delete2();
|
|
10443
10844
|
await ctx.reply(formatSuccess(result), { parse_mode: "HTML" });
|
|
10444
10845
|
break;
|
|
10445
10846
|
}
|
|
10446
10847
|
case "status": {
|
|
10447
|
-
const status =
|
|
10848
|
+
const status = pm2Status2();
|
|
10448
10849
|
if (!status) {
|
|
10449
10850
|
await ctx.reply(serviceNotRunningMessage(), {
|
|
10450
10851
|
parse_mode: "HTML"
|
|
@@ -10456,7 +10857,7 @@ async function handleService(ctx, args) {
|
|
|
10456
10857
|
}
|
|
10457
10858
|
case "logs": {
|
|
10458
10859
|
const lines = args[1] ? Number(args[1]) : 50;
|
|
10459
|
-
const logs =
|
|
10860
|
+
const logs = pm2Logs2(lines);
|
|
10460
10861
|
await ctx.reply(codeBlock(logs), { parse_mode: "HTML" });
|
|
10461
10862
|
break;
|
|
10462
10863
|
}
|
|
@@ -10485,7 +10886,7 @@ function createBot(config) {
|
|
|
10485
10886
|
bot.use(async (ctx, next) => {
|
|
10486
10887
|
const chatId = ctx.chat?.id;
|
|
10487
10888
|
if (!chatId || !config.allowedChatIds.includes(chatId)) {
|
|
10488
|
-
|
|
10889
|
+
logger2.warn("Unauthorized access attempt", {
|
|
10489
10890
|
chatId,
|
|
10490
10891
|
from: ctx.from?.username
|
|
10491
10892
|
});
|
|
@@ -10659,7 +11060,7 @@ function parseArgs(text, command) {
|
|
|
10659
11060
|
return [];
|
|
10660
11061
|
return rest.split(/\s+/);
|
|
10661
11062
|
}
|
|
10662
|
-
var import_grammy3,
|
|
11063
|
+
var import_grammy3, logger2;
|
|
10663
11064
|
var init_bot = __esm(() => {
|
|
10664
11065
|
init_dist();
|
|
10665
11066
|
init_cancel();
|
|
@@ -10669,7 +11070,7 @@ var init_bot = __esm(() => {
|
|
|
10669
11070
|
init_keyboards();
|
|
10670
11071
|
init_messages();
|
|
10671
11072
|
import_grammy3 = __toESM(require_mod(), 1);
|
|
10672
|
-
|
|
11073
|
+
logger2 = createLogger("telegram");
|
|
10673
11074
|
});
|
|
10674
11075
|
|
|
10675
11076
|
// src/index.ts
|
|
@@ -10705,25 +11106,25 @@ async function main(args) {
|
|
|
10705
11106
|
}
|
|
10706
11107
|
}
|
|
10707
11108
|
function handleStart() {
|
|
10708
|
-
const result =
|
|
10709
|
-
|
|
11109
|
+
const result = pm2Start2();
|
|
11110
|
+
logger3.info(result);
|
|
10710
11111
|
}
|
|
10711
11112
|
function handleStop() {
|
|
10712
|
-
const result =
|
|
10713
|
-
|
|
11113
|
+
const result = pm2Stop2();
|
|
11114
|
+
logger3.info(result);
|
|
10714
11115
|
}
|
|
10715
11116
|
function handleRestart() {
|
|
10716
|
-
const result =
|
|
10717
|
-
|
|
11117
|
+
const result = pm2Restart2();
|
|
11118
|
+
logger3.info(result);
|
|
10718
11119
|
}
|
|
10719
11120
|
function handleDelete() {
|
|
10720
|
-
const result =
|
|
10721
|
-
|
|
11121
|
+
const result = pm2Delete2();
|
|
11122
|
+
logger3.info(result);
|
|
10722
11123
|
}
|
|
10723
11124
|
function handleStatus() {
|
|
10724
|
-
const status =
|
|
11125
|
+
const status = pm2Status2();
|
|
10725
11126
|
if (!status) {
|
|
10726
|
-
|
|
11127
|
+
logger3.warn("Bot is not running");
|
|
10727
11128
|
return;
|
|
10728
11129
|
}
|
|
10729
11130
|
const uptimeStr = status.uptime ? `${Math.floor((Date.now() - status.uptime) / 1000)}s` : "N/A";
|
|
@@ -10739,15 +11140,15 @@ function handleStatus() {
|
|
|
10739
11140
|
}
|
|
10740
11141
|
function handleLogs(args) {
|
|
10741
11142
|
const lines = args[0] ? Number(args[0]) : 50;
|
|
10742
|
-
const logs =
|
|
11143
|
+
const logs = pm2Logs2(lines);
|
|
10743
11144
|
console.log(logs);
|
|
10744
11145
|
}
|
|
10745
11146
|
async function handleBot() {
|
|
10746
11147
|
const config = loadTelegramConfig();
|
|
10747
11148
|
const { createBot: createBot2 } = await Promise.resolve().then(() => (init_bot(), exports_bot));
|
|
10748
11149
|
const bot = createBot2(config);
|
|
10749
|
-
|
|
10750
|
-
|
|
11150
|
+
logger3.info("Starting Telegram bot...");
|
|
11151
|
+
logger3.info(`Allowed chat IDs: ${config.allowedChatIds.join(", ")}`);
|
|
10751
11152
|
try {
|
|
10752
11153
|
await bot.api.setMyCommands([
|
|
10753
11154
|
{ command: "run", description: "Execute issues" },
|
|
@@ -10775,20 +11176,20 @@ async function handleBot() {
|
|
|
10775
11176
|
{ command: "service", description: "Manage bot process" },
|
|
10776
11177
|
{ command: "help", description: "Show help message" }
|
|
10777
11178
|
]);
|
|
10778
|
-
|
|
11179
|
+
logger3.info("Telegram command menu synced.");
|
|
10779
11180
|
} catch (err) {
|
|
10780
|
-
|
|
11181
|
+
logger3.warn("Failed to sync command menu", { error: String(err) });
|
|
10781
11182
|
}
|
|
10782
11183
|
await bot.api.deleteWebhook({ drop_pending_updates: true });
|
|
10783
11184
|
const runner = import_runner.run(bot);
|
|
10784
11185
|
const shutdown = () => {
|
|
10785
|
-
|
|
11186
|
+
logger3.info("Shutting down bot...");
|
|
10786
11187
|
runner.stop();
|
|
10787
11188
|
process.exit(0);
|
|
10788
11189
|
};
|
|
10789
11190
|
process.on("SIGINT", shutdown);
|
|
10790
11191
|
process.on("SIGTERM", shutdown);
|
|
10791
|
-
|
|
11192
|
+
logger3.info("Bot is running. Send /help in Telegram to get started.");
|
|
10792
11193
|
}
|
|
10793
11194
|
function printHelp() {
|
|
10794
11195
|
console.log(`
|
|
@@ -10820,13 +11221,13 @@ function printHelp() {
|
|
|
10820
11221
|
locus pkg telegram bot # Run in foreground (development)
|
|
10821
11222
|
`);
|
|
10822
11223
|
}
|
|
10823
|
-
var import_runner,
|
|
11224
|
+
var import_runner, logger3;
|
|
10824
11225
|
var init_src = __esm(() => {
|
|
10825
11226
|
init_dist();
|
|
10826
11227
|
init_config();
|
|
10827
11228
|
init_pm2();
|
|
10828
11229
|
import_runner = __toESM(require_mod2(), 1);
|
|
10829
|
-
|
|
11230
|
+
logger3 = createLogger("telegram");
|
|
10830
11231
|
});
|
|
10831
11232
|
|
|
10832
11233
|
// src/cli.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@locusai/locus-telegram",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.14",
|
|
4
4
|
"description": "Remote-control Locus via Telegram with full CLI mapping, git operations, and PM2 management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@grammyjs/runner": "^2.0.3",
|
|
30
|
-
"@locusai/
|
|
31
|
-
"
|
|
32
|
-
"
|
|
30
|
+
"@locusai/locus-gateway": "^0.22.14",
|
|
31
|
+
"@locusai/locus-pm2": "^0.22.14",
|
|
32
|
+
"@locusai/sdk": "^0.22.14",
|
|
33
|
+
"grammy": "^1.35.0"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"typescript": "^5.8.3"
|