@songsid/agend 2.1.4-beta.2 → 2.1.4-beta.20
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/agent-endpoint.js +1 -1
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/antigravity.d.ts +1 -0
- package/dist/backend/antigravity.js +4 -0
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js +23 -2
- 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 +72 -25
- 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/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 +159 -36
- 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 +171 -5
- package/dist/daemon.js +696 -82
- 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 +14 -0
- package/dist/fleet-manager.d.ts +231 -10
- package/dist/fleet-manager.js +1798 -493
- 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 +8 -0
- package/dist/instance-lifecycle.js +46 -25
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +12 -0
- package/dist/instructions.js +33 -0
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +656 -220
- package/dist/locale.js.map +1 -1
- 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 +9 -1
- package/dist/restart-progress.js +30 -6
- package/dist/restart-progress.js.map +1 -1
- package/dist/scheduler/db.d.ts +5 -0
- package/dist/scheduler/db.js +35 -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/tips.d.ts +39 -0
- package/dist/tips.js +351 -0
- package/dist/tips.js.map +1 -0
- 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 +28 -4
- package/dist/topic-commands.js +217 -65
- 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 +106 -0
- package/dist/transcript-sources.js +428 -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/update-marker.d.ts +30 -0
- package/dist/update-marker.js +71 -20
- package/dist/update-marker.js.map +1 -1
- package/dist/update-progress.d.ts +6 -0
- package/dist/update-progress.js +29 -0
- package/dist/update-progress.js.map +1 -0
- 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,10 @@ 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
|
+
}
|
|
273
281
|
if (text === "/update" || text.startsWith("/update@")) {
|
|
274
282
|
await this.handleUpdateCommand(msg);
|
|
275
283
|
return true;
|
|
@@ -352,7 +360,7 @@ export class TopicCommands {
|
|
|
352
360
|
return true;
|
|
353
361
|
}
|
|
354
362
|
if (pauseWake.action === "pause" && isGeneralInstance(this.ctx.fleetConfig, target)) {
|
|
355
|
-
await adapter.sendText(msg.chatId,
|
|
363
|
+
await adapter.sendText(msg.chatId, t("general.pause_forbidden"), { threadId: msg.threadId });
|
|
356
364
|
return true;
|
|
357
365
|
}
|
|
358
366
|
await adapter.sendText(msg.chatId, await this.runPauseWake(target, pauseWake.action), { threadId: msg.threadId });
|
|
@@ -379,7 +387,7 @@ export class TopicCommands {
|
|
|
379
387
|
const level = text.replace(/^\/effort(@\S+)?/, "").trim();
|
|
380
388
|
if (level) {
|
|
381
389
|
const reply = await this.ctx.applyEffort?.(instanceName, level)
|
|
382
|
-
?? "
|
|
390
|
+
?? t("effort.unsupported", this.effectiveBackend(instanceName));
|
|
383
391
|
await adapter.sendText(msg.chatId, reply, { threadId: msg.threadId });
|
|
384
392
|
}
|
|
385
393
|
else if (this.ctx.promptEffortMenu) {
|
|
@@ -410,7 +418,7 @@ export class TopicCommands {
|
|
|
410
418
|
await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
|
|
411
419
|
}
|
|
412
420
|
else {
|
|
413
|
-
await adapter.sendText(msg.chatId, "
|
|
421
|
+
await adapter.sendText(msg.chatId, t("model.usage"), { threadId: msg.threadId });
|
|
414
422
|
}
|
|
415
423
|
return true;
|
|
416
424
|
}
|
|
@@ -422,6 +430,39 @@ export class TopicCommands {
|
|
|
422
430
|
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
423
431
|
return true;
|
|
424
432
|
}
|
|
433
|
+
if (text === "/steer" || text.startsWith("/steer ") || text.startsWith("/steer@")) {
|
|
434
|
+
const adapter = this.getReplyAdapter(msg);
|
|
435
|
+
if (!adapter)
|
|
436
|
+
return false;
|
|
437
|
+
// Deliberately NOT admin-gated: anyone who can speak in this topic can
|
|
438
|
+
// already send the instance a message; /steer only changes WHEN it lands
|
|
439
|
+
// (mid-turn instead of after), and it goes through the full [user:]
|
|
440
|
+
// formatting — unlike /raw, which bypasses it and is gated.
|
|
441
|
+
const content = text.replace(/^\/steer(@\S+)?/, "").trim();
|
|
442
|
+
if (!content) {
|
|
443
|
+
await adapter.sendText(msg.chatId, t("steer.usage"), { threadId: msg.threadId });
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
446
|
+
const result = this.sendSteer(instanceName, content, msg);
|
|
447
|
+
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
if (text === "/btw" || text.startsWith("/btw ") || text.startsWith("/btw@")) {
|
|
451
|
+
const adapter = this.getReplyAdapter(msg);
|
|
452
|
+
if (!adapter)
|
|
453
|
+
return false;
|
|
454
|
+
// Like /steer, /btw is not admin-gated: anyone who can send the agent a
|
|
455
|
+
// normal message may ask a side question. Claude owns the isolation from
|
|
456
|
+
// the active turn; AgEnD only submits its native command immediately.
|
|
457
|
+
const content = text.replace(/^\/btw(@\S+)?/, "").trim();
|
|
458
|
+
if (!content) {
|
|
459
|
+
await adapter.sendText(msg.chatId, t("btw.usage"), { threadId: msg.threadId });
|
|
460
|
+
return true;
|
|
461
|
+
}
|
|
462
|
+
const result = this.sendBtw(instanceName, content, msg);
|
|
463
|
+
await adapter.sendText(msg.chatId, result, { threadId: msg.threadId });
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
425
466
|
if (text === "/clear" || text.startsWith("/clear@")) {
|
|
426
467
|
const adapter = this.getReplyAdapter(msg);
|
|
427
468
|
if (!adapter)
|
|
@@ -430,8 +471,13 @@ export class TopicCommands {
|
|
|
430
471
|
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
431
472
|
return true;
|
|
432
473
|
}
|
|
433
|
-
|
|
434
|
-
|
|
474
|
+
if (!this.ctx.promptClearConfirmation) {
|
|
475
|
+
await adapter.sendText(msg.chatId, t("clear.prompt_unavailable"), { threadId: msg.threadId });
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
const fallback = await this.ctx.promptClearConfirmation(instanceName, msg.threadId ?? msg.chatId, adapter, msg.chatId, msg.threadId);
|
|
479
|
+
if (fallback)
|
|
480
|
+
await adapter.sendText(msg.chatId, fallback, { threadId: msg.threadId });
|
|
435
481
|
return true;
|
|
436
482
|
}
|
|
437
483
|
if (text === "/cancel" || text.startsWith("/cancel@")) {
|
|
@@ -470,7 +516,7 @@ export class TopicCommands {
|
|
|
470
516
|
}
|
|
471
517
|
async runPauseWake(instanceName, action) {
|
|
472
518
|
if (action === "pause" && isGeneralInstance(this.ctx.fleetConfig, instanceName)) {
|
|
473
|
-
return
|
|
519
|
+
return t("general.pause_forbidden");
|
|
474
520
|
}
|
|
475
521
|
try {
|
|
476
522
|
const result = await this.ctx.changeInstancePauseState(instanceName, action);
|
|
@@ -519,22 +565,88 @@ export class TopicCommands {
|
|
|
519
565
|
?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
520
566
|
const cmd = compactCommandForBackend(backend);
|
|
521
567
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
522
|
-
return
|
|
568
|
+
return t("compact.sent", cmd);
|
|
569
|
+
}
|
|
570
|
+
return t("compact.not_connected");
|
|
571
|
+
}
|
|
572
|
+
/** Whether the instance backend exposes a verified full-reset command. */
|
|
573
|
+
supportsClear(instanceName) {
|
|
574
|
+
return clearCommandForBackend(this.effectiveBackend(instanceName)) !== null;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Steer: interject a message into the instance's CURRENT turn. The daemon
|
|
578
|
+
* pastes it into the busy CLI instead of queueing for idle (falling back to
|
|
579
|
+
* the queue if the TUI swallows busy input — see Daemon.steerMessage).
|
|
580
|
+
*/
|
|
581
|
+
/**
|
|
582
|
+
* Backends whose TUI accepts a busy-pane paste as steering input,
|
|
583
|
+
* live-verified: claude-code and codex buffer-then-submit at the turn
|
|
584
|
+
* boundary, grok accepts it in its input line. kiro's legacy TUI swallows
|
|
585
|
+
* the paste outright, and opencode/antigravity are unverified — for those
|
|
586
|
+
* the user gets an honest "not supported" instead of a silent queue
|
|
587
|
+
* fallback that looks like a steer but behaves like a normal message.
|
|
588
|
+
*/
|
|
589
|
+
static STEER_SUPPORTED_BACKENDS = new Set(["claude-code", "codex", "grok", "mock"]);
|
|
590
|
+
sendSteer(instanceName, content, msg) {
|
|
591
|
+
const backend = this.effectiveBackend(instanceName);
|
|
592
|
+
if (!TopicCommands.STEER_SUPPORTED_BACKENDS.has(backend)) {
|
|
593
|
+
return t("steer.unsupported", backend);
|
|
523
594
|
}
|
|
524
|
-
|
|
595
|
+
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
596
|
+
if (!ipc?.connected)
|
|
597
|
+
return t("steer.not_connected");
|
|
598
|
+
ipc.send({
|
|
599
|
+
type: "steer",
|
|
600
|
+
content,
|
|
601
|
+
delivery_epoch: this.ctx.getDeliveryEpoch?.(instanceName) ?? undefined,
|
|
602
|
+
meta: {
|
|
603
|
+
chat_id: msg.chatId,
|
|
604
|
+
message_id: msg.messageId ?? "",
|
|
605
|
+
user: msg.username,
|
|
606
|
+
user_id: msg.userId,
|
|
607
|
+
thread_id: msg.threadId ?? "",
|
|
608
|
+
adapter_id: msg.adapterId ?? "",
|
|
609
|
+
source: msg.source,
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
return t("steer.sent", instanceName);
|
|
613
|
+
}
|
|
614
|
+
/** Ask a Claude Code side question without folding it into the active turn. */
|
|
615
|
+
sendBtw(instanceName, content, msg) {
|
|
616
|
+
const backend = this.effectiveBackend(instanceName);
|
|
617
|
+
if (!BTW_SUPPORTED_BACKENDS.has(backend))
|
|
618
|
+
return t("btw.unsupported", backend);
|
|
619
|
+
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
620
|
+
if (!ipc?.connected)
|
|
621
|
+
return t("btw.not_connected");
|
|
622
|
+
ipc.send({
|
|
623
|
+
type: "btw",
|
|
624
|
+
content,
|
|
625
|
+
delivery_epoch: this.ctx.getDeliveryEpoch?.(instanceName) ?? undefined,
|
|
626
|
+
meta: {
|
|
627
|
+
chat_id: msg.chatId,
|
|
628
|
+
message_id: msg.messageId ?? "",
|
|
629
|
+
user: msg.username,
|
|
630
|
+
user_id: msg.userId,
|
|
631
|
+
thread_id: msg.threadId ?? "",
|
|
632
|
+
adapter_id: msg.adapterId ?? "",
|
|
633
|
+
source: msg.source,
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
return t("btw.sent", instanceName);
|
|
525
637
|
}
|
|
526
638
|
/** Send the backend-appropriate full conversation reset through raw_paste. */
|
|
527
639
|
async sendClear(instanceName) {
|
|
528
640
|
const backend = this.effectiveBackend(instanceName);
|
|
529
641
|
const cmd = clearCommandForBackend(backend);
|
|
530
642
|
if (!cmd)
|
|
531
|
-
return
|
|
643
|
+
return t("clear.unsupported");
|
|
532
644
|
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
533
645
|
if (ipc?.connected) {
|
|
534
646
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
535
|
-
return
|
|
647
|
+
return t("clear.sent", cmd);
|
|
536
648
|
}
|
|
537
|
-
return "
|
|
649
|
+
return t("clear.not_connected");
|
|
538
650
|
}
|
|
539
651
|
/** Send a backend-appropriate session-save command to a fleet-topic instance. */
|
|
540
652
|
async sendSave(instanceName, filename) {
|
|
@@ -546,13 +658,13 @@ export class TopicCommands {
|
|
|
546
658
|
?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
547
659
|
const cmd = saveCommandForBackend(backend, filename);
|
|
548
660
|
if (!cmd)
|
|
549
|
-
return
|
|
661
|
+
return t("save.unsupported");
|
|
550
662
|
const ipc = this.ctx.instanceIpcClients.get(instanceName);
|
|
551
663
|
if (ipc?.connected) {
|
|
552
664
|
ipc.send({ type: "raw_paste", content: cmd });
|
|
553
|
-
return
|
|
665
|
+
return t("save.sent", cmd, instanceName);
|
|
554
666
|
}
|
|
555
|
-
return "
|
|
667
|
+
return t("save.not_connected");
|
|
556
668
|
}
|
|
557
669
|
async handleRestartCommand(msg) {
|
|
558
670
|
const adapter = this.getReplyAdapter(msg);
|
|
@@ -569,8 +681,8 @@ export class TopicCommands {
|
|
|
569
681
|
process.kill(process.pid, "SIGUSR2");
|
|
570
682
|
}
|
|
571
683
|
/**
|
|
572
|
-
* `/usage` — AI subscription usage for
|
|
573
|
-
*
|
|
684
|
+
* `/usage` — AI subscription usage for CLI backends used by running or paused
|
|
685
|
+
* instances, one compact message. Same permission level as /ctx (none): whoever
|
|
574
686
|
* can talk to the fleet may see how much headroom it has left. Vendor rate
|
|
575
687
|
* limits are protected by the shared 5-minute cache, not by gating callers.
|
|
576
688
|
*/
|
|
@@ -581,7 +693,7 @@ export class TopicCommands {
|
|
|
581
693
|
try {
|
|
582
694
|
const { getUsageSnapshot } = await import("./usage/usage-api.js");
|
|
583
695
|
const { renderUsageHtml, renderUsageMarkdown } = await import("./usage/format-rich.js");
|
|
584
|
-
const payload = await getUsageSnapshot();
|
|
696
|
+
const payload = await getUsageSnapshot(false, this.ctx.getActiveUsageProviderIds?.());
|
|
585
697
|
// Each platform gets its native rich format: Telegram needs parse_mode
|
|
586
698
|
// HTML; Discord renders Markdown in plain content. Anything else falls back
|
|
587
699
|
// to Markdown, which degrades to readable text.
|
|
@@ -593,7 +705,40 @@ export class TopicCommands {
|
|
|
593
705
|
}
|
|
594
706
|
}
|
|
595
707
|
catch (err) {
|
|
596
|
-
await adapter.sendText(msg.chatId,
|
|
708
|
+
await adapter.sendText(msg.chatId, t("usage.failed", err.message), { threadId: msg.threadId });
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async handleTipsCommand(msg) {
|
|
712
|
+
const adapter = this.getReplyAdapter(msg);
|
|
713
|
+
if (!adapter || !this.ctx.fleetConfig)
|
|
714
|
+
return;
|
|
715
|
+
const mode = msg.text.trim().replace(/^\/tips(?:@\S+)?/, "").trim().toLowerCase();
|
|
716
|
+
if (mode === "on" || mode === "off") {
|
|
717
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
718
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
this.ctx.fleetConfig.defaults.tips = mode === "on";
|
|
722
|
+
this.ctx.saveFleetConfig();
|
|
723
|
+
await adapter.sendText(msg.chatId, t(mode === "on" ? "tips.enabled" : "tips.disabled"), {
|
|
724
|
+
threadId: msg.threadId,
|
|
725
|
+
});
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (mode) {
|
|
729
|
+
await adapter.sendText(msg.chatId, t("tips.usage"), { threadId: msg.threadId });
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const result = await this.ctx.promptTip?.(
|
|
733
|
+
// /tips is a General command, so the route target is the current General.
|
|
734
|
+
Object.entries(this.ctx.fleetConfig.instances)
|
|
735
|
+
.find(([, config]) => config.general_topic === true
|
|
736
|
+
&& (!config.channel_id || config.channel_id === msg.adapterId))?.[0] ?? "general", adapter, msg.chatId, msg.threadId) ?? "unavailable";
|
|
737
|
+
if (result === "empty") {
|
|
738
|
+
await adapter.sendText(msg.chatId, t("tips.empty"), { threadId: msg.threadId });
|
|
739
|
+
}
|
|
740
|
+
else if (result === "unavailable") {
|
|
741
|
+
await adapter.sendText(msg.chatId, t("tips.unavailable"), { threadId: msg.threadId });
|
|
597
742
|
}
|
|
598
743
|
}
|
|
599
744
|
async handleStatusCommand(msg) {
|
|
@@ -622,7 +767,7 @@ export class TopicCommands {
|
|
|
622
767
|
/** Get fleet status as markdown text (shared by TG + DC) */
|
|
623
768
|
async getStatusText() {
|
|
624
769
|
if (!this.ctx.fleetConfig)
|
|
625
|
-
return "
|
|
770
|
+
return t("status.no_config");
|
|
626
771
|
const rows = [];
|
|
627
772
|
let pausedCount = 0;
|
|
628
773
|
const fleetNames = new Set(Object.keys(this.ctx.fleetConfig.instances));
|
|
@@ -659,10 +804,10 @@ export class TopicCommands {
|
|
|
659
804
|
icon = "🔴";
|
|
660
805
|
else
|
|
661
806
|
icon = "⚪";
|
|
662
|
-
const stateLabel = executionState === "idle" ? "
|
|
663
|
-
: executionState === "working" ? "
|
|
664
|
-
: executionState === "stuck" ? "
|
|
665
|
-
: executionState === "paused" ? "
|
|
807
|
+
const stateLabel = executionState === "idle" ? `🟢 ${t("state.idle")}`
|
|
808
|
+
: executionState === "working" ? `🔵 ${t("state.working")}`
|
|
809
|
+
: executionState === "stuck" ? `🔴 ${t("state.stuck")}`
|
|
810
|
+
: executionState === "paused" ? `⏸ ${t("state.paused")}`
|
|
666
811
|
: "—";
|
|
667
812
|
const displayName = this.shortInstanceName(name);
|
|
668
813
|
// IPC reachability moved here from /sysinfo: it is per-instance health, and
|
|
@@ -674,27 +819,27 @@ export class TopicCommands {
|
|
|
674
819
|
rows.push(`| ${displayName} | ${backend} | ${contextStr} | ${effort} | ${formatCents(costCents)} | ${icon} | ${stateLabel} | ${ipc} |`);
|
|
675
820
|
}
|
|
676
821
|
if (rows.length === 0)
|
|
677
|
-
return "
|
|
822
|
+
return t("status.no_instances");
|
|
678
823
|
const lines = [
|
|
679
|
-
|
|
824
|
+
`## ${t("status.title")}`,
|
|
680
825
|
"",
|
|
681
|
-
"
|
|
826
|
+
t("status.table_header"),
|
|
682
827
|
"|----------|---------|-----|--------|------|--------|-------|-----|",
|
|
683
828
|
...rows,
|
|
684
829
|
"",
|
|
685
|
-
|
|
830
|
+
t("status.paused_count", pausedCount),
|
|
686
831
|
];
|
|
687
832
|
const limitCents = this.ctx.costGuard?.getLimitCents() ?? 0;
|
|
688
833
|
const totalCents = this.ctx.costGuard?.getFleetTotalCents() ?? 0;
|
|
689
834
|
if (limitCents > 0) {
|
|
690
|
-
lines.push("",
|
|
835
|
+
lines.push("", t("status.daily_cost", formatCents(totalCents), formatCents(limitCents)));
|
|
691
836
|
}
|
|
692
837
|
// Adapter states (only show if any are not connected)
|
|
693
838
|
const adapterStates = this.ctx.getAdapterStates?.();
|
|
694
839
|
if (adapterStates && adapterStates.size > 0) {
|
|
695
840
|
const issues = [...adapterStates.entries()].filter(([, s]) => s.status !== "connected");
|
|
696
841
|
if (issues.length > 0) {
|
|
697
|
-
lines.push("", "
|
|
842
|
+
lines.push("", `**${t("status.adapters")}**`);
|
|
698
843
|
for (const [id, s] of adapterStates) {
|
|
699
844
|
const icon = s.status === "connected" ? "✅" : s.status === "retrying" ? "🔄" : "❌";
|
|
700
845
|
lines.push(` ${icon} ${id}: ${s.status}${s.lastError ? ` (${s.lastError.slice(0, 60)})` : ""}`);
|
|
@@ -725,17 +870,17 @@ export class TopicCommands {
|
|
|
725
870
|
const require = createRequire(import.meta.url);
|
|
726
871
|
const agendVersion = require("../package.json").version ?? "unknown";
|
|
727
872
|
return [
|
|
728
|
-
|
|
873
|
+
`## ${t("sysinfo.title")}`,
|
|
729
874
|
"",
|
|
730
|
-
"
|
|
875
|
+
`| ${t("sysinfo.metric")} | ${t("sysinfo.value")} |`,
|
|
731
876
|
"|--------|-------|",
|
|
732
877
|
`| AgEnD | v${agendVersion} |`,
|
|
733
878
|
`| OS | ${process.platform} ${osRelease()} (${process.arch}) |`,
|
|
734
879
|
`| Node | ${process.version} |`,
|
|
735
880
|
`| tmux | ${tmuxVersion()} |`,
|
|
736
|
-
`|
|
|
737
|
-
`|
|
|
738
|
-
`|
|
|
881
|
+
`| ${t("sysinfo.uptime")} | ${upHours}h ${upMins}m |`,
|
|
882
|
+
`| ${t("sysinfo.memory")} | ${info.memory_mb.rss} MB RSS |`,
|
|
883
|
+
`| ${t("sysinfo.heap")} | ${info.memory_mb.heapUsed} / ${info.memory_mb.heapTotal} MB |`,
|
|
739
884
|
].join("\n");
|
|
740
885
|
}
|
|
741
886
|
async handleUpdateCommand(msg) {
|
|
@@ -754,11 +899,13 @@ export class TopicCommands {
|
|
|
754
899
|
await adapter.sendText(chatId, t("not_authorized"), { threadId });
|
|
755
900
|
return;
|
|
756
901
|
}
|
|
757
|
-
await adapter.sendText(chatId, t("update.
|
|
902
|
+
const sent = await adapter.sendText(chatId, t("update.progress.preparing", 0), { threadId });
|
|
903
|
+
this.ctx.beginUpdateProgress?.(adapter, chatId, threadId, sent.messageId);
|
|
758
904
|
const currentVersion = createRequire(import.meta.url)("../package.json").version ?? "";
|
|
759
905
|
const updateCmd = currentVersion.includes("beta") ? "agend update --beta" : "agend update";
|
|
760
906
|
const { spawn } = await import("node:child_process");
|
|
761
907
|
const child = spawn("sh", ["-c", `sleep 2 && ${updateCmd}`], { detached: true, stdio: "ignore" });
|
|
908
|
+
child.once("error", err => this.ctx.failUpdateProgress?.(err.message));
|
|
762
909
|
child.unref();
|
|
763
910
|
}
|
|
764
911
|
async handleDoctorCommand(msg) {
|
|
@@ -786,11 +933,11 @@ export class TopicCommands {
|
|
|
786
933
|
timeout: 30_000,
|
|
787
934
|
encoding: "utf-8",
|
|
788
935
|
});
|
|
789
|
-
output = stripAnsi(stdout) || "
|
|
936
|
+
output = stripAnsi(stdout) || t("doctor.no_output");
|
|
790
937
|
}
|
|
791
938
|
catch (err) {
|
|
792
939
|
const e = err;
|
|
793
|
-
output = stripAnsi(e.stdout ?? e.message ?? "
|
|
940
|
+
output = stripAnsi(e.stdout ?? e.message ?? t("doctor.failed"));
|
|
794
941
|
}
|
|
795
942
|
await adapter.sendText(chatId, output, { threadId });
|
|
796
943
|
}
|
|
@@ -799,7 +946,7 @@ export class TopicCommands {
|
|
|
799
946
|
const adapter = this.getReplyAdapter(msg);
|
|
800
947
|
if (!adapter)
|
|
801
948
|
return;
|
|
802
|
-
await adapter.sendText(msg.chatId, "
|
|
949
|
+
await adapter.sendText(msg.chatId, t("topic.unbound"), { threadId: msg.threadId });
|
|
803
950
|
}
|
|
804
951
|
/** Handle topic deletion — stop daemon and remove from config */
|
|
805
952
|
async handleTopicDeleted(threadId) {
|
|
@@ -912,21 +1059,24 @@ export class TopicCommands {
|
|
|
912
1059
|
}
|
|
913
1060
|
try {
|
|
914
1061
|
const fleetCommands = [
|
|
915
|
-
{ command: "status", description: "🔒
|
|
916
|
-
{ command: "sysinfo", description: "
|
|
917
|
-
{ command: "dashboard", description: "🔒
|
|
918
|
-
{ command: "ctx", description: "
|
|
919
|
-
{ command: "compact", description: "
|
|
920
|
-
{ command: "
|
|
921
|
-
{ command: "
|
|
922
|
-
{ command: "
|
|
923
|
-
{ command: "
|
|
924
|
-
{ command: "
|
|
925
|
-
{ command: "
|
|
926
|
-
{ command: "
|
|
927
|
-
{ command: "
|
|
928
|
-
{ command: "
|
|
929
|
-
{ command: "
|
|
1062
|
+
{ command: "status", description: "🔒 " + t("slash.status") },
|
|
1063
|
+
{ command: "sysinfo", description: t("slash.sysinfo") },
|
|
1064
|
+
{ command: "dashboard", description: "🔒 " + t("slash.dashboard") },
|
|
1065
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
1066
|
+
{ command: "compact", description: t("slash.compact") },
|
|
1067
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1068
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1069
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1070
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1071
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1072
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1073
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1074
|
+
{ command: "restart", description: "🔒 " + t("slash.restart") },
|
|
1075
|
+
{ command: "collab", description: "🔒 " + t("slash.collab") },
|
|
1076
|
+
{ command: "update", description: "🔒 " + t("slash.update") },
|
|
1077
|
+
{ command: "doctor", description: "🔒 " + t("slash.doctor") },
|
|
1078
|
+
{ command: "usage", description: t("slash.usage") },
|
|
1079
|
+
{ command: "tips", description: t("slash.tips") },
|
|
930
1080
|
];
|
|
931
1081
|
const setCommands = async (commands, scope) => {
|
|
932
1082
|
const response = await fetch(`https://api.telegram.org/bot${botToken}/setMyCommands`, {
|
|
@@ -947,15 +1097,17 @@ export class TopicCommands {
|
|
|
947
1097
|
}
|
|
948
1098
|
};
|
|
949
1099
|
const classicCommands = [
|
|
950
|
-
{ command: "start", description: "🔒
|
|
951
|
-
{ command: "stop", description: "🔒
|
|
952
|
-
{ command: "compact", description: "🔒
|
|
953
|
-
{ command: "
|
|
954
|
-
{ command: "
|
|
955
|
-
{ command: "
|
|
956
|
-
{ command: "
|
|
957
|
-
{ command: "
|
|
958
|
-
{ command: "
|
|
1100
|
+
{ command: "start", description: "🔒 " + t("slash.start") },
|
|
1101
|
+
{ command: "stop", description: "🔒 " + t("slash.stop") },
|
|
1102
|
+
{ command: "compact", description: "🔒 " + t("slash.compact") },
|
|
1103
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1104
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1105
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1106
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1107
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1108
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1109
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1110
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
959
1111
|
];
|
|
960
1112
|
// A chat_administrators scope has higher precedence than the chat scope.
|
|
961
1113
|
// Keep both synchronized so a stale admin-only list from BotFather or an
|