@songsid/agend 2.0.11-beta.9 β†’ 2.0.12-beta.1

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.
Files changed (70) hide show
  1. package/dist/backend/antigravity.d.ts +12 -0
  2. package/dist/backend/antigravity.js +66 -1
  3. package/dist/backend/antigravity.js.map +1 -1
  4. package/dist/backend/claude-code.d.ts +2 -0
  5. package/dist/backend/claude-code.js +2 -0
  6. package/dist/backend/claude-code.js.map +1 -1
  7. package/dist/backend/codex.d.ts +10 -0
  8. package/dist/backend/codex.js +45 -0
  9. package/dist/backend/codex.js.map +1 -1
  10. package/dist/backend/gemini-cli.d.ts +2 -0
  11. package/dist/backend/gemini-cli.js +3 -0
  12. package/dist/backend/gemini-cli.js.map +1 -1
  13. package/dist/backend/kiro.d.ts +2 -0
  14. package/dist/backend/kiro.js +4 -0
  15. package/dist/backend/kiro.js.map +1 -1
  16. package/dist/backend/mock.d.ts +2 -0
  17. package/dist/backend/mock.js +2 -0
  18. package/dist/backend/mock.js.map +1 -1
  19. package/dist/backend/opencode.d.ts +2 -0
  20. package/dist/backend/opencode.js +3 -0
  21. package/dist/backend/opencode.js.map +1 -1
  22. package/dist/backend/types.d.ts +12 -0
  23. package/dist/backend/types.js.map +1 -1
  24. package/dist/channel/adapters/discord.js +21 -16
  25. package/dist/channel/adapters/discord.js.map +1 -1
  26. package/dist/channel/adapters/telegram.js +1 -0
  27. package/dist/channel/adapters/telegram.js.map +1 -1
  28. package/dist/channel/mcp-tools.js +3 -0
  29. package/dist/channel/mcp-tools.js.map +1 -1
  30. package/dist/channel/tool-router.js +29 -2
  31. package/dist/channel/tool-router.js.map +1 -1
  32. package/dist/channel/types.d.ts +2 -0
  33. package/dist/classic-channel-manager.d.ts +59 -12
  34. package/dist/classic-channel-manager.js +121 -30
  35. package/dist/classic-channel-manager.js.map +1 -1
  36. package/dist/cli.js +70 -21
  37. package/dist/cli.js.map +1 -1
  38. package/dist/config-validator.d.ts +20 -0
  39. package/dist/config-validator.js +154 -0
  40. package/dist/config-validator.js.map +1 -0
  41. package/dist/daemon.js +1 -1
  42. package/dist/daemon.js.map +1 -1
  43. package/dist/fleet-manager.d.ts +21 -5
  44. package/dist/fleet-manager.js +421 -259
  45. package/dist/fleet-manager.js.map +1 -1
  46. package/dist/instance-lifecycle.d.ts +1 -1
  47. package/dist/instance-lifecycle.js +12 -11
  48. package/dist/instance-lifecycle.js.map +1 -1
  49. package/dist/locale.d.ts +19 -0
  50. package/dist/locale.js +179 -0
  51. package/dist/locale.js.map +1 -0
  52. package/dist/outbound-schemas.d.ts +2 -1
  53. package/dist/outbound-schemas.js +1 -0
  54. package/dist/outbound-schemas.js.map +1 -1
  55. package/dist/quickstart.js +222 -9
  56. package/dist/quickstart.js.map +1 -1
  57. package/dist/scheduler/scheduler.js +8 -2
  58. package/dist/scheduler/scheduler.js.map +1 -1
  59. package/dist/settings-api.d.ts +33 -0
  60. package/dist/settings-api.js +283 -0
  61. package/dist/settings-api.js.map +1 -0
  62. package/dist/topic-commands.d.ts +28 -1
  63. package/dist/topic-commands.js +110 -19
  64. package/dist/topic-commands.js.map +1 -1
  65. package/dist/types.d.ts +2 -0
  66. package/dist/ui/settings.html +617 -0
  67. package/dist/ui/view.html +271 -43
  68. package/dist/view-api.js +86 -37
  69. package/dist/view-api.js.map +1 -1
  70. package/package.json +1 -1
