@songsid/agend 2.1.4-beta.3 → 2.1.4-beta.30
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-endpoint.js +1 -1
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/antigravity.d.ts +9 -5
- package/dist/backend/antigravity.js +155 -30
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +23 -21
- package/dist/backend/claude-code.js +47 -24
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +17 -0
- package/dist/backend/codex.js +122 -7
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro.d.ts +1 -0
- package/dist/backend/kiro.js +14 -0
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode.d.ts +27 -11
- package/dist/backend/opencode.js +48 -37
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +14 -0
- package/dist/backend/types.js +7 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.js +106 -19
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.js +5 -3
- 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/mcp-server.js +6 -20
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +4 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +1 -1
- package/dist/classic-channel-manager.d.ts +40 -3
- package/dist/classic-channel-manager.js +218 -31
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +148 -33
- package/dist/cli.js.map +1 -1
- package/dist/completion-install.d.ts +70 -0
- package/dist/completion-install.js +152 -0
- package/dist/completion-install.js.map +1 -0
- package/dist/completion.d.ts +7 -1
- package/dist/completion.js +14 -4
- package/dist/completion.js.map +1 -1
- package/dist/config-validator.js +38 -7
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +10 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +173 -5
- package/dist/daemon.js +721 -83
- package/dist/daemon.js.map +1 -1
- package/dist/doctor.d.ts +26 -0
- package/dist/doctor.js +216 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fleet-context.d.ts +47 -0
- package/dist/fleet-manager.d.ts +247 -10
- package/dist/fleet-manager.js +1978 -384
- 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/backend-providers/SKILL.md +114 -0
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +3 -1
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +52 -0
- package/dist/general-knowledge/skills/development-workflow/SKILL.md +28 -0
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -0
- package/dist/general-knowledge/skills/fleet-health/SKILL.md +1 -0
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -0
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +1 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +64 -16
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -0
- package/dist/general-knowledge/skills/scheduling/SKILL.md +1 -0
- package/dist/general-knowledge/skills/session-management/SKILL.md +1 -0
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +1 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +30 -0
- package/dist/instance-lifecycle.d.ts +28 -0
- package/dist/instance-lifecycle.js +79 -5
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.js +35 -1
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +746 -288
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.d.ts +85 -0
- package/dist/login-flows.js +159 -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/outbound-handlers.d.ts +8 -0
- package/dist/outbound-handlers.js +44 -5
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +5 -0
- package/dist/outbound-schemas.js +7 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/quickstart.js +25 -0
- package/dist/quickstart.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/tips.d.ts +44 -0
- package/dist/tips.js +355 -0
- package/dist/tips.js.map +1 -0
- package/dist/tmux-manager.d.ts +6 -0
- package/dist/tmux-manager.js +12 -0
- 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 +36 -4
- package/dist/topic-commands.js +322 -64
- 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 +120 -51
- package/dist/ui/view.html +23 -15
- package/dist/usage/usage-api.d.ts +8 -2
- package/dist/usage/usage-api.js +24 -3
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/workflow-templates/default.md +2 -1
- package/package.json +1 -1
package/dist/topic-commands.js
CHANGED
|
@@ -10,7 +10,7 @@ import { formatCents } from "./cost-guard.js";
|
|
|
10
10
|
import { getTmuxSocketName, getTmuxSessionName } from "./paths.js";
|
|
11
11
|
import { t } from "./locale.js";
|
|
12
12
|
import { clampContextPercent, parseContextPercent, parseTokenContextRatio, } from "./context-percent.js";
|
|
13
|
-
import {
|
|
13
|
+
import { isGeneralInstance } from "./general-instance.js";
|
|
14
14
|
export { parseContextPercent, parseTokenContextRatio } from "./context-percent.js";
|
|
15
15
|
/** Sanitize a directory name into a valid instance name. Keeps Unicode letters (incl. CJK). */
|
|
16
16
|
export function sanitizeInstanceName(name) {
|
|
@@ -19,6 +19,8 @@ export function sanitizeInstanceName(name) {
|
|
|
19
19
|
}
|
|
20
20
|
/** Allowed filename for /save and /load (no path separators, no shell/inject chars). */
|
|
21
21
|
export const SAVE_FILENAME_RE = /^[\w.-]+$/;
|
|
22
|
+
/** Backends with a native side-question command that does not steer the active turn. */
|
|
23
|
+
export const BTW_SUPPORTED_BACKENDS = new Set(["claude-code"]);
|
|
22
24
|
/**
|
|
23
25
|
* Build the backend-specific session-save command, or null if the backend has no
|
|
24
26
|
* /save equivalent. kiro-cli → `/chat save <name>`; claude-code → `/export <name>.md`.
|
|
@@ -36,7 +38,8 @@ export function parseSaveFilename(text) {
|
|
|
36
38
|
return (m?.[1] ?? "").trim();
|
|
37
39
|
}
|
|
38
40
|
/** Shared message when a backend doesn't support /save. */
|
|
39
|
-
|
|
41
|
+
/** @deprecated Prefer t("save.unsupported") at the user-facing call site. */
|
|
42
|
+
export const SAVE_UNSUPPORTED_MSG = "⚠️ /save is not supported for this backend (only Kiro CLI and Claude Code).";
|
|
40
43
|
export function parsePauseWakeCommand(text) {
|
|
41
44
|
const match = text.match(/^\/(pause|wake)(?:@\S+)?(?:\s+(\S+))?$/);
|
|
42
45
|
if (!match)
|
|
@@ -103,7 +106,8 @@ export function clearCommandForBackend(backend) {
|
|
|
103
106
|
return null;
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
|
-
|
|
109
|
+
/** @deprecated Prefer t("clear.unsupported") at the user-facing call site. */
|
|
110
|
+
export const CLEAR_UNSUPPORTED_MSG = "⚠️ Clear is not supported for this backend.";
|
|
107
111
|
/**
|
|
108
112
|
* Extract context-usage % from a captured CLI pane. Scans bottom-up so the
|
|
109
113
|
* MOST RECENT prompt wins (a captured scrollback may hold several). Covers the
|
|
@@ -270,6 +274,20 @@ export class TopicCommands {
|
|
|
270
274
|
await this.handleUsageCommand(msg);
|
|
271
275
|
return true;
|
|
272
276
|
}
|
|
277
|
+
if (text === "/tips" || text.startsWith("/tips ") || text.startsWith("/tips@")) {
|
|
278
|
+
await this.handleTipsCommand(msg);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if (text === "/login" || text.startsWith("/login ") || text.startsWith("/login@")) {
|
|
282
|
+
await this.handleLoginCommand(msg);
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
// Telegram command names cannot contain "-", so the menu registers
|
|
286
|
+
// /install_cli; accept both spellings as typed text.
|
|
287
|
+
if (/^\/install[-_]cli(?:@\S+)?(?:\s|$)/.test(text)) {
|
|
288
|
+
await this.handleInstallCliCommand(msg);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
273
291
|
if (text === "/update" || text.startsWith("/update@")) {
|
|
274
292
|
await this.handleUpdateCommand(msg);
|
|
275
293
|
return true;
|
|
@@ -332,6 +350,13 @@ export class TopicCommands {
|
|
|
332
350
|
const text = msg.text?.trim();
|
|
333
351
|
if (!text)
|
|
334
352
|
return false;
|
|
353
|
+
// Tips are informational and should appear where requested, including a
|
|
354
|
+
// worker topic. This also keeps Telegram text commands aligned with
|
|
355
|
+
// Discord's channel-local slash-command behavior.
|
|
356
|
+
if (text === "/tips" || text.startsWith("/tips ") || text.startsWith("/tips@")) {
|
|
357
|
+
await this.handleTipsCommand(msg);
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
335
360
|
const pauseWake = parsePauseWakeCommand(text);
|
|
336
361
|
if (pauseWake) {
|
|
337
362
|
const adapter = this.getReplyAdapter(msg);
|
|
@@ -352,7 +377,7 @@ export class TopicCommands {
|
|
|
352
377
|
return true;
|
|
353
378
|
}
|
|
354
379
|
if (pauseWake.action === "pause" && isGeneralInstance(this.ctx.fleetConfig, target)) {
|
|
355
|
-
await adapter.sendText(msg.chatId,
|
|
380
|
+
await adapter.sendText(msg.chatId, t("general.pause_forbidden"), { threadId: msg.threadId });
|
|
356
381
|
return true;
|
|
357
382
|
}
|
|
358
383
|
await adapter.sendText(msg.chatId, await this.runPauseWake(target, pauseWake.action), { threadId: msg.threadId });
|
|
@@ -379,7 +404,7 @@ export class TopicCommands {
|
|
|
379
404
|
const level = text.replace(/^\/effort(@\S+)?/, "").trim();
|
|
380
405
|
if (level) {
|
|
381
406
|
const reply = await this.ctx.applyEffort?.(instanceName, level)
|
|
382
|
-
?? "
|
|
407
|
+
?? t("effort.unsupported", this.effectiveBackend(instanceName));
|
|
383
408
|
await adapter.sendText(msg.chatId, reply, { threadId: msg.threadId });
|
|
384
409
|
}
|
|
385
410
|
else if (this.ctx.promptEffortMenu) {
|
|
@@ -410,7 +435,7 @@ export class TopicCommands {
|
|
|
410
435
|
await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
|
|
411
436
|
}
|
|
412
437
|
else {
|
|
413
|
-
await adapter.sendText(msg.chatId, "
|
|
438
|
+
await adapter.sendText(msg.chatId, t("model.usage"), { threadId: msg.threadId });
|
|
414
439
|
}
|
|
415
440
|
return true;
|
|
416
441
|
}
|
|
@@ -422,6 +447,39 @@ export class TopicCommands {
|
|
|
422
447
|
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
423
448
|
return true;
|
|
424
449
|
}
|
|
450
|
+
if (text === "/steer" || text.startsWith("/steer ") || text.startsWith("/steer@")) {
|
|
451
|
+
const adapter = this.getReplyAdapter(msg);
|
|
452
|
+
if (!adapter)
|
|
453
|
+
return false;
|
|
454
|
+
// Deliberately NOT admin-gated: anyone who can speak in this topic can
|
|
455
|
+
// already send the instance a message; /steer only changes WHEN it lands
|
|
456
|
+
// (mid-turn instead of after), and it goes through the full [user:]
|
|
457
|
+
// formatting — unlike /raw, which bypasses it and is gated.
|
|
458
|
+
const content = text.replace(/^\/steer(@\S+)?/, "").trim();
|
|
459
|
+
if (!content) {
|
|
460
|
+
await adapter.sendText(msg.chatId, t("steer.usage"), { threadId: msg.threadId });
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
const result = this.sendSteer(instanceName, content, msg);
|
|
464
|
+
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
if (text === "/btw" || text.startsWith("/btw ") || text.startsWith("/btw@")) {
|
|
468
|
+
const adapter = this.getReplyAdapter(msg);
|
|
469
|
+
if (!adapter)
|
|
470
|
+
return false;
|
|
471
|
+
// Like /steer, /btw is not admin-gated: anyone who can send the agent a
|
|
472
|
+
// normal message may ask a side question. Claude owns the isolation from
|
|
473
|
+
// the active turn; AgEnD only submits its native command immediately.
|
|
474
|
+
const content = text.replace(/^\/btw(@\S+)?/, "").trim();
|
|
475
|
+
if (!content) {
|
|
476
|
+
await adapter.sendText(msg.chatId, t("btw.usage"), { threadId: msg.threadId });
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
const result = this.sendBtw(instanceName, content, msg);
|
|
480
|
+
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
425
483
|
if (text === "/clear" || text.startsWith("/clear@")) {
|
|
426
484
|
const adapter = this.getReplyAdapter(msg);
|
|
427
485
|
if (!adapter)
|
|
@@ -430,8 +488,13 @@ export class TopicCommands {
|
|
|
430
488
|
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
431
489
|
return true;
|
|
432
490
|
}
|
|
433
|
-
|
|
434
|
-
|
|
491
|
+
if (!this.ctx.promptClearConfirmation) {
|
|
492
|
+
await adapter.sendText(msg.chatId, t("clear.prompt_unavailable"), { threadId: msg.threadId });
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
const fallback = await this.ctx.promptClearConfirmation(instanceName, msg.threadId ?? msg.chatId, adapter, msg.chatId, msg.threadId);
|
|
496
|
+
if (fallback)
|
|
497
|
+
await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
|
|
435
498
|
return true;
|
|
436
499
|
}
|
|
437
500
|
if (text === "/cancel" || text.startsWith("/cancel@")) {
|
|
@@ -470,7 +533,7 @@ export class TopicCommands {
|
|
|
470
533
|
}
|
|
471
534
|
async runPauseWake(instanceName, action) {
|
|
472
535
|
if (action === "pause" && isGeneralInstance(this.ctx.fleetConfig, instanceName)) {
|
|
473
|
-
return
|
|
536
|
+
return t("general.pause_forbidden");
|
|
474
537
|
}
|
|
475
538
|
try {
|
|
476
539
|
const result = await this.ctx.changeInstancePauseState(instanceName, action);
|
|
@@ -519,22 +582,88 @@ export class TopicCommands {
|
|
|
519
582
|
?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
520
583
|
const cmd = compactCommandForBackend(backend);
|
|
521
584
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
522
|
-
return
|
|
585
|
+
return t("compact.sent", cmd);
|
|
586
|
+
}
|
|
587
|
+
return t("compact.not_connected");
|
|
588
|
+
}
|
|
589
|
+
/** Whether the instance backend exposes a verified full-reset command. */
|
|
590
|
+
supportsClear(instanceName) {
|
|
591
|
+
return clearCommandForBackend(this.effectiveBackend(instanceName)) !== null;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Steer: interject a message into the instance's CURRENT turn. The daemon
|
|
595
|
+
* pastes it into the busy CLI instead of queueing for idle (falling back to
|
|
596
|
+
* the queue if the TUI swallows busy input — see Daemon.steerMessage).
|
|
597
|
+
*/
|
|
598
|
+
/**
|
|
599
|
+
* Backends whose TUI accepts a busy-pane paste as steering input,
|
|
600
|
+
* live-verified: claude-code and codex buffer-then-submit at the turn
|
|
601
|
+
* boundary, grok accepts it in its input line. kiro's legacy TUI swallows
|
|
602
|
+
* the paste outright, and opencode/antigravity are unverified — for those
|
|
603
|
+
* the user gets an honest "not supported" instead of a silent queue
|
|
604
|
+
* fallback that looks like a steer but behaves like a normal message.
|
|
605
|
+
*/
|
|
606
|
+
static STEER_SUPPORTED_BACKENDS = new Set(["claude-code", "codex", "grok", "mock"]);
|
|
607
|
+
sendSteer(instanceName, content, msg) {
|
|
608
|
+
const backend = this.effectiveBackend(instanceName);
|
|
609
|
+
if (!TopicCommands.STEER_SUPPORTED_BACKENDS.has(backend)) {
|
|
610
|
+
return t("steer.unsupported", backend);
|
|
523
611
|
}
|
|
524
|
-
|
|
612
|
+
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
613
|
+
if (!ipc?.connected)
|
|
614
|
+
return t("steer.not_connected");
|
|
615
|
+
ipc.send({
|
|
616
|
+
type: "steer",
|
|
617
|
+
content,
|
|
618
|
+
delivery_epoch: this.ctx.getDeliveryEpoch?.(instanceName) ?? undefined,
|
|
619
|
+
meta: {
|
|
620
|
+
chat_id: msg.chatId,
|
|
621
|
+
message_id: msg.messageId ?? "",
|
|
622
|
+
user: msg.username,
|
|
623
|
+
user_id: msg.userId,
|
|
624
|
+
thread_id: msg.threadId ?? "",
|
|
625
|
+
adapter_id: msg.adapterId ?? "",
|
|
626
|
+
source: msg.source,
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
return t("steer.sent", instanceName);
|
|
630
|
+
}
|
|
631
|
+
/** Ask a Claude Code side question without folding it into the active turn. */
|
|
632
|
+
sendBtw(instanceName, content, msg) {
|
|
633
|
+
const backend = this.effectiveBackend(instanceName);
|
|
634
|
+
if (!BTW_SUPPORTED_BACKENDS.has(backend))
|
|
635
|
+
return t("btw.unsupported", backend);
|
|
636
|
+
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
637
|
+
if (!ipc?.connected)
|
|
638
|
+
return t("btw.not_connected");
|
|
639
|
+
ipc.send({
|
|
640
|
+
type: "btw",
|
|
641
|
+
content,
|
|
642
|
+
delivery_epoch: this.ctx.getDeliveryEpoch?.(instanceName) ?? undefined,
|
|
643
|
+
meta: {
|
|
644
|
+
chat_id: msg.chatId,
|
|
645
|
+
message_id: msg.messageId ?? "",
|
|
646
|
+
user: msg.username,
|
|
647
|
+
user_id: msg.userId,
|
|
648
|
+
thread_id: msg.threadId ?? "",
|
|
649
|
+
adapter_id: msg.adapterId ?? "",
|
|
650
|
+
source: msg.source,
|
|
651
|
+
},
|
|
652
|
+
});
|
|
653
|
+
return t("btw.sent", instanceName);
|
|
525
654
|
}
|
|
526
655
|
/** Send the backend-appropriate full conversation reset through raw_paste. */
|
|
527
656
|
async sendClear(instanceName) {
|
|
528
657
|
const backend = this.effectiveBackend(instanceName);
|
|
529
658
|
const cmd = clearCommandForBackend(backend);
|
|
530
659
|
if (!cmd)
|
|
531
|
-
return
|
|
660
|
+
return t("clear.unsupported");
|
|
532
661
|
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
533
662
|
if (ipc?.connected) {
|
|
534
663
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
535
|
-
return
|
|
664
|
+
return t("clear.sent", cmd);
|
|
536
665
|
}
|
|
537
|
-
return "
|
|
666
|
+
return t("clear.not_connected");
|
|
538
667
|
}
|
|
539
668
|
/** Send a backend-appropriate session-save command to a fleet-topic instance. */
|
|
540
669
|
async sendSave(instanceName, filename) {
|
|
@@ -546,13 +675,13 @@ export class TopicCommands {
|
|
|
546
675
|
?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
547
676
|
const cmd = saveCommandForBackend(backend, filename);
|
|
548
677
|
if (!cmd)
|
|
549
|
-
return
|
|
678
|
+
return t("save.unsupported");
|
|
550
679
|
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
551
680
|
if (ipc?.connected) {
|
|
552
681
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
553
|
-
return
|
|
682
|
+
return t("save.sent", cmd, instanceName);
|
|
554
683
|
}
|
|
555
|
-
return "
|
|
684
|
+
return t("save.not_connected");
|
|
556
685
|
}
|
|
557
686
|
async handleRestartCommand(msg) {
|
|
558
687
|
const adapter = this.getReplyAdapter(msg);
|
|
@@ -569,8 +698,8 @@ export class TopicCommands {
|
|
|
569
698
|
process.kill(process.pid, "SIGUSR2");
|
|
570
699
|
}
|
|
571
700
|
/**
|
|
572
|
-
* `/usage` — AI subscription usage for
|
|
573
|
-
*
|
|
701
|
+
* `/usage` — AI subscription usage for CLI backends used by running or paused
|
|
702
|
+
* instances, one compact message. Same permission level as /ctx (none): whoever
|
|
574
703
|
* can talk to the fleet may see how much headroom it has left. Vendor rate
|
|
575
704
|
* limits are protected by the shared 5-minute cache, not by gating callers.
|
|
576
705
|
*/
|
|
@@ -581,7 +710,7 @@ export class TopicCommands {
|
|
|
581
710
|
try {
|
|
582
711
|
const { getUsageSnapshot } = await import("./usage/usage-api.js");
|
|
583
712
|
const { renderUsageHtml, renderUsageMarkdown } = await import("./usage/format-rich.js");
|
|
584
|
-
const payload = await getUsageSnapshot();
|
|
713
|
+
const payload = await getUsageSnapshot(false, this.ctx.getActiveUsageProviderIds?.());
|
|
585
714
|
// Each platform gets its native rich format: Telegram needs parse_mode
|
|
586
715
|
// HTML; Discord renders Markdown in plain content. Anything else falls back
|
|
587
716
|
// to Markdown, which degrades to readable text.
|
|
@@ -593,7 +722,129 @@ export class TopicCommands {
|
|
|
593
722
|
}
|
|
594
723
|
}
|
|
595
724
|
catch (err) {
|
|
596
|
-
await adapter.sendText(msg.chatId,
|
|
725
|
+
await adapter.sendText(msg.chatId, t("usage.failed", err.message), { threadId: msg.threadId });
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* `/login [backend] | code <text> | cancel` — remote CLI re-login.
|
|
730
|
+
* Fleet-admin only: the flow emits live authorization URLs/codes, and anyone
|
|
731
|
+
* completing one binds their own account to this fleet's CLI.
|
|
732
|
+
*/
|
|
733
|
+
async handleLoginCommand(msg) {
|
|
734
|
+
const adapter = this.getReplyAdapter(msg);
|
|
735
|
+
if (!adapter)
|
|
736
|
+
return;
|
|
737
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
738
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (!this.ctx.startLoginSession || !this.ctx.promptLoginBackends
|
|
742
|
+
|| !this.ctx.loginSubmitInput || !this.ctx.cancelLoginSession) {
|
|
743
|
+
await adapter.sendText(msg.chatId, t("login.no_session"), { threadId: msg.threadId });
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const arg = msg.text.trim().replace(/^\/login(?:@\S+)?/, "").trim();
|
|
747
|
+
const chat = {
|
|
748
|
+
adapter,
|
|
749
|
+
adapterId: msg.adapterId ?? adapter.id,
|
|
750
|
+
chatId: msg.chatId,
|
|
751
|
+
threadId: msg.threadId,
|
|
752
|
+
};
|
|
753
|
+
if (!arg) {
|
|
754
|
+
await this.ctx.promptLoginBackends(chat);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (arg === "cancel") {
|
|
758
|
+
await adapter.sendText(msg.chatId, await this.ctx.cancelLoginSession(), { threadId: msg.threadId });
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
if (arg.startsWith("code ")) {
|
|
762
|
+
const input = arg.slice(5).trim();
|
|
763
|
+
const reply = input ? await this.ctx.loginSubmitInput(input) : t("login.usage");
|
|
764
|
+
await adapter.sendText(msg.chatId, reply, { threadId: msg.threadId });
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (/\s/.test(arg)) {
|
|
768
|
+
await adapter.sendText(msg.chatId, t("login.usage"), { threadId: msg.threadId });
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const started = await this.ctx.startLoginSession(arg, chat);
|
|
772
|
+
// null → the still-valid-auth confirmation prompt was posted instead.
|
|
773
|
+
if (started)
|
|
774
|
+
await adapter.sendText(msg.chatId, started, { threadId: msg.threadId });
|
|
775
|
+
}
|
|
776
|
+
/** `/install-cli <backend> | cancel` — remote CLI install. Fleet-admin only. */
|
|
777
|
+
async handleInstallCliCommand(msg) {
|
|
778
|
+
const adapter = this.getReplyAdapter(msg);
|
|
779
|
+
if (!adapter)
|
|
780
|
+
return;
|
|
781
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
782
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (!this.ctx.startInstallSession || !this.ctx.cancelInstallSession) {
|
|
786
|
+
await adapter.sendText(msg.chatId, t("install.no_session"), { threadId: msg.threadId });
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const arg = msg.text.trim().replace(/^\/install[-_]cli(?:@\S+)?/, "").trim();
|
|
790
|
+
if (!arg || /\s/.test(arg)) {
|
|
791
|
+
await adapter.sendText(msg.chatId, t("install.usage"), { threadId: msg.threadId });
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (arg === "cancel") {
|
|
795
|
+
await adapter.sendText(msg.chatId, await this.ctx.cancelInstallSession(), { threadId: msg.threadId });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const chat = {
|
|
799
|
+
adapter,
|
|
800
|
+
adapterId: msg.adapterId ?? adapter.id,
|
|
801
|
+
chatId: msg.chatId,
|
|
802
|
+
threadId: msg.threadId,
|
|
803
|
+
};
|
|
804
|
+
await adapter.sendText(msg.chatId, await this.ctx.startInstallSession(arg, chat), { threadId: msg.threadId });
|
|
805
|
+
}
|
|
806
|
+
async handleTipsCommand(msg) {
|
|
807
|
+
const adapter = this.getReplyAdapter(msg);
|
|
808
|
+
if (!adapter || !this.ctx.fleetConfig)
|
|
809
|
+
return;
|
|
810
|
+
const mode = msg.text.trim().replace(/^\/tips(?:@\S+)?/, "").trim().toLowerCase();
|
|
811
|
+
if (mode === "advanced on") {
|
|
812
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
813
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
await adapter.sendText(msg.chatId, this.ctx.unlockAdvancedTips?.(msg.userId)
|
|
817
|
+
? t("tips.advanced.unlocked")
|
|
818
|
+
: t("tips.unavailable"), { threadId: msg.threadId });
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (mode === "on" || mode === "off") {
|
|
822
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
823
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
this.ctx.fleetConfig.defaults.tips = mode === "on";
|
|
827
|
+
this.ctx.saveFleetConfig();
|
|
828
|
+
await adapter.sendText(msg.chatId, t(mode === "on" ? "tips.enabled" : "tips.disabled"), {
|
|
829
|
+
threadId: msg.threadId,
|
|
830
|
+
});
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (mode) {
|
|
834
|
+
await adapter.sendText(msg.chatId, t("tips.usage"), { threadId: msg.threadId });
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const result = await this.ctx.promptTip?.(
|
|
838
|
+
// The instance name scopes prompt cleanup; the Tip is posted at the
|
|
839
|
+
// caller's current chat/thread below.
|
|
840
|
+
Object.entries(this.ctx.fleetConfig.instances)
|
|
841
|
+
.find(([, config]) => config.general_topic === true
|
|
842
|
+
&& (!config.channel_id || config.channel_id === msg.adapterId))?.[0] ?? "general", adapter, msg.chatId, msg.threadId) ?? "unavailable";
|
|
843
|
+
if (result === "empty") {
|
|
844
|
+
await adapter.sendText(msg.chatId, t("tips.empty"), { threadId: msg.threadId });
|
|
845
|
+
}
|
|
846
|
+
else if (result === "unavailable") {
|
|
847
|
+
await adapter.sendText(msg.chatId, t("tips.unavailable"), { threadId: msg.threadId });
|
|
597
848
|
}
|
|
598
849
|
}
|
|
599
850
|
async handleStatusCommand(msg) {
|
|
@@ -622,7 +873,7 @@ export class TopicCommands {
|
|
|
622
873
|
/** Get fleet status as markdown text (shared by TG + DC) */
|
|
623
874
|
async getStatusText() {
|
|
624
875
|
if (!this.ctx.fleetConfig)
|
|
625
|
-
return "
|
|
876
|
+
return t("status.no_config");
|
|
626
877
|
const rows = [];
|
|
627
878
|
let pausedCount = 0;
|
|
628
879
|
const fleetNames = new Set(Object.keys(this.ctx.fleetConfig.instances));
|
|
@@ -659,10 +910,10 @@ export class TopicCommands {
|
|
|
659
910
|
icon = "🔴";
|
|
660
911
|
else
|
|
661
912
|
icon = "⚪";
|
|
662
|
-
const stateLabel = executionState === "idle" ? "
|
|
663
|
-
: executionState === "working" ? "
|
|
664
|
-
: executionState === "stuck" ? "
|
|
665
|
-
: executionState === "paused" ? "
|
|
913
|
+
const stateLabel = executionState === "idle" ? `🟢 ${t("state.idle")}`
|
|
914
|
+
: executionState === "working" ? `🔵 ${t("state.working")}`
|
|
915
|
+
: executionState === "stuck" ? `🔴 ${t("state.stuck")}`
|
|
916
|
+
: executionState === "paused" ? `⏸ ${t("state.paused")}`
|
|
666
917
|
: "—";
|
|
667
918
|
const displayName = this.shortInstanceName(name);
|
|
668
919
|
// IPC reachability moved here from /sysinfo: it is per-instance health, and
|
|
@@ -674,27 +925,27 @@ export class TopicCommands {
|
|
|
674
925
|
rows.push(`| ${displayName} | ${backend} | ${contextStr} | ${effort} | ${formatCents(costCents)} | ${icon} | ${stateLabel} | ${ipc} |`);
|
|
675
926
|
}
|
|
676
927
|
if (rows.length === 0)
|
|
677
|
-
return "
|
|
928
|
+
return t("status.no_instances");
|
|
678
929
|
const lines = [
|
|
679
|
-
|
|
930
|
+
`## ${t("status.title")}`,
|
|
680
931
|
"",
|
|
681
|
-
"
|
|
932
|
+
t("status.table_header"),
|
|
682
933
|
"|----------|---------|-----|--------|------|--------|-------|-----|",
|
|
683
934
|
...rows,
|
|
684
935
|
"",
|
|
685
|
-
|
|
936
|
+
t("status.paused_count", pausedCount),
|
|
686
937
|
];
|
|
687
938
|
const limitCents = this.ctx.costGuard?.getLimitCents() ?? 0;
|
|
688
939
|
const totalCents = this.ctx.costGuard?.getFleetTotalCents() ?? 0;
|
|
689
940
|
if (limitCents > 0) {
|
|
690
|
-
lines.push("",
|
|
941
|
+
lines.push("", t("status.daily_cost", formatCents(totalCents), formatCents(limitCents)));
|
|
691
942
|
}
|
|
692
943
|
// Adapter states (only show if any are not connected)
|
|
693
944
|
const adapterStates = this.ctx.getAdapterStates?.();
|
|
694
945
|
if (adapterStates && adapterStates.size > 0) {
|
|
695
946
|
const issues = [...adapterStates.entries()].filter(([, s]) => s.status !== "connected");
|
|
696
947
|
if (issues.length > 0) {
|
|
697
|
-
lines.push("", "
|
|
948
|
+
lines.push("", `**${t("status.adapters")}**`);
|
|
698
949
|
for (const [id, s] of adapterStates) {
|
|
699
950
|
const icon = s.status === "connected" ? "✅" : s.status === "retrying" ? "🔄" : "❌";
|
|
700
951
|
lines.push(` ${icon} ${id}: ${s.status}${s.lastError ? ` (${s.lastError.slice(0, 60)})` : ""}`);
|
|
@@ -725,17 +976,17 @@ export class TopicCommands {
|
|
|
725
976
|
const require = createRequire(import.meta.url);
|
|
726
977
|
const agendVersion = require("../package.json").version ?? "unknown";
|
|
727
978
|
return [
|
|
728
|
-
|
|
979
|
+
`## ${t("sysinfo.title")}`,
|
|
729
980
|
"",
|
|
730
|
-
"
|
|
981
|
+
`| ${t("sysinfo.metric")} | ${t("sysinfo.value")} |`,
|
|
731
982
|
"|--------|-------|",
|
|
732
983
|
`| AgEnD | v${agendVersion} |`,
|
|
733
984
|
`| OS | ${process.platform} ${osRelease()} (${process.arch}) |`,
|
|
734
985
|
`| Node | ${process.version} |`,
|
|
735
986
|
`| tmux | ${tmuxVersion()} |`,
|
|
736
|
-
`|
|
|
737
|
-
`|
|
|
738
|
-
`|
|
|
987
|
+
`| ${t("sysinfo.uptime")} | ${upHours}h ${upMins}m |`,
|
|
988
|
+
`| ${t("sysinfo.memory")} | ${info.memory_mb.rss} MB RSS |`,
|
|
989
|
+
`| ${t("sysinfo.heap")} | ${info.memory_mb.heapUsed} / ${info.memory_mb.heapTotal} MB |`,
|
|
739
990
|
].join("\n");
|
|
740
991
|
}
|
|
741
992
|
async handleUpdateCommand(msg) {
|
|
@@ -788,11 +1039,11 @@ export class TopicCommands {
|
|
|
788
1039
|
timeout: 30_000,
|
|
789
1040
|
encoding: "utf-8",
|
|
790
1041
|
});
|
|
791
|
-
output = stripAnsi(stdout) || "
|
|
1042
|
+
output = stripAnsi(stdout) || t("doctor.no_output");
|
|
792
1043
|
}
|
|
793
1044
|
catch (err) {
|
|
794
1045
|
const e = err;
|
|
795
|
-
output = stripAnsi(e.stdout ?? e.message ?? "
|
|
1046
|
+
output = stripAnsi(e.stdout ?? e.message ?? t("doctor.failed"));
|
|
796
1047
|
}
|
|
797
1048
|
await adapter.sendText(chatId, output, { threadId });
|
|
798
1049
|
}
|
|
@@ -801,7 +1052,7 @@ export class TopicCommands {
|
|
|
801
1052
|
const adapter = this.getReplyAdapter(msg);
|
|
802
1053
|
if (!adapter)
|
|
803
1054
|
return;
|
|
804
|
-
await adapter.sendText(msg.chatId, "
|
|
1055
|
+
await adapter.sendText(msg.chatId, t("topic.unbound"), { threadId: msg.threadId });
|
|
805
1056
|
}
|
|
806
1057
|
/** Handle topic deletion — stop daemon and remove from config */
|
|
807
1058
|
async handleTopicDeleted(threadId) {
|
|
@@ -914,21 +1165,26 @@ export class TopicCommands {
|
|
|
914
1165
|
}
|
|
915
1166
|
try {
|
|
916
1167
|
const fleetCommands = [
|
|
917
|
-
{ command: "status", description: "🔒
|
|
918
|
-
{ command: "sysinfo", description: "
|
|
919
|
-
{ command: "dashboard", description: "🔒
|
|
920
|
-
{ command: "ctx", description: "
|
|
921
|
-
{ command: "compact", description: "
|
|
922
|
-
{ command: "
|
|
923
|
-
{ command: "
|
|
924
|
-
{ command: "
|
|
925
|
-
{ command: "
|
|
926
|
-
{ command: "
|
|
927
|
-
{ command: "
|
|
928
|
-
{ command: "
|
|
929
|
-
{ command: "
|
|
930
|
-
{ command: "
|
|
931
|
-
{ command: "
|
|
1168
|
+
{ command: "status", description: "🔒 " + t("slash.status") },
|
|
1169
|
+
{ command: "sysinfo", description: t("slash.sysinfo") },
|
|
1170
|
+
{ command: "dashboard", description: "🔒 " + t("slash.dashboard") },
|
|
1171
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
1172
|
+
{ command: "compact", description: t("slash.compact") },
|
|
1173
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1174
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1175
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1176
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1177
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1178
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1179
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1180
|
+
{ command: "restart", description: "🔒 " + t("slash.restart") },
|
|
1181
|
+
{ command: "collab", description: "🔒 " + t("slash.collab") },
|
|
1182
|
+
{ command: "update", description: "🔒 " + t("slash.update") },
|
|
1183
|
+
{ command: "doctor", description: "🔒 " + t("slash.doctor") },
|
|
1184
|
+
{ command: "login", description: "🔒 " + t("slash.login") },
|
|
1185
|
+
{ command: "install_cli", description: "🔒 " + t("slash.install_cli") },
|
|
1186
|
+
{ command: "usage", description: t("slash.usage") },
|
|
1187
|
+
{ command: "tips", description: t("slash.tips") },
|
|
932
1188
|
];
|
|
933
1189
|
const setCommands = async (commands, scope) => {
|
|
934
1190
|
const response = await fetch(`https://api.telegram.org/bot${botToken}/setMyCommands`, {
|
|
@@ -949,15 +1205,17 @@ export class TopicCommands {
|
|
|
949
1205
|
}
|
|
950
1206
|
};
|
|
951
1207
|
const classicCommands = [
|
|
952
|
-
{ command: "start", description: "🔒
|
|
953
|
-
{ command: "stop", description: "🔒
|
|
954
|
-
{ command: "compact", description: "🔒
|
|
955
|
-
{ command: "
|
|
956
|
-
{ command: "
|
|
957
|
-
{ command: "
|
|
958
|
-
{ command: "
|
|
959
|
-
{ command: "
|
|
960
|
-
{ command: "
|
|
1208
|
+
{ command: "start", description: "🔒 " + t("slash.start") },
|
|
1209
|
+
{ command: "stop", description: "🔒 " + t("slash.stop") },
|
|
1210
|
+
{ command: "compact", description: "🔒 " + t("slash.compact") },
|
|
1211
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1212
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1213
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1214
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1215
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1216
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1217
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1218
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
961
1219
|
];
|
|
962
1220
|
// A chat_administrators scope has higher precedence than the chat scope.
|
|
963
1221
|
// Keep both synchronized so a stale admin-only list from BotFather or an
|