appback-remoteagent 0.13.5 → 0.13.7
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/bot.js +63 -4
- package/dist/config.js +1 -1
- package/dist/index.js +2 -63
- package/dist/store/file-store.js +1 -1
- package/dist/telegram-command-menu.js +71 -0
- package/package.json +1 -1
package/dist/bot.js
CHANGED
|
@@ -9,6 +9,7 @@ import { config } from "./config.js";
|
|
|
9
9
|
import { ProviderSetupService } from "./services/provider-setup-service.js";
|
|
10
10
|
import { RemoteShellService } from "./services/remote-shell-service.js";
|
|
11
11
|
import { AgentMemoryService } from "./services/agent-memory-service.js";
|
|
12
|
+
import { deleteTelegramCommandMenu, setTelegramCommandMenu } from "./telegram-command-menu.js";
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
const HELP_TEXT = [
|
|
14
15
|
"Commands:",
|
|
@@ -24,7 +25,7 @@ const HELP_TEXT = [
|
|
|
24
25
|
"/stop",
|
|
25
26
|
"/sandbox codex <read-only|workspace-write|danger-full-access>",
|
|
26
27
|
"/status",
|
|
27
|
-
"/option [retry <count>|timeout <seconds>|intent <count>]",
|
|
28
|
+
"/option [retry <count>|timeout <seconds>|intent <count>|command-menu <on|off|refresh>]",
|
|
28
29
|
"/state [clear|note <text>]",
|
|
29
30
|
"/artifacts list|cleanup <days>",
|
|
30
31
|
"/secret set|list|remove",
|
|
@@ -484,8 +485,8 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
484
485
|
await reply(ctx, formatRuntimeOptions());
|
|
485
486
|
return;
|
|
486
487
|
}
|
|
487
|
-
if (option !== "retry" && option !== "timeout" && option !== "intent") {
|
|
488
|
-
await reply(ctx, "Usage: `/option retry <count>`, `/option timeout <seconds>`, or `/option
|
|
488
|
+
if (option !== "retry" && option !== "timeout" && option !== "intent" && option !== "command-menu") {
|
|
489
|
+
await reply(ctx, "Usage: `/option retry <count>`, `/option timeout <seconds>`, `/option intent <count>`, or `/option command-menu <on|off|refresh>`\n\n`retry` controls automatic continuation turns. `timeout` controls one provider execution limit. `intent` controls retries for untagged intent-only provider replies. `command-menu` controls Telegram slash-command autocomplete for all configured bots.", {
|
|
489
490
|
parse_mode: "Markdown",
|
|
490
491
|
});
|
|
491
492
|
return;
|
|
@@ -495,12 +496,39 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
495
496
|
? `Current automatic continuation retry limit: ${formatRetryLimit(config.telegramAutoProgressMaxTurns)}\n\nUsage: \`/option retry <count>\``
|
|
496
497
|
: option === "intent"
|
|
497
498
|
? `Current untagged intent retry limit: ${formatRetryLimit(config.telegramUntaggedIntentRetries)}\n\nUsage: \`/option intent <count>\``
|
|
498
|
-
:
|
|
499
|
+
: option === "command-menu"
|
|
500
|
+
? `Current Telegram command menu: ${config.telegramCommandMenuEnabled ? "on" : "off"}\n\nUsage: \`/option command-menu on\`, \`/option command-menu off\`, or \`/option command-menu refresh\``
|
|
501
|
+
: `Current provider execution timeout: ${formatTimeoutSeconds(config.commandTimeoutMs)}\n\nUsage: \`/option timeout <seconds>\``;
|
|
499
502
|
await reply(ctx, current, {
|
|
500
503
|
parse_mode: "Markdown",
|
|
501
504
|
});
|
|
502
505
|
return;
|
|
503
506
|
}
|
|
507
|
+
if (option === "command-menu") {
|
|
508
|
+
const action = value.toLowerCase();
|
|
509
|
+
if (!["on", "off", "refresh"].includes(action)) {
|
|
510
|
+
await reply(ctx, "Invalid command-menu option. Use `/option command-menu on`, `/option command-menu off`, or `/option command-menu refresh`.", {
|
|
511
|
+
parse_mode: "Markdown",
|
|
512
|
+
});
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const enabled = action === "refresh" ? config.telegramCommandMenuEnabled : action === "on";
|
|
516
|
+
const result = await applyTelegramCommandMenuOption(enabled);
|
|
517
|
+
if (action !== "refresh") {
|
|
518
|
+
config.telegramCommandMenuEnabled = enabled;
|
|
519
|
+
await upsertInstalledEnvValue("TELEGRAM_COMMAND_MENU_ENABLED", String(enabled));
|
|
520
|
+
}
|
|
521
|
+
await bridge.logSystem(botId, chatId, `Runtime option TELEGRAM_COMMAND_MENU_ENABLED ${action === "refresh" ? "refreshed" : `set to ${enabled}`}. ${result.summary}`);
|
|
522
|
+
await reply(ctx, [
|
|
523
|
+
action === "refresh"
|
|
524
|
+
? `Refreshed Telegram command menu for configured bots.`
|
|
525
|
+
: `Set Telegram command menu to ${enabled ? "on" : "off"}.`,
|
|
526
|
+
"",
|
|
527
|
+
result.summary,
|
|
528
|
+
action === "refresh" ? "" : `Saved: TELEGRAM_COMMAND_MENU_ENABLED=${enabled}`,
|
|
529
|
+
].filter(Boolean).join("\n"));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
504
532
|
const parsed = Number.parseInt(value, 10);
|
|
505
533
|
if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== value.trim()) {
|
|
506
534
|
await reply(ctx, option === "retry"
|
|
@@ -1583,14 +1611,17 @@ function formatRuntimeOptions() {
|
|
|
1583
1611
|
`- retry: ${formatRetryLimit(config.telegramAutoProgressMaxTurns)} (TELEGRAM_AUTO_PROGRESS_MAX_TURNS)`,
|
|
1584
1612
|
`- timeout: ${formatTimeoutSeconds(config.commandTimeoutMs)} (COMMAND_TIMEOUT_MS)`,
|
|
1585
1613
|
`- intent: ${formatRetryLimit(config.telegramUntaggedIntentRetries)} (TELEGRAM_UNTAGGED_INTENT_RETRIES)`,
|
|
1614
|
+
`- command-menu: ${config.telegramCommandMenuEnabled ? "on" : "off"} (TELEGRAM_COMMAND_MENU_ENABLED)`,
|
|
1586
1615
|
"",
|
|
1587
1616
|
"Usage:",
|
|
1588
1617
|
"/option retry <count>",
|
|
1589
1618
|
"/option timeout <seconds>",
|
|
1590
1619
|
"/option intent <count>",
|
|
1620
|
+
"/option command-menu <on|off|refresh>",
|
|
1591
1621
|
"",
|
|
1592
1622
|
"`retry 0` disables the automatic continuation limit.",
|
|
1593
1623
|
"`intent 0` disables untagged intent-only response retries.",
|
|
1624
|
+
"`command-menu refresh` reapplies Telegram slash-command autocomplete without changing the saved option.",
|
|
1594
1625
|
].join("\n");
|
|
1595
1626
|
}
|
|
1596
1627
|
function formatRetryLimit(value) {
|
|
@@ -1620,6 +1651,34 @@ async function upsertInstalledEnvValue(key, value) {
|
|
|
1620
1651
|
}
|
|
1621
1652
|
await fs.writeFile(envPath, `${next.join("\n").replace(/\n+$/u, "")}\n`, "utf8");
|
|
1622
1653
|
}
|
|
1654
|
+
async function applyTelegramCommandMenuOption(enabled) {
|
|
1655
|
+
let applied = 0;
|
|
1656
|
+
const failures = [];
|
|
1657
|
+
for (const token of config.telegramBotTokens) {
|
|
1658
|
+
const botLabel = await resolveTelegramBotLabel(token).catch(() => "unknown-bot");
|
|
1659
|
+
try {
|
|
1660
|
+
if (enabled) {
|
|
1661
|
+
await setTelegramCommandMenu(token);
|
|
1662
|
+
}
|
|
1663
|
+
else {
|
|
1664
|
+
await deleteTelegramCommandMenu(token);
|
|
1665
|
+
}
|
|
1666
|
+
applied += 1;
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
failures.push(`${botLabel}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
const parts = [`applied=${applied}/${config.telegramBotTokens.length}`];
|
|
1673
|
+
if (failures.length > 0) {
|
|
1674
|
+
parts.push(`failed=${failures.length}`, ...failures.map((failure) => `- ${failure}`));
|
|
1675
|
+
}
|
|
1676
|
+
return { summary: parts.join("\n") };
|
|
1677
|
+
}
|
|
1678
|
+
async function resolveTelegramBotLabel(token) {
|
|
1679
|
+
const payload = await callTelegramApi(token, "getMe", {});
|
|
1680
|
+
return payload.username ? `@${payload.username}` : String(payload.id);
|
|
1681
|
+
}
|
|
1623
1682
|
function chunkMessage(text, size) {
|
|
1624
1683
|
if (text.length <= size) {
|
|
1625
1684
|
return [text];
|
package/dist/config.js
CHANGED
|
@@ -130,7 +130,7 @@ const telegramBotUsernames = readTelegramBotUsernames();
|
|
|
130
130
|
export const config = {
|
|
131
131
|
telegramBotTokens,
|
|
132
132
|
telegramBotUsernames,
|
|
133
|
-
telegramCommandMenuEnabled: readBoolean("TELEGRAM_COMMAND_MENU_ENABLED",
|
|
133
|
+
telegramCommandMenuEnabled: readBoolean("TELEGRAM_COMMAND_MENU_ENABLED", true),
|
|
134
134
|
telegramPollingBackoffMinMs: readTimeout("TELEGRAM_POLLING_BACKOFF_MIN_MS", 60_000),
|
|
135
135
|
telegramPollingBackoffMaxMs: readTimeout("TELEGRAM_POLLING_BACKOFF_MAX_MS", 900_000),
|
|
136
136
|
telegramPollingMaxConcurrency: readTimeout("TELEGRAM_POLLING_MAX_CONCURRENCY", 3),
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { BotManagementService } from "./services/bot-management-service.js";
|
|
|
15
15
|
import { LocalUiService } from "./services/local-ui-service.js";
|
|
16
16
|
import { AgentMemoryService } from "./services/agent-memory-service.js";
|
|
17
17
|
import { terminateAllSpawnedExecutions } from "./adapters/windows-shell.js";
|
|
18
|
+
import { setTelegramCommandMenu } from "./telegram-command-menu.js";
|
|
18
19
|
const execFileAsync = promisify(execFile);
|
|
19
20
|
const TELEGRAM_GET_UPDATES_HTTP_TIMEOUT_SECONDS = 30;
|
|
20
21
|
const TELEGRAM_GET_UPDATES_CURL_TIMEOUT_SECONDS = 60;
|
|
@@ -107,23 +108,6 @@ function startArtifactCleanupSchedule(memoryService) {
|
|
|
107
108
|
interval.unref();
|
|
108
109
|
}
|
|
109
110
|
async function configureTelegramCommandMenu(bot) {
|
|
110
|
-
const commands = [
|
|
111
|
-
{ command: "start", description: "Start a new Codex or Claude session" },
|
|
112
|
-
{ command: "list", description: "List sessions" },
|
|
113
|
-
{ command: "switch", description: "Switch to a session" },
|
|
114
|
-
{ command: "status", description: "Show current session status" },
|
|
115
|
-
{ command: "state", description: "Show or edit session state notes" },
|
|
116
|
-
{ command: "option", description: "Show or change runtime options" },
|
|
117
|
-
{ command: "model", description: "Show or change provider model" },
|
|
118
|
-
{ command: "stop", description: "Stop active work and clear queued messages" },
|
|
119
|
-
{ command: "batch", description: "Collect and send a multi-message batch" },
|
|
120
|
-
{ command: "bots", description: "List configured Telegram bots" },
|
|
121
|
-
{ command: "bot", description: "Manage Telegram bots" },
|
|
122
|
-
{ command: "install", description: "Install or update Codex or Claude" },
|
|
123
|
-
{ command: "login", description: "Run provider login flow" },
|
|
124
|
-
{ command: "reset", description: "Clear this chat binding" },
|
|
125
|
-
{ command: "help", description: "Show command help" },
|
|
126
|
-
];
|
|
127
111
|
const token = bot.token;
|
|
128
112
|
if (!token) {
|
|
129
113
|
throw new Error("Telegram bot token is unavailable for command menu registration.");
|
|
@@ -131,7 +115,7 @@ async function configureTelegramCommandMenu(bot) {
|
|
|
131
115
|
let lastError;
|
|
132
116
|
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
133
117
|
try {
|
|
134
|
-
await
|
|
118
|
+
await setTelegramCommandMenu(token);
|
|
135
119
|
return;
|
|
136
120
|
}
|
|
137
121
|
catch (error) {
|
|
@@ -143,51 +127,6 @@ async function configureTelegramCommandMenu(bot) {
|
|
|
143
127
|
}
|
|
144
128
|
throw lastError;
|
|
145
129
|
}
|
|
146
|
-
async function setTelegramCommandsViaCurl(token, commands) {
|
|
147
|
-
const url = `https://api.telegram.org/bot${token}/setMyCommands`;
|
|
148
|
-
const payload = JSON.stringify({ commands });
|
|
149
|
-
let stdout;
|
|
150
|
-
let stderr;
|
|
151
|
-
try {
|
|
152
|
-
const result = await execFileAsync("curl", [
|
|
153
|
-
"-sS",
|
|
154
|
-
"-4",
|
|
155
|
-
"--max-time",
|
|
156
|
-
"20",
|
|
157
|
-
"-H",
|
|
158
|
-
"Content-Type: application/json",
|
|
159
|
-
"-d",
|
|
160
|
-
payload,
|
|
161
|
-
url,
|
|
162
|
-
]);
|
|
163
|
-
stdout = result.stdout;
|
|
164
|
-
stderr = result.stderr;
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
throw new Error(`Telegram setMyCommands curl failed: ${formatCurlError(error)}`);
|
|
168
|
-
}
|
|
169
|
-
if (stderr?.trim()) {
|
|
170
|
-
console.error(`curl stderr for setMyCommands: ${stderr.trim()}`);
|
|
171
|
-
}
|
|
172
|
-
const parsed = JSON.parse(stdout);
|
|
173
|
-
if (!parsed.ok) {
|
|
174
|
-
throw new Error(parsed.description || "Telegram setMyCommands failed.");
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
function formatCurlError(error) {
|
|
178
|
-
if (!(error instanceof Error)) {
|
|
179
|
-
return String(error);
|
|
180
|
-
}
|
|
181
|
-
const code = typeof error === "object" && error !== null && "code" in error
|
|
182
|
-
? String(error.code ?? "")
|
|
183
|
-
: "";
|
|
184
|
-
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
185
|
-
? String(error.stderr ?? "").trim()
|
|
186
|
-
: "";
|
|
187
|
-
return [code ? `code=${code}` : undefined, stderr || error.message]
|
|
188
|
-
.filter(Boolean)
|
|
189
|
-
.join(" ");
|
|
190
|
-
}
|
|
191
130
|
main().catch((error) => {
|
|
192
131
|
console.error("RemoteAgent fatal error:", error);
|
|
193
132
|
releaseProcessLockSync();
|
package/dist/store/file-store.js
CHANGED
|
@@ -404,7 +404,7 @@ export class FileStore {
|
|
|
404
404
|
}
|
|
405
405
|
async writeJsonFileAtomic(filePath, value) {
|
|
406
406
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
407
|
-
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
407
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
408
408
|
await fs.writeFile(temporaryPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
409
409
|
await fs.rename(temporaryPath, filePath);
|
|
410
410
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
export const TELEGRAM_COMMAND_MENU = [
|
|
5
|
+
{ command: "start", description: "Start a new Codex or Claude session" },
|
|
6
|
+
{ command: "list", description: "List sessions" },
|
|
7
|
+
{ command: "switch", description: "Switch to a session" },
|
|
8
|
+
{ command: "status", description: "Show current session status" },
|
|
9
|
+
{ command: "state", description: "Show or edit session state notes" },
|
|
10
|
+
{ command: "option", description: "Show or change runtime options" },
|
|
11
|
+
{ command: "model", description: "Show or change provider model" },
|
|
12
|
+
{ command: "stop", description: "Stop active work and clear queued messages" },
|
|
13
|
+
{ command: "batch", description: "Collect and send a multi-message batch" },
|
|
14
|
+
{ command: "bots", description: "List configured Telegram bots" },
|
|
15
|
+
{ command: "bot", description: "Manage Telegram bots" },
|
|
16
|
+
{ command: "install", description: "Install or update Codex or Claude" },
|
|
17
|
+
{ command: "login", description: "Run provider login flow" },
|
|
18
|
+
{ command: "reset", description: "Clear this chat binding" },
|
|
19
|
+
{ command: "help", description: "Show command help" },
|
|
20
|
+
];
|
|
21
|
+
export async function setTelegramCommandMenu(token) {
|
|
22
|
+
await callTelegramCommandApi(token, "setMyCommands", {
|
|
23
|
+
commands: TELEGRAM_COMMAND_MENU,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export async function deleteTelegramCommandMenu(token) {
|
|
27
|
+
await callTelegramCommandApi(token, "deleteMyCommands", {});
|
|
28
|
+
}
|
|
29
|
+
async function callTelegramCommandApi(token, method, payload) {
|
|
30
|
+
let stdout;
|
|
31
|
+
let stderr;
|
|
32
|
+
try {
|
|
33
|
+
const result = await execFileAsync("curl", [
|
|
34
|
+
"-sS",
|
|
35
|
+
"-4",
|
|
36
|
+
"--max-time",
|
|
37
|
+
"20",
|
|
38
|
+
"-H",
|
|
39
|
+
"Content-Type: application/json",
|
|
40
|
+
"-d",
|
|
41
|
+
JSON.stringify(payload),
|
|
42
|
+
`https://api.telegram.org/bot${token}/${method}`,
|
|
43
|
+
]);
|
|
44
|
+
stdout = result.stdout;
|
|
45
|
+
stderr = result.stderr;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new Error(`Telegram ${method} curl failed: ${formatCurlError(error)}`);
|
|
49
|
+
}
|
|
50
|
+
if (stderr?.trim()) {
|
|
51
|
+
console.error(`curl stderr for ${method}: ${stderr.trim()}`);
|
|
52
|
+
}
|
|
53
|
+
const parsed = JSON.parse(stdout);
|
|
54
|
+
if (!parsed.ok) {
|
|
55
|
+
throw new Error(parsed.description || `Telegram ${method} failed.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function formatCurlError(error) {
|
|
59
|
+
if (!(error instanceof Error)) {
|
|
60
|
+
return String(error);
|
|
61
|
+
}
|
|
62
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
63
|
+
? String(error.code ?? "")
|
|
64
|
+
: "";
|
|
65
|
+
const stderr = typeof error === "object" && error !== null && "stderr" in error
|
|
66
|
+
? String(error.stderr ?? "").trim()
|
|
67
|
+
: "";
|
|
68
|
+
return [code ? `code=${code}` : undefined, stderr || error.message]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join(" ");
|
|
71
|
+
}
|