@@ -0,0 +1,283 @@
1
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import yaml from "js-yaml";
5
+ import { validateFleetConfig, validateClassicBotConfig } from "./config-validator.js";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ function json(res, code, body) {
8
+ res.writeHead(code, { "Content-Type": "application/json" });
9
+ res.end(JSON.stringify(body));
10
+ }
11
+ function readBody(req, maxBytes) {
12
+ return new Promise((resolve, reject) => {
13
+ const chunks = [];
14
+ let size = 0;
15
+ req.on("data", (c) => {
16
+ size += c.length;
17
+ if (size > maxBytes) {
18
+ reject(new Error("payload too large"));
19
+ req.destroy();
20
+ return;
21
+ }
22
+ chunks.push(c);
23
+ });
24
+ req.on("end", () => resolve(Buffer.concat(chunks)));
25
+ req.on("error", reject);
26
+ });
27
+ }
28
+ export function isSettingsPath(path) {
29
+ return path === "/settings" || path.startsWith("/api/settings/");
30
+ }
31
+ const classicPath = (ctx) => join(ctx.dataDir, "classicBot.yaml");
32
+ function readClassic(ctx) {
33
+ const p = classicPath(ctx);
34
+ if (!existsSync(p))
35
+ return {};
36
+ try {
37
+ return yaml.load(readFileSync(p, "utf-8")) ?? {};
38
+ }
39
+ catch (err) {
40
+ ctx.logger.warn({ err }, "settings: failed to parse classicBot.yaml");
41
+ return {};
42
+ }
43
+ }
44
+ const issueKey = (i) => i.path + "\u0000" + i.message;
45
+ /**
46
+ * Reject a write only if it INTRODUCES new validation errors. Pre-existing
47
+ * errors elsewhere in the config (common while a fleet is being assembled)
48
+ * must not block an unrelated edit β€” otherwise one bad channel would lock the
49
+ * user out of saving anything, which reads as "save didn't persist".
50
+ * Returns true (and responds 400) when the edit adds errors.
51
+ */
52
+ function rejectIfWorse(res, before, after) {
53
+ const had = new Set(before.errors.map(issueKey));
54
+ const introduced = after.errors.filter(e => !had.has(issueKey(e)));
55
+ if (introduced.length) {
56
+ json(res, 400, { ok: false, errors: introduced, warnings: after.warnings });
57
+ return true;
58
+ }
59
+ return false;
60
+ }
61
+ /** Warnings to surface on a successful save: new warnings + any pre-existing errors (informational). */
62
+ function saveWarnings(before, after) {
63
+ const had = new Set(before.errors.map(issueKey));
64
+ const preExisting = after.errors.filter(e => had.has(issueKey(e))).map(e => ({ path: e.path, message: "pre-existing: " + e.message }));
65
+ return [...after.warnings, ...preExisting];
66
+ }
67
+ export function handleSettingsRequest(req, res, url, ctx) {
68
+ const path = url.pathname;
69
+ if (!isSettingsPath(path))
70
+ return false;
71
+ const method = req.method ?? "GET";
72
+ // ── Static page ──
73
+ if (method === "GET" && path === "/settings") {
74
+ try {
75
+ const html = readFileSync(join(__dirname, "ui", "settings.html"), "utf-8");
76
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
77
+ res.end(html);
78
+ }
79
+ catch {
80
+ json(res, 500, { error: "settings.html not found" });
81
+ }
82
+ return true;
83
+ }
84
+ // ── Reads ──
85
+ if (method === "GET" && path === "/api/settings/fleet") {
86
+ json(res, 200, ctx.fleetConfig ?? {});
87
+ return true;
88
+ }
89
+ if (method === "GET" && path === "/api/settings/classic") {
90
+ json(res, 200, readClassic(ctx));
91
+ return true;
92
+ }
93
+ // Everything below mutates β€” needs an in-memory fleet config.
94
+ const cfg = ctx.fleetConfig;
95
+ // ── Fleet defaults ──
96
+ if (method === "PUT" && path === "/api/settings/fleet/defaults") {
97
+ if (!cfg) {
98
+ json(res, 503, { error: "fleet not loaded" });
99
+ return true;
100
+ }
101
+ readBody(req, 512 * 1024).then(buf => {
102
+ let body;
103
+ try {
104
+ body = JSON.parse(buf.toString("utf-8") || "{}");
105
+ }
106
+ catch {
107
+ return json(res, 400, { error: "invalid JSON" });
108
+ }
109
+ if (typeof body !== "object" || body === null || Array.isArray(body))
110
+ return json(res, 400, { error: "expected an object" });
111
+ const merged = { ...cfg.defaults, ...body };
112
+ const before = validateFleetConfig(cfg);
113
+ const after = validateFleetConfig({ ...cfg, defaults: merged });
114
+ if (rejectIfWorse(res, before, after))
115
+ return;
116
+ cfg.defaults = merged;
117
+ ctx.saveFleetConfig();
118
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
119
+ }).catch(() => json(res, 400, { error: "bad request" }));
120
+ return true;
121
+ }
122
+ // ── Fleet channels (full replace) ──
123
+ if (method === "PUT" && path === "/api/settings/fleet/channels") {
124
+ if (!cfg) {
125
+ json(res, 503, { error: "fleet not loaded" });
126
+ return true;
127
+ }
128
+ readBody(req, 512 * 1024).then(buf => {
129
+ let body;
130
+ try {
131
+ body = JSON.parse(buf.toString("utf-8") || "[]");
132
+ }
133
+ catch {
134
+ return json(res, 400, { error: "invalid JSON" });
135
+ }
136
+ if (!Array.isArray(body))
137
+ return json(res, 400, { error: "expected an array of channels" });
138
+ const next = { ...cfg, channels: body };
139
+ delete next.channel; // channels[] supersedes the legacy single channel
140
+ const before = validateFleetConfig(cfg);
141
+ const after = validateFleetConfig(next);
142
+ if (rejectIfWorse(res, before, after))
143
+ return;
144
+ cfg.channels = body;
145
+ delete cfg.channel;
146
+ ctx.saveFleetConfig();
147
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
148
+ }).catch(() => json(res, 400, { error: "bad request" }));
149
+ return true;
150
+ }
151
+ // ── Classic defaults ──
152
+ if (method === "PUT" && path === "/api/settings/classic/defaults") {
153
+ readBody(req, 512 * 1024).then(buf => {
154
+ let body;
155
+ try {
156
+ body = JSON.parse(buf.toString("utf-8") || "{}");
157
+ }
158
+ catch {
159
+ return json(res, 400, { error: "invalid JSON" });
160
+ }
161
+ if (typeof body !== "object" || body === null || Array.isArray(body))
162
+ return json(res, 400, { error: "expected an object" });
163
+ const classic = readClassic(ctx);
164
+ const merged = { ...(classic.defaults ?? {}), ...body };
165
+ const before = validateClassicBotConfig(classic);
166
+ const after = validateClassicBotConfig({ ...classic, defaults: merged });
167
+ if (rejectIfWorse(res, before, after))
168
+ return;
169
+ classic.defaults = merged;
170
+ writeFileSync(classicPath(ctx), yaml.dump(classic, { lineWidth: -1 }));
171
+ ctx.logger.info("settings: updated classicBot defaults");
172
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
173
+ }).catch(() => json(res, 400, { error: "bad request" }));
174
+ return true;
175
+ }
176
+ // ── Reload (SIGHUP) ──
177
+ if (method === "POST" && path === "/api/settings/reload") {
178
+ ctx.logger.info("settings: reload requested β€” sending SIGHUP");
179
+ try {
180
+ process.kill(process.pid, "SIGHUP");
181
+ }
182
+ catch (err) {
183
+ ctx.logger.warn({ err }, "settings: SIGHUP failed");
184
+ }
185
+ json(res, 200, { ok: true });
186
+ return true;
187
+ }
188
+ // ── Instances (create / patch / delete) ──
189
+ const validName = (n) => !!n && /^[^\\/\x00]+$/.test(n);
190
+ // Create-or-merge an instance, blocking only on newly-introduced errors.
191
+ const commitInstance = (name, exists, body) => {
192
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
193
+ json(res, 400, { error: "expected an object" });
194
+ return;
195
+ }
196
+ const base = (exists ? cfg.instances[name] : {});
197
+ const mergedInst = { ...base, ...body };
198
+ const before = validateFleetConfig(cfg);
199
+ const after = validateFleetConfig({ ...cfg, instances: { ...cfg.instances, [name]: mergedInst } });
200
+ if (rejectIfWorse(res, before, after))
201
+ return;
202
+ cfg.instances[name] = mergedInst;
203
+ ctx.saveFleetConfig();
204
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
205
+ };
206
+ // POST /api/settings/fleet/instances β€” create, name taken from the body.
207
+ if (method === "POST" && path === "/api/settings/fleet/instances") {
208
+ if (!cfg) {
209
+ json(res, 503, { error: "fleet not loaded" });
210
+ return true;
211
+ }
212
+ readBody(req, 512 * 1024).then(buf => {
213
+ let body;
214
+ try {
215
+ body = JSON.parse(buf.toString("utf-8") || "{}");
216
+ }
217
+ catch {
218
+ return json(res, 400, { error: "invalid JSON" });
219
+ }
220
+ if (typeof body !== "object" || body === null || Array.isArray(body))
221
+ return json(res, 400, { error: "expected an object" });
222
+ const name = typeof body.name === "string" ? body.name.trim() : "";
223
+ if (!validName(name))
224
+ return json(res, 400, { error: "missing or invalid instance name (provide `name` in the body)" });
225
+ if (cfg.instances[name])
226
+ return json(res, 409, { error: "instance already exists" });
227
+ const { name: _n, ...instBody } = body;
228
+ commitInstance(name, false, instBody);
229
+ }).catch(() => json(res, 400, { error: "bad request" }));
230
+ return true;
231
+ }
232
+ // /api/settings/fleet/instances/:name β€” create / patch / delete.
233
+ const instPrefix = "/api/settings/fleet/instances/";
234
+ if (path.startsWith(instPrefix)) {
235
+ if (!cfg) {
236
+ json(res, 503, { error: "fleet not loaded" });
237
+ return true;
238
+ }
239
+ const name = decodeURIComponent(path.slice(instPrefix.length));
240
+ if (!validName(name)) {
241
+ json(res, 400, { error: "invalid instance name" });
242
+ return true;
243
+ }
244
+ if (method === "DELETE") {
245
+ if (!cfg.instances[name]) {
246
+ json(res, 404, { error: "instance not found" });
247
+ return true;
248
+ }
249
+ // DELETE never blocks on validation; surface any resulting warnings.
250
+ delete cfg.instances[name];
251
+ ctx.saveFleetConfig();
252
+ json(res, 200, { ok: true, warnings: validateFleetConfig(cfg).warnings });
253
+ return true;
254
+ }
255
+ if (method === "POST" || method === "PATCH") {
256
+ const exists = !!cfg.instances[name];
257
+ if (method === "POST" && exists) {
258
+ json(res, 409, { error: "instance already exists" });
259
+ return true;
260
+ }
261
+ if (method === "PATCH" && !exists) {
262
+ json(res, 404, { error: "instance not found" });
263
+ return true;
264
+ }
265
+ readBody(req, 512 * 1024).then(buf => {
266
+ let body;
267
+ try {
268
+ body = JSON.parse(buf.toString("utf-8") || "{}");
269
+ }
270
+ catch {
271
+ return json(res, 400, { error: "invalid JSON" });
272
+ }
273
+ commitInstance(name, exists, body);
274
+ }).catch(() => json(res, 400, { error: "bad request" }));
275
+ return true;
276
+ }
277
+ json(res, 405, { error: "method not allowed" });
278
+ return true;
279
+ }
280
+ json(res, 404, { error: "not found" });
281
+ return true;
282
+ }
283
+ //# sourceMappingURL=settings-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-api.js","sourceRoot":"","sources":["../src/settings-api.ts"],"names":[],"mappings":"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"}
@@ -13,12 +13,25 @@ export declare function saveCommandForBackend(backend: string, filename: string,
13
13
  export declare function parseSaveFilename(text: string): string;
14
14
  /** Shared message when a backend doesn't support /save. */
15
15
  export declare const SAVE_UNSUPPORTED_MSG = "\u26A0\uFE0F /save is not supported for this backend (only kiro-cli and claude-code)";
16
+ /**
17
+ * The in-session compact/context-reset command for a backend NAME (the fleet
18
+ * process routes /compact via IPC and only has the backend string, not a
19
+ * CliBackend instance). Keep in sync with each backend's getCompactCommand().
20
+ * Most CLIs (claude-code, kiro-cli, codex, opencode, gemini-cli) use "/compact".
21
+ * Antigravity (agy) has NO summarizing compact β€” its only manual context-reset
22
+ * is "/clear" (a full reset; it also auto-summarizes at a token threshold).
23
+ */
24
+ export declare function compactCommandForBackend(backend: string): string;
16
25
  /**
17
26
  * Extract context-usage % from a captured CLI pane. Scans bottom-up so the
18
27
  * MOST RECENT prompt wins (a captured scrollback may hold several). Covers the
19
28
  * common CLI prompt formats:
20
29
  * kiro-cli classic: "6% !>" kiro-cli TUI: "β—” 6%"
21
30
  * bracketed: "[6%]" claude/others prompt: "6% ❯" / "6% >"
31
+ * codex TUI footer: "Context 94% left" (remaining) or "Context 6% used"
32
+ * opencode footer: "1.2K (6%)" (token count then parenthesized %)
33
+ * All values returned are context USED (low % = fresh session); codex's
34
+ * "N% left" is remaining, so it's inverted to 100 - N.
22
35
  */
23
36
  export declare function parseContextPercent(pane: string): number | null;
24
37
  export declare class TopicCommands {
@@ -28,11 +41,25 @@ export declare class TopicCommands {
28
41
  private getReplyAdapter;
29
42
  /** Parse and dispatch commands from the General topic */
30
43
  handleGeneralCommand(msg: InboundMessage): Promise<boolean>;
44
+ /**
45
+ * Build the dashboard URL text (View / Settings / Web UI). The Settings/Web UI
46
+ * URLs carry the web token; when `htmlSpoiler` is set they're wrapped in a
47
+ * Telegram HTML spoiler (`<tg-spoiler>`) so the token isn't shown in the clear
48
+ * in a shared topic (the caller must send with format: "html"). /view is
49
+ * public, so it's never spoilered. DC uses the plain form (ephemeral reply).
50
+ */
51
+ getDashboardText(htmlSpoiler?: boolean): string;
52
+ /**
53
+ * /dashboard (TG): admin-only. Replies directly in the topic; the token-
54
+ * bearing URLs are wrapped in a Telegram HTML spoiler so they aren't shown in
55
+ * the clear (the adapter supports plain/HTML, not MarkdownV2's `||…||`).
56
+ */
57
+ private handleDashboardCommand;
31
58
  /** Handle /ctx or /compact in any instance topic β€” returns true if handled */
32
59
  handleInstanceCommand(msg: InboundMessage, instanceName: string): Promise<boolean>;
33
60
  /** Get context usage text for an instance (shared by TG + DC) */
34
61
  getCtxText(instanceName: string): Promise<string>;
35
- /** Send /compact to an instance's tmux pane */
62
+ /** Send the backend-appropriate compact command to an instance's tmux pane */
36
63
  sendCompact(instanceName: string): Promise<string>;
37
64
  /** Send a backend-appropriate session-save command to a fleet-topic instance. */
38
65
  sendSave(instanceName: string, filename: string): Promise<string>;
@@ -6,6 +6,8 @@ import { createRequire } from "node:module";
6
6
  const execAsync = promisify(exec);
7
7
  import { DEFAULT_INSTANCE_CONFIG } from "./config.js";
8
8
  import { formatCents } from "./cost-guard.js";
9
+ import { getAgendHome } from "./paths.js";
10
+ import { t } from "./locale.js";
9
11
  /** Sanitize a directory name into a valid instance name. Keeps Unicode letters (incl. CJK). */
10
12
  export function sanitizeInstanceName(name) {
11
13
  const sanitized = name.toLowerCase().replace(/[^\p{L}\d-]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
@@ -31,18 +33,43 @@ export function parseSaveFilename(text) {
31
33
  }
32
34
  /** Shared message when a backend doesn't support /save. */
33
35
  export const SAVE_UNSUPPORTED_MSG = "⚠️ /save is not supported for this backend (only kiro-cli and claude-code)";
36
+ /**
37
+ * The in-session compact/context-reset command for a backend NAME (the fleet
38
+ * process routes /compact via IPC and only has the backend string, not a
39
+ * CliBackend instance). Keep in sync with each backend's getCompactCommand().
40
+ * Most CLIs (claude-code, kiro-cli, codex, opencode, gemini-cli) use "/compact".
41
+ * Antigravity (agy) has NO summarizing compact β€” its only manual context-reset
42
+ * is "/clear" (a full reset; it also auto-summarizes at a token threshold).
43
+ */
44
+ export function compactCommandForBackend(backend) {
45
+ if (backend === "antigravity")
46
+ return "/clear";
47
+ return "/compact";
48
+ }
34
49
  /**
35
50
  * Extract context-usage % from a captured CLI pane. Scans bottom-up so the
36
51
  * MOST RECENT prompt wins (a captured scrollback may hold several). Covers the
37
52
  * common CLI prompt formats:
38
53
  * kiro-cli classic: "6% !>" kiro-cli TUI: "β—” 6%"
39
54
  * bracketed: "[6%]" claude/others prompt: "6% ❯" / "6% >"
55
+ * codex TUI footer: "Context 94% left" (remaining) or "Context 6% used"
56
+ * opencode footer: "1.2K (6%)" (token count then parenthesized %)
57
+ * All values returned are context USED (low % = fresh session); codex's
58
+ * "N% left" is remaining, so it's inverted to 100 - N.
40
59
  */
41
60
  export function parseContextPercent(pane) {
42
61
  const lines = pane.split("\n");
43
62
  for (let i = lines.length - 1; i >= 0; i--) {
44
63
  const line = lines[i];
45
- const m = line.match(/(\d+)%.*[!❯>]/) || line.match(/β—”\s*(\d+)%/) || line.match(/\[(\d+)%\]/);
64
+ // codex "context-remaining" item shows "Context N% left" (REMAINING) β†’ used = 100 - N
65
+ const left = line.match(/Context\s+(\d+)%\s+left/i);
66
+ if (left)
67
+ return 100 - parseInt(left[1], 10);
68
+ const m = line.match(/(\d+)%.*[!❯>]/)
69
+ || line.match(/β—”\s*(\d+)%/)
70
+ || line.match(/\[(\d+)%\]/)
71
+ || line.match(/Context\s+(\d+)%\s+used/i) // codex "context-used" variant
72
+ || line.match(/\d+(?:\.\d+)?[KM]?\s*\((\d+)%\)/); // opencode "1.2K (6%)"
46
73
  if (m)
47
74
  return parseInt(m[1], 10);
48
75
  }
@@ -86,8 +113,60 @@ export class TopicCommands {
86
113
  await this.handleUpdateCommand(msg);
87
114
  return true;
88
115
  }
116
+ if (text === "/dashboard" || text.startsWith("/dashboard@")) {
117
+ await this.handleDashboardCommand(msg);
118
+ return true;
119
+ }
89
120
  return false;
90
121
  }
122
+ /**
123
+ * Build the dashboard URL text (View / Settings / Web UI). The Settings/Web UI
124
+ * URLs carry the web token; when `htmlSpoiler` is set they're wrapped in a
125
+ * Telegram HTML spoiler (`<tg-spoiler>`) so the token isn't shown in the clear
126
+ * in a shared topic (the caller must send with format: "html"). /view is
127
+ * public, so it's never spoilered. DC uses the plain form (ephemeral reply).
128
+ */
129
+ getDashboardText(htmlSpoiler = false) {
130
+ const port = this.ctx.fleetConfig?.health_port ?? 19280;
131
+ const host = this.ctx.fleetConfig?.hostname || "localhost";
132
+ let token = "";
133
+ try {
134
+ token = readFileSync(join(getAgendHome(), "web.token"), "utf-8").trim();
135
+ }
136
+ catch { /* not started yet */ }
137
+ const base = `http://${host}:${port}`;
138
+ const hide = (u) => htmlSpoiler ? `<tg-spoiler>${u}</tg-spoiler>` : u;
139
+ return [
140
+ t("dashboard.title"),
141
+ "",
142
+ `β€’ View: ${base}/view`,
143
+ `β€’ View (edit): ${hide(`${base}/view?token=${token}`)}`,
144
+ `β€’ Settings: ${hide(`${base}/settings?token=${token}`)}`,
145
+ `β€’ Web UI: ${hide(`${base}/ui?token=${token}`)}`,
146
+ ].join("\n");
147
+ }
148
+ /**
149
+ * /dashboard (TG): admin-only. Replies directly in the topic; the token-
150
+ * bearing URLs are wrapped in a Telegram HTML spoiler so they aren't shown in
151
+ * the clear (the adapter supports plain/HTML, not MarkdownV2's `||…||`).
152
+ */
153
+ async handleDashboardCommand(msg) {
154
+ const adapter = this.getReplyAdapter(msg);
155
+ if (!adapter)
156
+ return;
157
+ const chatId = msg.chatId;
158
+ const threadId = msg.threadId;
159
+ const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
160
+ if (allowed.length === 0) {
161
+ await adapter.sendText(chatId, t("dashboard.disabled"), { threadId });
162
+ return;
163
+ }
164
+ if (!allowed.some(u => String(u) === String(msg.userId))) {
165
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
166
+ return;
167
+ }
168
+ await adapter.sendText(chatId, this.getDashboardText(true), { threadId, format: "html" });
169
+ }
91
170
  /** Handle /ctx or /compact in any instance topic β€” returns true if handled */
92
171
  async handleInstanceCommand(msg, instanceName) {
93
172
  const text = msg.text?.trim();
@@ -99,8 +178,8 @@ export class TopicCommands {
99
178
  return false;
100
179
  const isCollab = this.ctx.toggleFleetCollab(instanceName);
101
180
  await adapter.sendText(msg.chatId, isCollab
102
- ? "🀝 Collaboration mode **ON** β€” bot/webhook messages to this topic will reach the agent."
103
- : "πŸ’¬ Collaboration mode **OFF** β€” only user messages trigger the agent.", { threadId: msg.threadId });
181
+ ? t("collab.on")
182
+ : t("collab.off"), { threadId: msg.threadId });
104
183
  return true;
105
184
  }
106
185
  if (text === "/compact" || text.startsWith("/compact@")) {
@@ -116,7 +195,7 @@ export class TopicCommands {
116
195
  if (!adapter)
117
196
  return false;
118
197
  const ok = this.ctx.cancelInstance(instanceName);
119
- await adapter.sendText(msg.chatId, ok ? `πŸ›‘ ε·²ι€ε‡Ίε–ζΆˆη΅¦ ${instanceName}。` : `❌ ${instanceName} ζœͺεœ¨εŸ·θ‘Œγ€‚`, { threadId: msg.threadId });
198
+ await adapter.sendText(msg.chatId, ok ? t("cancel.sent", instanceName) : t("cancel.not_running", instanceName), { threadId: msg.threadId });
120
199
  return true;
121
200
  }
122
201
  if (text === "/save" || text.startsWith("/save ") || text.startsWith("/save@")) {
@@ -125,11 +204,11 @@ export class TopicCommands {
125
204
  return false;
126
205
  const filename = parseSaveFilename(text);
127
206
  if (!filename) {
128
- await adapter.sendText(msg.chatId, "Usage: /save <filename>", { threadId: msg.threadId });
207
+ await adapter.sendText(msg.chatId, t("save.usage"), { threadId: msg.threadId });
129
208
  return true;
130
209
  }
131
210
  if (!SAVE_FILENAME_RE.test(filename)) {
132
- await adapter.sendText(msg.chatId, "β›” Invalid filename β€” only letters, numbers, dots, hyphens, underscores allowed.", { threadId: msg.threadId });
211
+ await adapter.sendText(msg.chatId, t("filename.invalid"), { threadId: msg.threadId });
133
212
  return true;
134
213
  }
135
214
  const result = await this.sendSave(instanceName, filename);
@@ -173,15 +252,18 @@ export class TopicCommands {
173
252
  catch { /* ignore */ }
174
253
  }
175
254
  return context != null
176
- ? `πŸ“Š Context: ${context}% used\nBackend: ${backend}\nInstance: ${instanceName}`
177
- : `Context info not available yet.\nBackend: ${backend}\nInstance: ${instanceName}`;
255
+ ? `${t("ctx.used", context)}\n${t("ctx.backend", backend)}\n${t("ctx.instance", instanceName)}`
256
+ : `${t("ctx.unavailable")}\n${t("ctx.backend", backend)}\n${t("ctx.instance", instanceName)}`;
178
257
  }
179
- /** Send /compact to an instance's tmux pane */
258
+ /** Send the backend-appropriate compact command to an instance's tmux pane */
180
259
  async sendCompact(instanceName) {
181
260
  const ipc = this.ctx.instanceIpcClients.get(instanceName);
182
261
  if (ipc?.connected) {
183
- ipc.send({ type: "raw_paste", content: "/compact" });
184
- return "πŸ—œοΈ Compact command sent.";
262
+ const backend = this.ctx.fleetConfig?.instances[instanceName]?.backend
263
+ ?? this.ctx.fleetConfig?.defaults?.backend ?? "claude-code";
264
+ const cmd = compactCommandForBackend(backend);
265
+ ipc.send({ type: "raw_paste", content: cmd });
266
+ return `πŸ—œοΈ Compact command sent (\`${cmd}\`).`;
185
267
  }
186
268
  return "❌ Instance not connected (IPC unavailable)";
187
269
  }
@@ -207,10 +289,10 @@ export class TopicCommands {
207
289
  const threadId = msg.threadId;
208
290
  const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
209
291
  if (allowed.length > 0 && !allowed.some(u => String(u) === String(msg.userId))) {
210
- await adapter.sendText(chatId, "β›” Not authorized", { threadId });
292
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
211
293
  return;
212
294
  }
213
- await adapter.sendText(chatId, "πŸ”„ Graceful restart β€” waiting for instances to idle...", { threadId });
295
+ await adapter.sendText(chatId, t("restart.graceful"), { threadId });
214
296
  process.kill(process.pid, "SIGUSR2");
215
297
  }
216
298
  async handleStatusCommand(msg) {
@@ -314,14 +396,14 @@ export class TopicCommands {
314
396
  // Access control β€” only allowed users can trigger update; empty = disabled
315
397
  const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
316
398
  if (allowed.length === 0) {
317
- await adapter.sendText(chatId, "β›” /update disabled β€” no allowed_users configured", { threadId });
399
+ await adapter.sendText(chatId, t("update.disabled"), { threadId });
318
400
  return;
319
401
  }
320
402
  if (!allowed.some(u => String(u) === String(msg.userId))) {
321
- await adapter.sendText(chatId, "β›” Not authorized", { threadId });
403
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
322
404
  return;
323
405
  }
324
- await adapter.sendText(chatId, "πŸ“¦ Updating AgEnD... Fleet will restart automatically.", { threadId });
406
+ await adapter.sendText(chatId, t("update.running"), { threadId });
325
407
  const currentVersion = createRequire(import.meta.url)("../package.json").version ?? "";
326
408
  const updateCmd = currentVersion.includes("beta") ? "agend update --beta" : "agend update";
327
409
  const { spawn } = await import("node:child_process");
@@ -336,10 +418,10 @@ export class TopicCommands {
336
418
  const threadId = msg.threadId;
337
419
  const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
338
420
  if (allowed.length > 0 && !allowed.some(u => String(u) === String(msg.userId))) {
339
- await adapter.sendText(chatId, "β›” Not authorized", { threadId });
421
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
340
422
  return;
341
423
  }
342
- await adapter.sendText(chatId, "🩺 Running diagnostics...", { threadId });
424
+ await adapter.sendText(chatId, t("doctor.running"), { threadId });
343
425
  try {
344
426
  const { execSync } = await import("node:child_process");
345
427
  const backend = this.ctx.fleetConfig?.defaults?.backend || "claude-code";
@@ -419,7 +501,15 @@ export class TopicCommands {
419
501
  }
420
502
  if (platformType === "discord") {
421
503
  const ch = channels.find(c => c.type === "discord");
422
- config.topic_id = ch?.options?.general_channel_id ?? 1;
504
+ const gcid = ch?.options?.general_channel_id;
505
+ // A Discord general needs a real channel id β€” NOT the TG-convention
506
+ // "1", which makes the DC adapter throw fetching channel "1". Skip
507
+ // (leave unbound) if there's no valid channel to bind to.
508
+ if (gcid == null || !/^\d{17,}$/.test(String(gcid))) {
509
+ this.ctx.logger.warn({ name }, "Discord general has no valid general_channel_id β€” skipping topic bind (set channel.options.general_channel_id)");
510
+ continue;
511
+ }
512
+ config.topic_id = gcid;
423
513
  }
424
514
  else {
425
515
  config.topic_id = 1;
@@ -463,6 +553,7 @@ export class TopicCommands {
463
553
  commands: [
464
554
  { command: "status", description: "Show fleet status and costs" },
465
555
  { command: "sysinfo", description: "System diagnostics" },
556
+ { command: "dashboard", description: "πŸ”’ Get dashboard URLs" },
466
557
  { command: "ctx", description: "Show context usage" },
467
558
  { command: "compact", description: "Compact agent context" },
468
559
  { command: "restart", description: "πŸ”’ Graceful restart all instances" },