@songsid/agend 2.1.4-beta.2 → 2.1.4-beta.21
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 +76 -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 +16 -0
- package/dist/fleet-manager.d.ts +233 -10
- package/dist/fleet-manager.js +1828 -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 +227 -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);
|
|
523
569
|
}
|
|
524
|
-
return "
|
|
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);
|
|
594
|
+
}
|
|
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,50 @@ 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 === "advanced on") {
|
|
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
|
+
await adapter.sendText(msg.chatId, this.ctx.unlockAdvancedTips?.(msg.userId)
|
|
722
|
+
? t("tips.advanced.unlocked")
|
|
723
|
+
: t("tips.unavailable"), { threadId: msg.threadId });
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (mode === "on" || mode === "off") {
|
|
727
|
+
if (!this.ctx.isFleetAdmin(msg.userId, msg.adapterId)) {
|
|
728
|
+
await adapter.sendText(msg.chatId, t("permission.denied"), { threadId: msg.threadId });
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
this.ctx.fleetConfig.defaults.tips = mode === "on";
|
|
732
|
+
this.ctx.saveFleetConfig();
|
|
733
|
+
await adapter.sendText(msg.chatId, t(mode === "on" ? "tips.enabled" : "tips.disabled"), {
|
|
734
|
+
threadId: msg.threadId,
|
|
735
|
+
});
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (mode) {
|
|
739
|
+
await adapter.sendText(msg.chatId, t("tips.usage"), { threadId: msg.threadId });
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const result = await this.ctx.promptTip?.(
|
|
743
|
+
// /tips is a General command, so the route target is the current General.
|
|
744
|
+
Object.entries(this.ctx.fleetConfig.instances)
|
|
745
|
+
.find(([, config]) => config.general_topic === true
|
|
746
|
+
&& (!config.channel_id || config.channel_id === msg.adapterId))?.[0] ?? "general", adapter, msg.chatId, msg.threadId) ?? "unavailable";
|
|
747
|
+
if (result === "empty") {
|
|
748
|
+
await adapter.sendText(msg.chatId, t("tips.empty"), { threadId: msg.threadId });
|
|
749
|
+
}
|
|
750
|
+
else if (result === "unavailable") {
|
|
751
|
+
await adapter.sendText(msg.chatId, t("tips.unavailable"), { threadId: msg.threadId });
|
|
597
752
|
}
|
|
598
753
|
}
|
|
599
754
|
async handleStatusCommand(msg) {
|
|
@@ -622,7 +777,7 @@ export class TopicCommands {
|
|
|
622
777
|
/** Get fleet status as markdown text (shared by TG + DC) */
|
|
623
778
|
async getStatusText() {
|
|
624
779
|
if (!this.ctx.fleetConfig)
|
|
625
|
-
return "
|
|
780
|
+
return t("status.no_config");
|
|
626
781
|
const rows = [];
|
|
627
782
|
let pausedCount = 0;
|
|
628
783
|
const fleetNames = new Set(Object.keys(this.ctx.fleetConfig.instances));
|
|
@@ -659,10 +814,10 @@ export class TopicCommands {
|
|
|
659
814
|
icon = "🔴";
|
|
660
815
|
else
|
|
661
816
|
icon = "⚪";
|
|
662
|
-
const stateLabel = executionState === "idle" ? "
|
|
663
|
-
: executionState === "working" ? "
|
|
664
|
-
: executionState === "stuck" ? "
|
|
665
|
-
: executionState === "paused" ? "
|
|
817
|
+
const stateLabel = executionState === "idle" ? `🟢 ${t("state.idle")}`
|
|
818
|
+
: executionState === "working" ? `🔵 ${t("state.working")}`
|
|
819
|
+
: executionState === "stuck" ? `🔴 ${t("state.stuck")}`
|
|
820
|
+
: executionState === "paused" ? `⏸ ${t("state.paused")}`
|
|
666
821
|
: "—";
|
|
667
822
|
const displayName = this.shortInstanceName(name);
|
|
668
823
|
// IPC reachability moved here from /sysinfo: it is per-instance health, and
|
|
@@ -674,27 +829,27 @@ export class TopicCommands {
|
|
|
674
829
|
rows.push(`| ${displayName} | ${backend} | ${contextStr} | ${effort} | ${formatCents(costCents)} | ${icon} | ${stateLabel} | ${ipc} |`);
|
|
675
830
|
}
|
|
676
831
|
if (rows.length === 0)
|
|
677
|
-
return "
|
|
832
|
+
return t("status.no_instances");
|
|
678
833
|
const lines = [
|
|
679
|
-
|
|
834
|
+
`## ${t("status.title")}`,
|
|
680
835
|
"",
|
|
681
|
-
"
|
|
836
|
+
t("status.table_header"),
|
|
682
837
|
"|----------|---------|-----|--------|------|--------|-------|-----|",
|
|
683
838
|
...rows,
|
|
684
839
|
"",
|
|
685
|
-
|
|
840
|
+
t("status.paused_count", pausedCount),
|
|
686
841
|
];
|
|
687
842
|
const limitCents = this.ctx.costGuard?.getLimitCents() ?? 0;
|
|
688
843
|
const totalCents = this.ctx.costGuard?.getFleetTotalCents() ?? 0;
|
|
689
844
|
if (limitCents > 0) {
|
|
690
|
-
lines.push("",
|
|
845
|
+
lines.push("", t("status.daily_cost", formatCents(totalCents), formatCents(limitCents)));
|
|
691
846
|
}
|
|
692
847
|
// Adapter states (only show if any are not connected)
|
|
693
848
|
const adapterStates = this.ctx.getAdapterStates?.();
|
|
694
849
|
if (adapterStates && adapterStates.size > 0) {
|
|
695
850
|
const issues = [...adapterStates.entries()].filter(([, s]) => s.status !== "connected");
|
|
696
851
|
if (issues.length > 0) {
|
|
697
|
-
lines.push("", "
|
|
852
|
+
lines.push("", `**${t("status.adapters")}**`);
|
|
698
853
|
for (const [id, s] of adapterStates) {
|
|
699
854
|
const icon = s.status === "connected" ? "✅" : s.status === "retrying" ? "🔄" : "❌";
|
|
700
855
|
lines.push(` ${icon} ${id}: ${s.status}${s.lastError ? ` (${s.lastError.slice(0, 60)})` : ""}`);
|
|
@@ -725,17 +880,17 @@ export class TopicCommands {
|
|
|
725
880
|
const require = createRequire(import.meta.url);
|
|
726
881
|
const agendVersion = require("../package.json").version ?? "unknown";
|
|
727
882
|
return [
|
|
728
|
-
|
|
883
|
+
`## ${t("sysinfo.title")}`,
|
|
729
884
|
"",
|
|
730
|
-
"
|
|
885
|
+
`| ${t("sysinfo.metric")} | ${t("sysinfo.value")} |`,
|
|
731
886
|
"|--------|-------|",
|
|
732
887
|
`| AgEnD | v${agendVersion} |`,
|
|
733
888
|
`| OS | ${process.platform} ${osRelease()} (${process.arch}) |`,
|
|
734
889
|
`| Node | ${process.version} |`,
|
|
735
890
|
`| tmux | ${tmuxVersion()} |`,
|
|
736
|
-
`|
|
|
737
|
-
`|
|
|
738
|
-
`|
|
|
891
|
+
`| ${t("sysinfo.uptime")} | ${upHours}h ${upMins}m |`,
|
|
892
|
+
`| ${t("sysinfo.memory")} | ${info.memory_mb.rss} MB RSS |`,
|
|
893
|
+
`| ${t("sysinfo.heap")} | ${info.memory_mb.heapUsed} / ${info.memory_mb.heapTotal} MB |`,
|
|
739
894
|
].join("\n");
|
|
740
895
|
}
|
|
741
896
|
async handleUpdateCommand(msg) {
|
|
@@ -754,11 +909,13 @@ export class TopicCommands {
|
|
|
754
909
|
await adapter.sendText(chatId, t("not_authorized"), { threadId });
|
|
755
910
|
return;
|
|
756
911
|
}
|
|
757
|
-
await adapter.sendText(chatId, t("update.
|
|
912
|
+
const sent = await adapter.sendText(chatId, t("update.progress.preparing", 0), { threadId });
|
|
913
|
+
this.ctx.beginUpdateProgress?.(adapter, chatId, threadId, sent.messageId);
|
|
758
914
|
const currentVersion = createRequire(import.meta.url)("../package.json").version ?? "";
|
|
759
915
|
const updateCmd = currentVersion.includes("beta") ? "agend update --beta" : "agend update";
|
|
760
916
|
const { spawn } = await import("node:child_process");
|
|
761
917
|
const child = spawn("sh", ["-c", `sleep 2 && ${updateCmd}`], { detached: true, stdio: "ignore" });
|
|
918
|
+
child.once("error", err => this.ctx.failUpdateProgress?.(err.message));
|
|
762
919
|
child.unref();
|
|
763
920
|
}
|
|
764
921
|
async handleDoctorCommand(msg) {
|
|
@@ -786,11 +943,11 @@ export class TopicCommands {
|
|
|
786
943
|
timeout: 30_000,
|
|
787
944
|
encoding: "utf-8",
|
|
788
945
|
});
|
|
789
|
-
output = stripAnsi(stdout) || "
|
|
946
|
+
output = stripAnsi(stdout) || t("doctor.no_output");
|
|
790
947
|
}
|
|
791
948
|
catch (err) {
|
|
792
949
|
const e = err;
|
|
793
|
-
output = stripAnsi(e.stdout ?? e.message ?? "
|
|
950
|
+
output = stripAnsi(e.stdout ?? e.message ?? t("doctor.failed"));
|
|
794
951
|
}
|
|
795
952
|
await adapter.sendText(chatId, output, { threadId });
|
|
796
953
|
}
|
|
@@ -799,7 +956,7 @@ export class TopicCommands {
|
|
|
799
956
|
const adapter = this.getReplyAdapter(msg);
|
|
800
957
|
if (!adapter)
|
|
801
958
|
return;
|
|
802
|
-
await adapter.sendText(msg.chatId, "
|
|
959
|
+
await adapter.sendText(msg.chatId, t("topic.unbound"), { threadId: msg.threadId });
|
|
803
960
|
}
|
|
804
961
|
/** Handle topic deletion — stop daemon and remove from config */
|
|
805
962
|
async handleTopicDeleted(threadId) {
|
|
@@ -912,21 +1069,24 @@ export class TopicCommands {
|
|
|
912
1069
|
}
|
|
913
1070
|
try {
|
|
914
1071
|
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: "
|
|
1072
|
+
{ command: "status", description: "🔒 " + t("slash.status") },
|
|
1073
|
+
{ command: "sysinfo", description: t("slash.sysinfo") },
|
|
1074
|
+
{ command: "dashboard", description: "🔒 " + t("slash.dashboard") },
|
|
1075
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
1076
|
+
{ command: "compact", description: t("slash.compact") },
|
|
1077
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1078
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1079
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1080
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1081
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1082
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1083
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1084
|
+
{ command: "restart", description: "🔒 " + t("slash.restart") },
|
|
1085
|
+
{ command: "collab", description: "🔒 " + t("slash.collab") },
|
|
1086
|
+
{ command: "update", description: "🔒 " + t("slash.update") },
|
|
1087
|
+
{ command: "doctor", description: "🔒 " + t("slash.doctor") },
|
|
1088
|
+
{ command: "usage", description: t("slash.usage") },
|
|
1089
|
+
{ command: "tips", description: t("slash.tips") },
|
|
930
1090
|
];
|
|
931
1091
|
const setCommands = async (commands, scope) => {
|
|
932
1092
|
const response = await fetch(`https://api.telegram.org/bot${botToken}/setMyCommands`, {
|
|
@@ -947,15 +1107,17 @@ export class TopicCommands {
|
|
|
947
1107
|
}
|
|
948
1108
|
};
|
|
949
1109
|
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: "
|
|
1110
|
+
{ command: "start", description: "🔒 " + t("slash.start") },
|
|
1111
|
+
{ command: "stop", description: "🔒 " + t("slash.stop") },
|
|
1112
|
+
{ command: "compact", description: "🔒 " + t("slash.compact") },
|
|
1113
|
+
{ command: "steer", description: t("slash.steer") },
|
|
1114
|
+
{ command: "btw", description: t("slash.btw") },
|
|
1115
|
+
{ command: "clear", description: "🔒 " + t("slash.clear") },
|
|
1116
|
+
{ command: "model", description: "🔒 " + t("slash.model") },
|
|
1117
|
+
{ command: "effort", description: "🔒 " + t("slash.effort") },
|
|
1118
|
+
{ command: "pause", description: "🔒 " + t("slash.pause") },
|
|
1119
|
+
{ command: "wake", description: "🔒 " + t("slash.wake") },
|
|
1120
|
+
{ command: "ctx", description: t("slash.ctx") },
|
|
959
1121
|
];
|
|
960
1122
|
// A chat_administrators scope has higher precedence than the chat scope.
|
|
961
1123
|
// Keep both synchronized so a stale admin-only list from BotFather or an
|