@songsid/agend 2.1.4-beta.8 → 2.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.
- package/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/agent-cli-instructions.md +2 -2
- package/dist/agent-endpoint.js +1 -1
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/antigravity.d.ts +12 -8
- package/dist/backend/antigravity.js +189 -42
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +87 -23
- package/dist/backend/claude-code.js +361 -46
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +18 -0
- package/dist/backend/codex.js +139 -5
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/gemini-cli.js +2 -2
- package/dist/backend/gemini-cli.js.map +1 -1
- package/dist/backend/grok.js +2 -2
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +52 -2
- package/dist/backend/kiro.js +267 -12
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode.js +17 -4
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +122 -3
- package/dist/backend/types.js +5 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-outage.d.ts +61 -0
- package/dist/backend-outage.js +71 -0
- package/dist/backend-outage.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +44 -2
- package/dist/channel/adapters/discord.js +529 -89
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +30 -0
- package/dist/channel/adapters/telegram.js +221 -19
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/agy-mcp-launcher.d.ts +2 -0
- package/dist/channel/agy-mcp-launcher.js +28 -0
- package/dist/channel/agy-mcp-launcher.js.map +1 -0
- package/dist/channel/ipc-bridge.d.ts +2 -1
- package/dist/channel/ipc-bridge.js +33 -4
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/markdown-chunk.d.ts +42 -0
- package/dist/channel/markdown-chunk.js +205 -0
- package/dist/channel/markdown-chunk.js.map +1 -0
- package/dist/channel/mcp-server.js +34 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +5 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.js +64 -8
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/types.d.ts +31 -1
- package/dist/classic-channel-manager.d.ts +65 -0
- package/dist/classic-channel-manager.js +172 -3
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +124 -83
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +16 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +12 -1
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +6 -0
- package/dist/cross-instance-envelope.js +30 -0
- package/dist/cross-instance-envelope.js.map +1 -0
- package/dist/daemon.d.ts +538 -11
- package/dist/daemon.js +2272 -288
- package/dist/daemon.js.map +1 -1
- package/dist/doctor.d.ts +41 -0
- package/dist/doctor.js +267 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fleet-context.d.ts +63 -0
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-manager.d.ts +424 -11
- package/dist/fleet-manager.js +2753 -444
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-yaml-slim.d.ts +10 -0
- package/dist/fleet-yaml-slim.js +59 -0
- package/dist/fleet-yaml-slim.js.map +1 -0
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +63 -16
- package/dist/general-knowledge/skills/session-management/SKILL.md +171 -8
- package/dist/instance-lifecycle.d.ts +114 -1
- package/dist/instance-lifecycle.js +404 -14
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +42 -4
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +1024 -310
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.d.ts +91 -0
- package/dist/login-flows.js +170 -0
- package/dist/login-flows.js.map +1 -0
- package/dist/login-manager.d.ts +63 -0
- package/dist/login-manager.js +134 -0
- package/dist/login-manager.js.map +1 -0
- package/dist/network-family.d.ts +18 -0
- package/dist/network-family.js +20 -0
- package/dist/network-family.js.map +1 -0
- package/dist/outbound-handlers.d.ts +16 -1
- package/dist/outbound-handlers.js +174 -40
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +10 -3
- package/dist/outbound-schemas.js +10 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-input-residue.d.ts +52 -0
- package/dist/pane-input-residue.js +107 -0
- package/dist/pane-input-residue.js.map +1 -0
- package/dist/process-memory.d.ts +6 -0
- package/dist/process-memory.js +45 -0
- package/dist/process-memory.js.map +1 -1
- package/dist/reply-dedup.d.ts +7 -8
- package/dist/reply-dedup.js +0 -0
- package/dist/reply-dedup.js.map +1 -1
- package/dist/restart-progress.d.ts +2 -0
- package/dist/restart-progress.js +3 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/scheduler/db.d.ts +12 -0
- package/dist/scheduler/db.js +59 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/service-installer.d.ts +11 -0
- package/dist/service-installer.js +84 -18
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +1 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-wizard.js +2 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/spawn-gate.d.ts +30 -0
- package/dist/spawn-gate.js +79 -0
- package/dist/spawn-gate.js.map +1 -0
- package/dist/steer-capability.d.ts +1 -0
- package/dist/steer-capability.js +6 -0
- package/dist/steer-capability.js.map +1 -0
- package/dist/storm-window.d.ts +83 -0
- package/dist/storm-window.js +251 -0
- package/dist/storm-window.js.map +1 -0
- package/dist/tips.d.ts +44 -0
- package/dist/tips.js +355 -0
- package/dist/tips.js.map +1 -0
- package/dist/tmux-manager.d.ts +16 -0
- package/dist/tmux-manager.js +110 -25
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-progress.d.ts +40 -0
- package/dist/tool-progress.js +289 -0
- package/dist/tool-progress.js.map +1 -0
- package/dist/topic-commands.d.ts +45 -6
- package/dist/topic-commands.js +394 -66
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.d.ts +15 -2
- package/dist/transcript-monitor.js +63 -17
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +131 -0
- package/dist/transcript-sources.js +580 -0
- package/dist/transcript-sources.js.map +1 -0
- package/dist/types.d.ts +11 -0
- package/dist/ui/dashboard.html +55 -32
- package/dist/ui/settings.html +200 -60
- package/dist/ui/view.html +147 -30
- package/dist/usage/format-rich.d.ts +1 -1
- package/dist/usage/format-rich.js +28 -24
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +7 -0
- package/dist/usage/i18n-keys.js +34 -0
- package/dist/usage/i18n-keys.js.map +1 -0
- package/dist/usage/i18n.d.ts +5 -0
- package/dist/usage/i18n.js +27 -0
- package/dist/usage/i18n.js.map +1 -0
- package/dist/usage/providers.d.ts +21 -0
- package/dist/usage/providers.js +153 -75
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +11 -3
- package/dist/usage/usage-api.js +61 -24
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/workflow-templates/default.md +2 -1
- package/package.json +2 -2
package/dist/topic-commands.js
CHANGED
|
@@ -8,9 +8,10 @@ const execAsync = promisify(exec);
|
|
|
8
8
|
import { DEFAULT_INSTANCE_CONFIG } from "./config.js";
|
|
9
9
|
import { formatCents } from "./cost-guard.js";
|
|
10
10
|
import { getTmuxSocketName, getTmuxSessionName } from "./paths.js";
|
|
11
|
-
import { t } from "./locale.js";
|
|
11
|
+
import { t, getLocale } from "./locale.js";
|
|
12
12
|
import { clampContextPercent, parseContextPercent, parseTokenContextRatio, } from "./context-percent.js";
|
|
13
|
-
import {
|
|
13
|
+
import { isGeneralInstance } from "./general-instance.js";
|
|
14
|
+
import { backendSupportsSteer } from "./steer-capability.js";
|
|
14
15
|
export { parseContextPercent, parseTokenContextRatio } from "./context-percent.js";
|
|
15
16
|
/** Sanitize a directory name into a valid instance name. Keeps Unicode letters (incl. CJK). */
|
|
16
17
|
export function sanitizeInstanceName(name) {
|
|
@@ -19,6 +20,8 @@ export function sanitizeInstanceName(name) {
|
|
|
19
20
|
}
|
|
20
21
|
/** Allowed filename for /save and /load (no path separators, no shell/inject chars). */
|
|
21
22
|
export const SAVE_FILENAME_RE = /^[\w.-]+$/;
|
|
23
|
+
/** Backends with a native side-question command that does not steer the active turn. */
|
|
24
|
+
export const BTW_SUPPORTED_BACKENDS = new Set(["claude-code"]);
|
|
22
25
|
/**
|
|
23
26
|
* Build the backend-specific session-save command, or null if the backend has no
|
|
24
27
|
* /save equivalent. kiro-cli → `/chat save <name>`; claude-code → `/export <name>.md`.
|
|
@@ -36,7 +39,8 @@ export function parseSaveFilename(text) {
|
|
|
36
39
|
return (m?.[1] ?? "").trim();
|
|
37
40
|
}
|
|
38
41
|
/** Shared message when a backend doesn't support /save. */
|
|
39
|
-
|
|
42
|
+
/** @deprecated Prefer t("save.unsupported") at the user-facing call site. */
|
|
43
|
+
export const SAVE_UNSUPPORTED_MSG = "⚠️ /save is not supported for this backend (only Kiro CLI and Claude Code).";
|
|
40
44
|
export function parsePauseWakeCommand(text) {
|
|
41
45
|
const match = text.match(/^\/(pause|wake)(?:@\S+)?(?:\s+(\S+))?$/);
|
|
42
46
|
if (!match)
|
|
@@ -103,7 +107,8 @@ export function clearCommandForBackend(backend) {
|
|
|
103
107
|
return null;
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
|
-
|
|
110
|
+
/** @deprecated Prefer t("clear.unsupported") at the user-facing call site. */
|
|
111
|
+
export const CLEAR_UNSUPPORTED_MSG = "⚠️ Clear is not supported for this backend.";
|
|
107
112
|
/**
|
|
108
113
|
* Extract context-usage % from a captured CLI pane. Scans bottom-up so the
|
|
109
114
|
* MOST RECENT prompt wins (a captured scrollback may hold several). Covers the
|
|
@@ -137,6 +142,29 @@ export function readStatuslineContextPct(dataDir, instanceName) {
|
|
|
137
142
|
return null;
|
|
138
143
|
}
|
|
139
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Claude Code's statusline payload reports the model that actually served the
|
|
147
|
+
* current session. This can differ from fleet.yaml after the CLI rejects a
|
|
148
|
+
* plan-gated model and keeps using its previous/default model.
|
|
149
|
+
*/
|
|
150
|
+
export function readStatuslineModel(dataDir, instanceName) {
|
|
151
|
+
try {
|
|
152
|
+
const statusFile = join(dataDir, "instances", instanceName, "statusline.json");
|
|
153
|
+
if (!existsSync(statusFile))
|
|
154
|
+
return null;
|
|
155
|
+
const data = JSON.parse(readFileSync(statusFile, "utf-8"));
|
|
156
|
+
const id = typeof data.model?.id === "string" ? data.model.id.trim() : "";
|
|
157
|
+
const displayName = typeof data.model?.display_name === "string"
|
|
158
|
+
? data.model.display_name.trim()
|
|
159
|
+
: "";
|
|
160
|
+
if (displayName && id && displayName !== id)
|
|
161
|
+
return `${displayName} (${id})`;
|
|
162
|
+
return id || displayName || null;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
140
168
|
/** Live-pane scrape used by /ctx, /status Ctx, and /view sidebar. */
|
|
141
169
|
export function scrapePaneContext(instanceName, backend) {
|
|
142
170
|
try {
|
|
@@ -270,6 +298,20 @@ export class TopicCommands {
|
|
|
270
298
|
await this.handleUsageCommand(msg);
|
|
271
299
|
return true;
|
|
272
300
|
}
|
|
301
|
+
if (text === "/tips" || text.startsWith("/tips ") || text.startsWith("/tips@")) {
|
|
302
|
+
await this.handleTipsCommand(msg);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
if (text === "/login" || text.startsWith("/login ") || text.startsWith("/login@")) {
|
|
306
|
+
await this.handleLoginCommand(msg);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
// Telegram command names cannot contain "-", so the menu registers
|
|
310
|
+
// /install_cli; accept both spellings as typed text.
|
|
311
|
+
if (/^\/install[-_]cli(?:@\S+)?(?:\s|$)/.test(text)) {
|
|
312
|
+
await this.handleInstallCliCommand(msg);
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
273
315
|
if (text === "/update" || text.startsWith("/update@")) {
|
|
274
316
|
await this.handleUpdateCommand(msg);
|
|
275
317
|
return true;
|
|
@@ -332,6 +374,13 @@ export class TopicCommands {
|
|
|
332
374
|
const text = msg.text?.trim();
|
|
333
375
|
if (!text)
|
|
334
376
|
return false;
|
|
377
|
+
// Tips are informational and should appear where requested, including a
|
|
378
|
+
// worker topic. This also keeps Telegram text commands aligned with
|
|
379
|
+
// Discord's channel-local slash-command behavior.
|
|
380
|
+
if (text === "/tips" || text.startsWith("/tips ") || text.startsWith("/tips@")) {
|
|
381
|
+
await this.handleTipsCommand(msg);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
335
384
|
const pauseWake = parsePauseWakeCommand(text);
|
|
336
385
|
if (pauseWake) {
|
|
337
386
|
const adapter = this.getReplyAdapter(msg);
|
|
@@ -352,7 +401,7 @@ export class TopicCommands {
|
|
|
352
401
|
return true;
|
|
353
402
|
}
|
|
354
403
|
if (pauseWake.action === "pause" && isGeneralInstance(this.ctx.fleetConfig, target)) {
|
|
355
|
-
await adapter.sendText(msg.chatId,
|
|
404
|
+
await adapter.sendText(msg.chatId, t("general.pause_forbidden"), { threadId: msg.threadId });
|
|
356
405
|
return true;
|
|
357
406
|
}
|
|
358
407
|
await adapter.sendText(msg.chatId, await this.runPauseWake(target, pauseWake.action), { threadId: msg.threadId });
|
|
@@ -379,7 +428,7 @@ export class TopicCommands {
|
|
|
379
428
|
const level = text.replace(/^\/effort(@\S+)?/, "").trim();
|
|
380
429
|
if (level) {
|
|
381
430
|
const reply = await this.ctx.applyEffort?.(instanceName, level)
|
|
382
|
-
?? "
|
|
431
|
+
?? t("effort.unsupported", this.effectiveBackend(instanceName));
|
|
383
432
|
await adapter.sendText(msg.chatId, reply, { threadId: msg.threadId });
|
|
384
433
|
}
|
|
385
434
|
else if (this.ctx.promptEffortMenu) {
|
|
@@ -410,7 +459,7 @@ export class TopicCommands {
|
|
|
410
459
|
await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
|
|
411
460
|
}
|
|
412
461
|
else {
|
|
413
|
-
await adapter.sendText(msg.chatId, "
|
|
462
|
+
await adapter.sendText(msg.chatId, t("model.usage"), { threadId: msg.threadId });
|
|
414
463
|
}
|
|
415
464
|
return true;
|
|
416
465
|
}
|
|
@@ -422,6 +471,39 @@ export class TopicCommands {
|
|
|
422
471
|
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
423
472
|
return true;
|
|
424
473
|
}
|
|
474
|
+
if (text === "/steer" || text.startsWith("/steer ") || text.startsWith("/steer@")) {
|
|
475
|
+
const adapter = this.getReplyAdapter(msg);
|
|
476
|
+
if (!adapter)
|
|
477
|
+
return false;
|
|
478
|
+
// Deliberately NOT admin-gated: anyone who can speak in this topic can
|
|
479
|
+
// already send the instance a message; /steer only changes WHEN it lands
|
|
480
|
+
// (mid-turn instead of after), and it goes through the full [user:]
|
|
481
|
+
// formatting — unlike /raw, which bypasses it and is gated.
|
|
482
|
+
const content = text.replace(/^\/steer(@\S+)?/, "").trim();
|
|
483
|
+
if (!content) {
|
|
484
|
+
await adapter.sendText(msg.chatId, t("steer.usage"), { threadId: msg.threadId });
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
const result = this.sendSteer(instanceName, content, msg);
|
|
488
|
+
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
if (text === "/btw" || text.startsWith("/btw ") || text.startsWith("/btw@")) {
|
|
492
|
+
const adapter = this.getReplyAdapter(msg);
|
|
493
|
+
if (!adapter)
|
|
494
|
+
return false;
|
|
495
|
+
// Like /steer, /btw is not admin-gated: anyone who can send the agent a
|
|
496
|
+
// normal message may ask a side question. Claude owns the isolation from
|
|
497
|
+
// the active turn; AgEnD only submits its native command immediately.
|
|
498
|
+
const content = text.replace(/^\/btw(@\S+)?/, "").trim();
|
|
499
|
+
if (!content) {
|
|
500
|
+
await adapter.sendText(msg.chatId, t("btw.usage"), { threadId: msg.threadId });
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
const result = this.sendBtw(instanceName, content, msg);
|
|
504
|
+
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
425
507
|
if (text === "/clear" || text.startsWith("/clear@")) {
|
|
426
508
|
const adapter = this.getReplyAdapter(msg);
|
|
427
509
|
if (!adapter)
|
|
@@ -475,7 +557,7 @@ export class TopicCommands {
|
|
|
475
557
|
}
|
|
476
558
|
async runPauseWake(instanceName, action) {
|
|
477
559
|
if (action === "pause" && isGeneralInstance(this.ctx.fleetConfig, instanceName)) {
|
|
478
|
-
return
|
|
560
|
+
return t("general.pause_forbidden");
|
|
479
561
|
}
|
|
480
562
|
try {
|
|
481
563
|
const result = await this.ctx.changeInstancePauseState(instanceName, action);
|
|
@@ -506,7 +588,10 @@ export class TopicCommands {
|
|
|
506
588
|
const contextLine = context == null ? null : formatContextUsageLine(context, tokenRatio);
|
|
507
589
|
// Effective model (resolves per-instance → fleet default → classic channel →
|
|
508
590
|
// the CLI's own default) via the shared resolver, so /ctx and /model agree.
|
|
509
|
-
const modelDisplay =
|
|
591
|
+
const modelDisplay = backend === "claude-code"
|
|
592
|
+
? readStatuslineModel(this.ctx.dataDir, instanceName)
|
|
593
|
+
?? this.ctx.modelDisplayForInstance?.(instanceName)
|
|
594
|
+
: this.ctx.modelDisplayForInstance?.(instanceName);
|
|
510
595
|
const modelLine = modelDisplay ? `\n${t("ctx.model", modelDisplay)}` : "";
|
|
511
596
|
return context != null
|
|
512
597
|
? `${contextLine}\n${t("ctx.backend", backend)}${modelLine}\n${t("ctx.instance", instanceName)}`
|
|
@@ -524,23 +609,88 @@ export class TopicCommands {
|
|
|
524
609
|
?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
525
610
|
const cmd = compactCommandForBackend(backend);
|
|
526
611
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
527
|
-
return
|
|
612
|
+
return t("compact.sent", cmd);
|
|
528
613
|
}
|
|
529
|
-
return "
|
|
614
|
+
return t("compact.not_connected");
|
|
530
615
|
}
|
|
531
616
|
/** Whether the instance backend exposes a verified full-reset command. */
|
|
532
617
|
supportsClear(instanceName) {
|
|
533
618
|
return clearCommandForBackend(this.effectiveBackend(instanceName)) !== null;
|
|
534
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* Steer: interject a message into the instance's CURRENT turn. The daemon
|
|
622
|
+
* pastes it into the busy CLI instead of queueing for idle (falling back to
|
|
623
|
+
* the queue if the TUI swallows busy input — see Daemon.steerMessage).
|
|
624
|
+
*/
|
|
625
|
+
/**
|
|
626
|
+
* Backends whose TUI accepts a busy-pane paste as steering input,
|
|
627
|
+
* live-verified: claude-code and codex buffer-then-submit at the turn
|
|
628
|
+
* boundary, grok accepts it in its input line. kiro's legacy TUI swallows
|
|
629
|
+
* the paste outright, and opencode/antigravity are unverified — for those
|
|
630
|
+
* the user gets an honest "not supported" instead of a silent queue
|
|
631
|
+
* fallback that looks like a steer but behaves like a normal message.
|
|
632
|
+
*/
|
|
633
|
+
sendSteer(instanceName, content, msg) {
|
|
634
|
+
const backend = this.effectiveBackend(instanceName);
|
|
635
|
+
if (!backendSupportsSteer(backend)) {
|
|
636
|
+
return t("steer.unsupported", backend);
|
|
637
|
+
}
|
|
638
|
+
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
639
|
+
if (!ipc?.connected)
|
|
640
|
+
return t("steer.not_connected");
|
|
641
|
+
ipc.send({
|
|
642
|
+
type: "steer",
|
|
643
|
+
content,
|
|
644
|
+
delivery_epoch: this.ctx.getDeliveryEpoch?.(instanceName) ?? undefined,
|
|
645
|
+
meta: {
|
|
646
|
+
chat_id: msg.chatId,
|
|
647
|
+
message_id: msg.messageId ?? "",
|
|
648
|
+
user: msg.username,
|
|
649
|
+
user_id: msg.userId,
|
|
650
|
+
thread_id: msg.threadId ?? "",
|
|
651
|
+
adapter_id: msg.adapterId ?? "",
|
|
652
|
+
source: msg.source,
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
return t("steer.sent", instanceName);
|
|
656
|
+
}
|
|
657
|
+
/** Ask a Claude Code side question without folding it into the active turn. */
|
|
658
|
+
sendBtw(instanceName, content, msg) {
|
|
659
|
+
const backend = this.effectiveBackend(instanceName);
|
|
660
|
+
if (!BTW_SUPPORTED_BACKENDS.has(backend))
|
|
661
|
+
return t("btw.unsupported", backend);
|
|
662
|
+
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
663
|
+
if (!ipc?.connected)
|
|
664
|
+
return t("btw.not_connected");
|
|
665
|
+
ipc.send({
|
|
666
|
+
type: "btw",
|
|
667
|
+
content,
|
|
668
|
+
delivery_epoch: this.ctx.getDeliveryEpoch?.(instanceName) ?? undefined,
|
|
669
|
+
meta: {
|
|
670
|
+
chat_id: msg.chatId,
|
|
671
|
+
message_id: msg.messageId ?? "",
|
|
672
|
+
user: msg.username,
|
|
673
|
+
user_id: msg.userId,
|
|
674
|
+
thread_id: msg.threadId ?? "",
|
|
675
|
+
adapter_id: msg.adapterId ?? "",
|
|
676
|
+
source: msg.source,
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
return t("btw.sent", instanceName);
|
|
680
|
+
}
|
|
535
681
|
/** Send the backend-appropriate full conversation reset through raw_paste. */
|
|
536
682
|
async sendClear(instanceName) {
|
|
537
683
|
const backend = this.effectiveBackend(instanceName);
|
|
538
684
|
const cmd = clearCommandForBackend(backend);
|
|
539
685
|
if (!cmd)
|
|
540
|
-
return
|
|
686
|
+
return t("clear.unsupported");
|
|
541
687
|
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
542
688
|
if (ipc?.connected) {
|
|
543
|
-
ipc.send({
|
|
689
|
+
ipc.send({
|
|
690
|
+
type: "raw_paste",
|
|
691
|
+
content: cmd,
|
|
692
|
+
...(backend === "kiro-cli" ? { confirm_clear: true } : {}),
|
|
693
|
+
});
|
|
544
694
|
return t("clear.sent", cmd);
|
|
545
695
|
}
|
|
546
696
|
return t("clear.not_connected");
|
|
@@ -555,13 +705,13 @@ export class TopicCommands {
|
|
|
555
705
|
?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
556
706
|
const cmd = saveCommandForBackend(backend, filename);
|
|
557
707
|
if (!cmd)
|
|
558
|
-
return
|
|
708
|
+
return t("save.unsupported");
|
|
559
709
|
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
560
710
|
if (ipc?.connected) {
|
|
561
711
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
562
|
-
return
|
|
712
|
+
return t("save.sent", cmd, instanceName);
|
|
563
713
|
}
|
|
564
|
-
return "
|
|
714
|
+
return t("save.not_connected");
|
|
565
715
|
}
|
|
566
716
|
async handleRestartCommand(msg) {
|
|
567
717
|
const adapter = this.getReplyAdapter(msg);
|
|
@@ -578,8 +728,8 @@ export class TopicCommands {
|
|
|
578
728
|
process.kill(process.pid, "SIGUSR2");
|
|
579
729
|
}
|
|
580
730
|
/**
|
|
581
|
-
* `/usage` — AI subscription usage for
|
|
582
|
-
*
|
|
731
|
+
* `/usage` — AI subscription usage for CLI backends used by running or paused
|
|
732
|
+
* instances, one compact message. Same permission level as /ctx (none): whoever
|
|
583
733
|
* can talk to the fleet may see how much headroom it has left. Vendor rate
|
|
584
734
|
* limits are protected by the shared 5-minute cache, not by gating callers.
|
|
585
735
|
*/
|
|
@@ -590,7 +740,7 @@ export class TopicCommands {
|
|
|
590
740
|
try {
|
|
591
741
|
const { getUsageSnapshot } = await import("./usage/usage-api.js");
|
|
592
742
|
const { renderUsageHtml, renderUsageMarkdown } = await import("./usage/format-rich.js");
|
|
593
|
-
const payload = await getUsageSnapshot();
|
|
743
|
+
const payload = await getUsageSnapshot(false, this.ctx.getActiveUsageProviderIds?.());
|
|
594
744
|
// Each platform gets its native rich format: Telegram needs parse_mode
|
|
595
745
|
// HTML; Discord renders Markdown in plain content. Anything else falls back
|
|
596
746
|
// to Markdown, which degrades to readable text.
|
|
@@ -602,7 +752,140 @@ export class TopicCommands {
|
|
|
602
752
|
}
|
|
603
753
|
}
|
|
604
754
|
catch (err) {
|
|
605
|
-
await adapter.sendText(msg.chatId,
|
|
755
|
+
await adapter.sendText(msg.chatId, t("usage.failed", err.message), { threadId: msg.threadId });
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* `/login [backend] | code <text> | cancel` — remote CLI re-login.
|
|
760
|
+
* Fleet-admin only: the flow emits live authorization URLs/codes, and anyone
|
|
761
|
+
* completing one binds their own account to this fleet's CLI.
|
|
762
|
+
*/
|
|
763
|
+
async handleLoginCommand(msg) {
|
|
764
|
+
const adapter = this.getReplyAdapter(msg);
|
|
765
|
+
if (!adapter)
|
|
766
|
+
return;
|
|
767
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
768
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (!this.ctx.startLoginSession || !this.ctx.promptLoginBackends
|
|
772
|
+
|| !this.ctx.loginSubmitInput || !this.ctx.cancelLoginSession) {
|
|
773
|
+
await adapter.sendText(msg.chatId, t("login.no_session"), { threadId: msg.threadId });
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const arg = msg.text.trim().replace(/^\/login(?:@\S+)?/, "").trim();
|
|
777
|
+
const chat = {
|
|
778
|
+
adapter,
|
|
779
|
+
adapterId: msg.adapterId ?? adapter.id,
|
|
780
|
+
chatId: msg.chatId,
|
|
781
|
+
threadId: msg.threadId,
|
|
782
|
+
};
|
|
783
|
+
if (!arg) {
|
|
784
|
+
await this.ctx.promptLoginBackends(chat);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (arg === "cancel") {
|
|
788
|
+
await adapter.sendText(msg.chatId, await this.ctx.cancelLoginSession(), { threadId: msg.threadId });
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (arg.startsWith("code ")) {
|
|
792
|
+
const input = arg.slice(5).trim();
|
|
793
|
+
const reply = input ? await this.ctx.loginSubmitInput(input) : t("login.usage");
|
|
794
|
+
await adapter.sendText(msg.chatId, reply, { threadId: msg.threadId });
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (/\s/.test(arg)) {
|
|
798
|
+
await adapter.sendText(msg.chatId, t("login.usage"), { threadId: msg.threadId });
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const started = await this.ctx.startLoginSession(arg, chat);
|
|
802
|
+
// null → the still-valid-auth confirmation prompt was posted instead.
|
|
803
|
+
if (started)
|
|
804
|
+
await adapter.sendText(msg.chatId, started, { threadId: msg.threadId });
|
|
805
|
+
}
|
|
806
|
+
/** `/install-cli <backend> | cancel` — remote CLI install. Fleet-admin only. */
|
|
807
|
+
async handleInstallCliCommand(msg) {
|
|
808
|
+
const adapter = this.getReplyAdapter(msg);
|
|
809
|
+
if (!adapter)
|
|
810
|
+
return;
|
|
811
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
812
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
if (!this.ctx.startInstallSession || !this.ctx.cancelInstallSession) {
|
|
816
|
+
await adapter.sendText(msg.chatId, t("install.no_session"), { threadId: msg.threadId });
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const arg = msg.text.trim().replace(/^\/install[-_]cli(?:@\S+)?/, "").trim();
|
|
820
|
+
// Bare call → offer the backends, matching bare `/login`. A multi-word arg
|
|
821
|
+
// is still a usage error: it means they typed something, just not a backend.
|
|
822
|
+
if (!arg && this.ctx.promptInstallBackends) {
|
|
823
|
+
await this.ctx.promptInstallBackends({
|
|
824
|
+
adapter,
|
|
825
|
+
adapterId: msg.adapterId ?? adapter.id,
|
|
826
|
+
chatId: msg.chatId,
|
|
827
|
+
threadId: msg.threadId,
|
|
828
|
+
});
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (!arg || /\s/.test(arg)) {
|
|
832
|
+
await adapter.sendText(msg.chatId, t("install.usage"), { threadId: msg.threadId });
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (arg === "cancel") {
|
|
836
|
+
await adapter.sendText(msg.chatId, await this.ctx.cancelInstallSession(), { threadId: msg.threadId });
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const chat = {
|
|
840
|
+
adapter,
|
|
841
|
+
adapterId: msg.adapterId ?? adapter.id,
|
|
842
|
+
chatId: msg.chatId,
|
|
843
|
+
threadId: msg.threadId,
|
|
844
|
+
};
|
|
845
|
+
await adapter.sendText(msg.chatId, await this.ctx.startInstallSession(arg, chat), { threadId: msg.threadId });
|
|
846
|
+
}
|
|
847
|
+
async handleTipsCommand(msg) {
|
|
848
|
+
const adapter = this.getReplyAdapter(msg);
|
|
849
|
+
if (!adapter || !this.ctx.fleetConfig)
|
|
850
|
+
return;
|
|
851
|
+
const mode = msg.text.trim().replace(/^\/tips(?:@\S+)?/, "").trim().toLowerCase();
|
|
852
|
+
if (mode === "advanced on") {
|
|
853
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
854
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
await adapter.sendText(msg.chatId, this.ctx.unlockAdvancedTips?.(msg.userId)
|
|
858
|
+
? t("tips.advanced.unlocked")
|
|
859
|
+
: t("tips.unavailable"), { threadId: msg.threadId });
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (mode === "on" || mode === "off") {
|
|
863
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
864
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
this.ctx.fleetConfig.defaults.tips = mode === "on";
|
|
868
|
+
this.ctx.saveFleetConfig();
|
|
869
|
+
await adapter.sendText(msg.chatId, t(mode === "on" ? "tips.enabled" : "tips.disabled"), {
|
|
870
|
+
threadId: msg.threadId,
|
|
871
|
+
});
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (mode) {
|
|
875
|
+
await adapter.sendText(msg.chatId, t("tips.usage"), { threadId: msg.threadId });
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const result = await this.ctx.promptTip?.(
|
|
879
|
+
// The instance name scopes prompt cleanup; the Tip is posted at the
|
|
880
|
+
// caller's current chat/thread below.
|
|
881
|
+
Object.entries(this.ctx.fleetConfig.instances)
|
|
882
|
+
.find(([, config]) => config.general_topic === true
|
|
883
|
+
&& (!config.channel_id || config.channel_id === msg.adapterId))?.[0] ?? "general", adapter, msg.chatId, msg.threadId) ?? "unavailable";
|
|
884
|
+
if (result === "empty") {
|
|
885
|
+
await adapter.sendText(msg.chatId, t("tips.empty"), { threadId: msg.threadId });
|
|
886
|
+
}
|
|
887
|
+
else if (result === "unavailable") {
|
|
888
|
+
await adapter.sendText(msg.chatId, t("tips.unavailable"), { threadId: msg.threadId });
|
|
606
889
|
}
|
|
607
890
|
}
|
|
608
891
|
async handleStatusCommand(msg) {
|
|
@@ -631,7 +914,7 @@ export class TopicCommands {
|
|
|
631
914
|
/** Get fleet status as markdown text (shared by TG + DC) */
|
|
632
915
|
async getStatusText() {
|
|
633
916
|
if (!this.ctx.fleetConfig)
|
|
634
|
-
return "
|
|
917
|
+
return t("status.no_config");
|
|
635
918
|
const rows = [];
|
|
636
919
|
let pausedCount = 0;
|
|
637
920
|
const fleetNames = new Set(Object.keys(this.ctx.fleetConfig.instances));
|
|
@@ -668,10 +951,10 @@ export class TopicCommands {
|
|
|
668
951
|
icon = "🔴";
|
|
669
952
|
else
|
|
670
953
|
icon = "⚪";
|
|
671
|
-
const stateLabel = executionState === "idle" ? "
|
|
672
|
-
: executionState === "working" ? "
|
|
673
|
-
: executionState === "stuck" ? "
|
|
674
|
-
: executionState === "paused" ? "
|
|
954
|
+
const stateLabel = executionState === "idle" ? `🟢 ${t("state.idle")}`
|
|
955
|
+
: executionState === "working" ? `🔵 ${t("state.working")}`
|
|
956
|
+
: executionState === "stuck" ? `🔴 ${t("state.stuck")}`
|
|
957
|
+
: executionState === "paused" ? `⏸ ${t("state.paused")}`
|
|
675
958
|
: "—";
|
|
676
959
|
const displayName = this.shortInstanceName(name);
|
|
677
960
|
// IPC reachability moved here from /sysinfo: it is per-instance health, and
|
|
@@ -683,27 +966,27 @@ export class TopicCommands {
|
|
|
683
966
|
rows.push(`| ${displayName} | ${backend} | ${contextStr} | ${effort} | ${formatCents(costCents)} | ${icon} | ${stateLabel} | ${ipc} |`);
|
|
684
967
|
}
|
|
685
968
|
if (rows.length === 0)
|
|
686
|
-
return "
|
|
969
|
+
return t("status.no_instances");
|
|
687
970
|
const lines = [
|
|
688
|
-
|
|
971
|
+
`## ${t("status.title")}`,
|
|
689
972
|
"",
|
|
690
|
-
"
|
|
973
|
+
t("status.table_header"),
|
|
691
974
|
"|----------|---------|-----|--------|------|--------|-------|-----|",
|
|
692
975
|
...rows,
|
|
693
976
|
"",
|
|
694
|
-
|
|
977
|
+
t("status.paused_count", pausedCount),
|
|
695
978
|
];
|
|
696
979
|
const limitCents = this.ctx.costGuard?.getLimitCents() ?? 0;
|
|
697
980
|
const totalCents = this.ctx.costGuard?.getFleetTotalCents() ?? 0;
|
|
698
981
|
if (limitCents > 0) {
|
|
699
|
-
lines.push("",
|
|
982
|
+
lines.push("", t("status.daily_cost", formatCents(totalCents), formatCents(limitCents)));
|
|
700
983
|
}
|
|
701
984
|
// Adapter states (only show if any are not connected)
|
|
702
985
|
const adapterStates = this.ctx.getAdapterStates?.();
|
|
703
986
|
if (adapterStates && adapterStates.size > 0) {
|
|
704
987
|
const issues = [...adapterStates.entries()].filter(([, s]) => s.status !== "connected");
|
|
705
988
|
if (issues.length > 0) {
|
|
706
|
-
lines.push("", "
|
|
989
|
+
lines.push("", `**${t("status.adapters")}**`);
|
|
707
990
|
for (const [id, s] of adapterStates) {
|
|
708
991
|
const icon = s.status === "connected" ? "✅" : s.status === "retrying" ? "🔄" : "❌";
|
|
709
992
|
lines.push(` ${icon} ${id}: ${s.status}${s.lastError ? ` (${s.lastError.slice(0, 60)})` : ""}`);
|
|
@@ -716,35 +999,73 @@ export class TopicCommands {
|
|
|
716
999
|
const adapter = this.getReplyAdapter(msg);
|
|
717
1000
|
if (!adapter)
|
|
718
1001
|
return;
|
|
719
|
-
const
|
|
1002
|
+
const platform = adapter.type === "discord" ? "discord" : "telegram";
|
|
1003
|
+
const text = this.getSysInfoText({ platform });
|
|
720
1004
|
await adapter.sendText(msg.chatId, text, { threadId: msg.threadId });
|
|
721
1005
|
}
|
|
722
1006
|
/**
|
|
723
|
-
* Get system info as
|
|
1007
|
+
* Get system info as formatted text.
|
|
724
1008
|
*
|
|
725
1009
|
* System-level only. The per-instance table (state/IPC/cost) moved to /status,
|
|
726
1010
|
* which was already the fleet-per-instance view — the two tables overlapped on
|
|
727
1011
|
* everything but the IPC column, and each command answered half of "is the
|
|
728
1012
|
* machine fine and are the instances fine".
|
|
1013
|
+
*
|
|
1014
|
+
* @param opts.platform - "telegram" uses markdown table, "discord" uses plain lines
|
|
729
1015
|
*/
|
|
730
|
-
getSysInfoText() {
|
|
1016
|
+
getSysInfoText(opts) {
|
|
731
1017
|
const info = this.ctx.getSysInfo();
|
|
732
1018
|
const upHours = Math.floor(info.uptime_seconds / 3600);
|
|
733
1019
|
const upMins = Math.floor((info.uptime_seconds % 3600) / 60);
|
|
734
1020
|
const require = createRequire(import.meta.url);
|
|
735
1021
|
const agendVersion = require("../package.json").version ?? "unknown";
|
|
1022
|
+
const tipsLang = getLocale() === "zh-TW" ? "zh" : "en";
|
|
1023
|
+
// Fleet summary lines (multi-line for mobile readability)
|
|
1024
|
+
const summaryLines = [
|
|
1025
|
+
`${t("sysinfo.instances")}: ${info.running_count} ${t("sysinfo.running")}, ${info.paused_count} ${t("sysinfo.paused")}`,
|
|
1026
|
+
...(info.fleet_mem_mb !== null ? [`${t("sysinfo.fleet_mem")}: ${(info.fleet_mem_mb / 1024).toFixed(1)} GB`] : []),
|
|
1027
|
+
`${t("sysinfo.system_mem")}: ${info.system_mem_gb.used} / ${info.system_mem_gb.total} GB`,
|
|
1028
|
+
];
|
|
1029
|
+
const resources = [
|
|
1030
|
+
`📚 **${t("sysinfo.resources")}**`,
|
|
1031
|
+
`- ${t("sysinfo.docs")}: https://songsid.github.io/AgEnD`,
|
|
1032
|
+
`- ${t("sysinfo.tips")}: https://songsid.github.io/AgEnD/tips-${tipsLang}.html`,
|
|
1033
|
+
`- ${t("sysinfo.github")}: https://github.com/songsid/AgEnD`,
|
|
1034
|
+
];
|
|
1035
|
+
// Discord: plain key-value lines (Discord doesn't render markdown tables)
|
|
1036
|
+
if (opts?.platform === "discord") {
|
|
1037
|
+
return [
|
|
1038
|
+
`## ${t("sysinfo.title")}`,
|
|
1039
|
+
`AgEnD: v${agendVersion}`,
|
|
1040
|
+
`OS: ${process.platform} ${osRelease()} (${process.arch})`,
|
|
1041
|
+
`Node: ${process.version}`,
|
|
1042
|
+
`tmux: ${tmuxVersion()}`,
|
|
1043
|
+
`${t("sysinfo.uptime")}: ${upHours}h ${upMins}m`,
|
|
1044
|
+
`${t("sysinfo.memory")}: ${info.memory_mb.rss} MB RSS`,
|
|
1045
|
+
`${t("sysinfo.heap")}: ${info.memory_mb.heapUsed} / ${info.memory_mb.heapTotal} MB`,
|
|
1046
|
+
"",
|
|
1047
|
+
...summaryLines,
|
|
1048
|
+
"",
|
|
1049
|
+
...resources,
|
|
1050
|
+
].join("\n");
|
|
1051
|
+
}
|
|
1052
|
+
// Telegram (default): markdown table
|
|
736
1053
|
return [
|
|
737
|
-
|
|
1054
|
+
`## ${t("sysinfo.title")}`,
|
|
738
1055
|
"",
|
|
739
|
-
"
|
|
1056
|
+
`| ${t("sysinfo.metric")} | ${t("sysinfo.value")} |`,
|
|
740
1057
|
"|--------|-------|",
|
|
741
1058
|
`| AgEnD | v${agendVersion} |`,
|
|
742
1059
|
`| OS | ${process.platform} ${osRelease()} (${process.arch}) |`,
|
|
743
1060
|
`| Node | ${process.version} |`,
|
|
744
1061
|
`| tmux | ${tmuxVersion()} |`,
|
|
745
|
-
`|
|
|
746
|
-
`|
|
|
747
|
-
`|
|
|
1062
|
+
`| ${t("sysinfo.uptime")} | ${upHours}h ${upMins}m |`,
|
|
1063
|
+
`| ${t("sysinfo.memory")} | ${info.memory_mb.rss} MB RSS |`,
|
|
1064
|
+
`| ${t("sysinfo.heap")} | ${info.memory_mb.heapUsed} / ${info.memory_mb.heapTotal} MB |`,
|
|
1065
|
+
"",
|
|
1066
|
+
...summaryLines,
|
|
1067
|
+
"",
|
|
1068
|
+
...resources,
|
|
748
1069
|
].join("\n");
|
|
749
1070
|
}
|
|
750
1071
|
async handleUpdateCommand(msg) {
|
|
@@ -797,11 +1118,11 @@ export class TopicCommands {
|
|
|
797
1118
|
timeout: 30_000,
|
|
798
1119
|
encoding: "utf-8",
|
|
799
1120
|
});
|
|
800
|
-
output = stripAnsi(stdout) || "
|
|
1121
|
+
output = stripAnsi(stdout) || t("doctor.no_output");
|
|
801
1122
|
}
|
|
802
1123
|
catch (err) {
|
|
803
1124
|
const e = err;
|
|
804
|
-
output = stripAnsi(e.stdout ?? e.message ?? "
|
|
1125
|
+
output = stripAnsi(e.stdout ?? e.message ?? t("doctor.failed"));
|
|
805
1126
|
}
|
|
806
1127
|
await adapter.sendText(chatId, output, { threadId });
|
|
807
1128
|
}
|
|
@@ -810,7 +1131,7 @@ export class TopicCommands {
|
|
|
810
1131
|
const adapter = this.getReplyAdapter(msg);
|
|
811
1132
|
if (!adapter)
|
|
812
1133
|
return;
|
|
813
|
-
await adapter.sendText(msg.chatId, "
|
|
1134
|
+
await adapter.sendText(msg.chatId, t("topic.unbound"), { threadId: msg.threadId });
|
|
814
1135
|
}
|
|
815
1136
|
/** Handle topic deletion — stop daemon and remove from config */
|
|
816
1137
|
async handleTopicDeleted(threadId) {
|
|
@@ -923,21 +1244,26 @@ export class TopicCommands {
|
|
|
923
1244
|
}
|
|
924
1245
|
try {
|
|
925
1246
|
const fleetCommands = [
|
|
926
|
-
{ command: "status", description: "🔒
|
|
927
|
-
{ command: "sysinfo", description: "
|
|
928
|
-
{ command: "dashboard", description: "🔒
|
|
929
|
-
{ command: "ctx", description: "
|
|
930
|
-
{ command: "compact", description: "
|
|
931
|
-
{ command: "
|
|
932
|
-
{ command: "
|
|
933
|
-
{ command: "
|
|
934
|
-
{ command: "
|
|
935
|
-
{ command: "
|
|
936
|
-
{ command: "
|
|
937
|
-
{ command: "
|
|
938
|
-
{ command: "
|
|
939
|
-
{ command: "
|
|
940
|
-
{ command: "
|
|
1247
|
+
{ command: "status", description: "🔒 " + t("slash.status") },
|
|
1248
|
+
{ command: "sysinfo", description: t("slash.sysinfo") },
|
|
1249
|
+
{ command: "dashboard", description: "🔒 " + t("slash.dashboard") },
|
|
1250
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
1251
|
+
{ command: "compact", description: t("slash.compact") },
|
|
1252
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1253
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1254
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1255
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1256
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1257
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1258
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1259
|
+
{ command: "restart", description: "🔒 " + t("slash.restart") },
|
|
1260
|
+
{ command: "collab", description: "🔒 " + t("slash.collab") },
|
|
1261
|
+
{ command: "update", description: "🔒 " + t("slash.update") },
|
|
1262
|
+
{ command: "doctor", description: "🔒 " + t("slash.doctor") },
|
|
1263
|
+
{ command: "login", description: "🔒 " + t("slash.login") },
|
|
1264
|
+
{ command: "install_cli", description: "🔒 " + t("slash.install_cli") },
|
|
1265
|
+
{ command: "usage", description: t("slash.usage") },
|
|
1266
|
+
{ command: "tips", description: t("slash.tips") },
|
|
941
1267
|
];
|
|
942
1268
|
const setCommands = async (commands, scope) => {
|
|
943
1269
|
const response = await fetch(`https://api.telegram.org/bot${botToken}/setMyCommands`, {
|
|
@@ -958,15 +1284,17 @@ export class TopicCommands {
|
|
|
958
1284
|
}
|
|
959
1285
|
};
|
|
960
1286
|
const classicCommands = [
|
|
961
|
-
{ command: "start", description: "🔒
|
|
962
|
-
{ command: "stop", description: "🔒
|
|
963
|
-
{ command: "compact", description: "🔒
|
|
964
|
-
{ command: "
|
|
965
|
-
{ command: "
|
|
966
|
-
{ command: "
|
|
967
|
-
{ command: "
|
|
968
|
-
{ command: "
|
|
969
|
-
{ command: "
|
|
1287
|
+
{ command: "start", description: "🔒 " + t("slash.start") },
|
|
1288
|
+
{ command: "stop", description: "🔒 " + t("slash.stop") },
|
|
1289
|
+
{ command: "compact", description: "🔒 " + t("slash.compact") },
|
|
1290
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1291
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1292
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1293
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1294
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1295
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1296
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1297
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
970
1298
|
];
|
|
971
1299
|
// A chat_administrators scope has higher precedence than the chat scope.
|
|
972
1300
|
// Keep both synchronized so a stale admin-only list from BotFather or an
|