@songsid/agend 2.0.11-beta.3 → 2.0.11-beta.31

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 (52) hide show
  1. package/dist/channel/adapters/discord.d.ts +2 -0
  2. package/dist/channel/adapters/discord.js +40 -34
  3. package/dist/channel/adapters/discord.js.map +1 -1
  4. package/dist/channel/factory.d.ts +4 -0
  5. package/dist/channel/factory.js +1 -0
  6. package/dist/channel/factory.js.map +1 -1
  7. package/dist/channel/mcp-tools.js +3 -0
  8. package/dist/channel/mcp-tools.js.map +1 -1
  9. package/dist/channel/tool-router.js +29 -2
  10. package/dist/channel/tool-router.js.map +1 -1
  11. package/dist/classic-channel-manager.d.ts +59 -12
  12. package/dist/classic-channel-manager.js +121 -30
  13. package/dist/classic-channel-manager.js.map +1 -1
  14. package/dist/cli.js +94 -7
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config-validator.d.ts +20 -0
  17. package/dist/config-validator.js +154 -0
  18. package/dist/config-validator.js.map +1 -0
  19. package/dist/fleet-manager.d.ts +29 -5
  20. package/dist/fleet-manager.js +451 -233
  21. package/dist/fleet-manager.js.map +1 -1
  22. package/dist/instance-lifecycle.d.ts +1 -1
  23. package/dist/instance-lifecycle.js +7 -6
  24. package/dist/instance-lifecycle.js.map +1 -1
  25. package/dist/locale.d.ts +19 -0
  26. package/dist/locale.js +137 -0
  27. package/dist/locale.js.map +1 -0
  28. package/dist/outbound-schemas.d.ts +1 -0
  29. package/dist/outbound-schemas.js +1 -0
  30. package/dist/outbound-schemas.js.map +1 -1
  31. package/dist/quickstart.js +252 -16
  32. package/dist/quickstart.js.map +1 -1
  33. package/dist/scheduler/scheduler.d.ts +1 -1
  34. package/dist/scheduler/scheduler.js +15 -4
  35. package/dist/scheduler/scheduler.js.map +1 -1
  36. package/dist/service-installer.d.ts +1 -0
  37. package/dist/service-installer.js +2 -1
  38. package/dist/service-installer.js.map +1 -1
  39. package/dist/settings-api.d.ts +63 -0
  40. package/dist/settings-api.js +331 -0
  41. package/dist/settings-api.js.map +1 -0
  42. package/dist/topic-commands.d.ts +14 -0
  43. package/dist/topic-commands.js +78 -16
  44. package/dist/topic-commands.js.map +1 -1
  45. package/dist/types.d.ts +2 -0
  46. package/dist/ui/settings.html +612 -0
  47. package/dist/ui/view.html +528 -0
  48. package/dist/view-api.d.ts +29 -0
  49. package/dist/view-api.js +370 -0
  50. package/dist/view-api.js.map +1 -0
  51. package/package.json +1 -1
  52. package/templates/systemd.service.ejs +2 -1
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Settings Web API (`/settings`) — CRUD over fleet.yaml + classicBot.yaml.
3
+ *
4
+ * GET /settings → static page
5
+ * GET /api/settings/fleet → fleet config as JSON
6
+ * GET /api/settings/classic → classicBot.yaml as JSON
7
+ * PUT /api/settings/fleet/defaults → merge fleet defaults
8
+ * PUT /api/settings/fleet/channels → replace channels[]
9
+ * POST /api/settings/fleet/instances/:name → create instance
10
+ * PATCH/api/settings/fleet/instances/:name → merge into instance
11
+ * DELETE /api/settings/fleet/instances/:name → remove instance
12
+ * PUT /api/settings/classic/defaults → merge classic defaults
13
+ * POST /api/settings/reload → SIGHUP hot-reload
14
+ *
15
+ * Auth: all routes require the web.token — enforced by the global web-token gate
16
+ * in fleet-manager BEFORE this handler runs (settings paths are not exempt), so
17
+ * no per-route auth is repeated here.
18
+ *
19
+ * Writes are validated first (config-validator): any error → 400 and nothing is
20
+ * written; warnings are non-blocking and returned alongside the result.
21
+ */
22
+ import type { IncomingMessage, ServerResponse } from "node:http";
23
+ import type { Logger } from "./logger.js";
24
+ import type { FleetConfig } from "./types.js";
25
+ /** Reserved schedule target for the fleet auto-update job (intercepted in handleScheduleTrigger). */
26
+ export declare const UPDATE_SCHEDULE_TARGET = "__fleet_update__";
27
+ interface SchedulerLike {
28
+ list(target?: string): Array<{
29
+ id: string;
30
+ cron: string;
31
+ message: string;
32
+ enabled: boolean;
33
+ last_triggered_at: string | null;
34
+ }>;
35
+ create(p: {
36
+ cron: string;
37
+ message: string;
38
+ source: string;
39
+ target: string;
40
+ reply_chat_id: string;
41
+ reply_thread_id: string | null;
42
+ label?: string;
43
+ }, allowReserved?: boolean): {
44
+ id: string;
45
+ };
46
+ update(id: string, p: {
47
+ cron?: string;
48
+ message?: string;
49
+ enabled?: boolean;
50
+ }): unknown;
51
+ }
52
+ export interface SettingsApiContext {
53
+ fleetConfig: FleetConfig | null;
54
+ configPath: string | null;
55
+ dataDir: string;
56
+ logger: Logger;
57
+ saveFleetConfig(): void;
58
+ scheduler?: SchedulerLike;
59
+ currentVersion?: string;
60
+ }
61
+ export declare function isSettingsPath(path: string): boolean;
62
+ export declare function handleSettingsRequest(req: IncomingMessage, res: ServerResponse, url: URL, ctx: SettingsApiContext): boolean;
63
+ export {};
@@ -0,0 +1,331 @@
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
+ /** Reserved schedule target for the fleet auto-update job (intercepted in handleScheduleTrigger). */
8
+ export const UPDATE_SCHEDULE_TARGET = "__fleet_update__";
9
+ function json(res, code, body) {
10
+ res.writeHead(code, { "Content-Type": "application/json" });
11
+ res.end(JSON.stringify(body));
12
+ }
13
+ function readBody(req, maxBytes) {
14
+ return new Promise((resolve, reject) => {
15
+ const chunks = [];
16
+ let size = 0;
17
+ req.on("data", (c) => {
18
+ size += c.length;
19
+ if (size > maxBytes) {
20
+ reject(new Error("payload too large"));
21
+ req.destroy();
22
+ return;
23
+ }
24
+ chunks.push(c);
25
+ });
26
+ req.on("end", () => resolve(Buffer.concat(chunks)));
27
+ req.on("error", reject);
28
+ });
29
+ }
30
+ export function isSettingsPath(path) {
31
+ return path === "/settings" || path.startsWith("/api/settings/");
32
+ }
33
+ const classicPath = (ctx) => join(ctx.dataDir, "classicBot.yaml");
34
+ function readClassic(ctx) {
35
+ const p = classicPath(ctx);
36
+ if (!existsSync(p))
37
+ return {};
38
+ try {
39
+ return yaml.load(readFileSync(p, "utf-8")) ?? {};
40
+ }
41
+ catch (err) {
42
+ ctx.logger.warn({ err }, "settings: failed to parse classicBot.yaml");
43
+ return {};
44
+ }
45
+ }
46
+ const issueKey = (i) => i.path + "\u0000" + i.message;
47
+ /**
48
+ * Reject a write only if it INTRODUCES new validation errors. Pre-existing
49
+ * errors elsewhere in the config (common while a fleet is being assembled)
50
+ * must not block an unrelated edit — otherwise one bad channel would lock the
51
+ * user out of saving anything, which reads as "save didn't persist".
52
+ * Returns true (and responds 400) when the edit adds errors.
53
+ */
54
+ function rejectIfWorse(res, before, after) {
55
+ const had = new Set(before.errors.map(issueKey));
56
+ const introduced = after.errors.filter(e => !had.has(issueKey(e)));
57
+ if (introduced.length) {
58
+ json(res, 400, { ok: false, errors: introduced, warnings: after.warnings });
59
+ return true;
60
+ }
61
+ return false;
62
+ }
63
+ /** Warnings to surface on a successful save: new warnings + any pre-existing errors (informational). */
64
+ function saveWarnings(before, after) {
65
+ const had = new Set(before.errors.map(issueKey));
66
+ const preExisting = after.errors.filter(e => had.has(issueKey(e))).map(e => ({ path: e.path, message: "pre-existing: " + e.message }));
67
+ return [...after.warnings, ...preExisting];
68
+ }
69
+ export function handleSettingsRequest(req, res, url, ctx) {
70
+ const path = url.pathname;
71
+ if (!isSettingsPath(path))
72
+ return false;
73
+ const method = req.method ?? "GET";
74
+ // ── Static page ──
75
+ if (method === "GET" && path === "/settings") {
76
+ try {
77
+ const html = readFileSync(join(__dirname, "ui", "settings.html"), "utf-8");
78
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
79
+ res.end(html);
80
+ }
81
+ catch {
82
+ json(res, 500, { error: "settings.html not found" });
83
+ }
84
+ return true;
85
+ }
86
+ // ── Reads ──
87
+ if (method === "GET" && path === "/api/settings/fleet") {
88
+ json(res, 200, ctx.fleetConfig ?? {});
89
+ return true;
90
+ }
91
+ if (method === "GET" && path === "/api/settings/classic") {
92
+ json(res, 200, readClassic(ctx));
93
+ return true;
94
+ }
95
+ // ── Auto-update schedule (backed by schedules.db via a reserved target) ──
96
+ if (path === "/api/settings/update-schedule") {
97
+ const existing = ctx.scheduler?.list(UPDATE_SCHEDULE_TARGET)[0];
98
+ if (method === "GET") {
99
+ json(res, 200, {
100
+ enabled: existing?.enabled ?? false,
101
+ cron: existing?.cron ?? "0 4 * * *",
102
+ channel: existing?.message === "beta" ? "beta" : "stable",
103
+ currentVersion: ctx.currentVersion ?? null,
104
+ lastChecked: existing?.last_triggered_at ?? null,
105
+ });
106
+ return true;
107
+ }
108
+ if (method === "PUT") {
109
+ if (!ctx.scheduler) {
110
+ json(res, 503, { error: "scheduler not available" });
111
+ return true;
112
+ }
113
+ readBody(req, 64 * 1024).then(buf => {
114
+ let body;
115
+ try {
116
+ body = JSON.parse(buf.toString("utf-8") || "{}");
117
+ }
118
+ catch {
119
+ return json(res, 400, { error: "invalid JSON" });
120
+ }
121
+ const cron = typeof body.cron === "string" && body.cron.trim() ? body.cron.trim() : "0 4 * * *";
122
+ const channel = body.channel === "beta" ? "beta" : "stable";
123
+ const enabled = body.enabled === true;
124
+ try {
125
+ if (existing)
126
+ ctx.scheduler.update(existing.id, { cron, message: channel, enabled });
127
+ else {
128
+ const created = ctx.scheduler.create({ cron, message: channel, source: "system", target: UPDATE_SCHEDULE_TARGET, reply_chat_id: "", reply_thread_id: null, label: "Auto Update" }, true);
129
+ if (!enabled)
130
+ ctx.scheduler.update(created.id, { enabled: false });
131
+ }
132
+ json(res, 200, { ok: true, enabled, cron, channel });
133
+ }
134
+ catch (e) {
135
+ json(res, 400, { error: e.message });
136
+ }
137
+ }).catch(() => json(res, 400, { error: "bad request" }));
138
+ return true;
139
+ }
140
+ }
141
+ // Everything below mutates — needs an in-memory fleet config.
142
+ const cfg = ctx.fleetConfig;
143
+ // ── Fleet defaults ──
144
+ if (method === "PUT" && path === "/api/settings/fleet/defaults") {
145
+ if (!cfg) {
146
+ json(res, 503, { error: "fleet not loaded" });
147
+ return true;
148
+ }
149
+ readBody(req, 512 * 1024).then(buf => {
150
+ let body;
151
+ try {
152
+ body = JSON.parse(buf.toString("utf-8") || "{}");
153
+ }
154
+ catch {
155
+ return json(res, 400, { error: "invalid JSON" });
156
+ }
157
+ if (typeof body !== "object" || body === null || Array.isArray(body))
158
+ return json(res, 400, { error: "expected an object" });
159
+ const merged = { ...cfg.defaults, ...body };
160
+ const before = validateFleetConfig(cfg);
161
+ const after = validateFleetConfig({ ...cfg, defaults: merged });
162
+ if (rejectIfWorse(res, before, after))
163
+ return;
164
+ cfg.defaults = merged;
165
+ ctx.saveFleetConfig();
166
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
167
+ }).catch(() => json(res, 400, { error: "bad request" }));
168
+ return true;
169
+ }
170
+ // ── Fleet channels (full replace) ──
171
+ if (method === "PUT" && path === "/api/settings/fleet/channels") {
172
+ if (!cfg) {
173
+ json(res, 503, { error: "fleet not loaded" });
174
+ return true;
175
+ }
176
+ readBody(req, 512 * 1024).then(buf => {
177
+ let body;
178
+ try {
179
+ body = JSON.parse(buf.toString("utf-8") || "[]");
180
+ }
181
+ catch {
182
+ return json(res, 400, { error: "invalid JSON" });
183
+ }
184
+ if (!Array.isArray(body))
185
+ return json(res, 400, { error: "expected an array of channels" });
186
+ const next = { ...cfg, channels: body };
187
+ delete next.channel; // channels[] supersedes the legacy single channel
188
+ const before = validateFleetConfig(cfg);
189
+ const after = validateFleetConfig(next);
190
+ if (rejectIfWorse(res, before, after))
191
+ return;
192
+ cfg.channels = body;
193
+ delete cfg.channel;
194
+ ctx.saveFleetConfig();
195
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
196
+ }).catch(() => json(res, 400, { error: "bad request" }));
197
+ return true;
198
+ }
199
+ // ── Classic defaults ──
200
+ if (method === "PUT" && path === "/api/settings/classic/defaults") {
201
+ readBody(req, 512 * 1024).then(buf => {
202
+ let body;
203
+ try {
204
+ body = JSON.parse(buf.toString("utf-8") || "{}");
205
+ }
206
+ catch {
207
+ return json(res, 400, { error: "invalid JSON" });
208
+ }
209
+ if (typeof body !== "object" || body === null || Array.isArray(body))
210
+ return json(res, 400, { error: "expected an object" });
211
+ const classic = readClassic(ctx);
212
+ const merged = { ...(classic.defaults ?? {}), ...body };
213
+ const before = validateClassicBotConfig(classic);
214
+ const after = validateClassicBotConfig({ ...classic, defaults: merged });
215
+ if (rejectIfWorse(res, before, after))
216
+ return;
217
+ classic.defaults = merged;
218
+ writeFileSync(classicPath(ctx), yaml.dump(classic, { lineWidth: -1 }));
219
+ ctx.logger.info("settings: updated classicBot defaults");
220
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
221
+ }).catch(() => json(res, 400, { error: "bad request" }));
222
+ return true;
223
+ }
224
+ // ── Reload (SIGHUP) ──
225
+ if (method === "POST" && path === "/api/settings/reload") {
226
+ ctx.logger.info("settings: reload requested — sending SIGHUP");
227
+ try {
228
+ process.kill(process.pid, "SIGHUP");
229
+ }
230
+ catch (err) {
231
+ ctx.logger.warn({ err }, "settings: SIGHUP failed");
232
+ }
233
+ json(res, 200, { ok: true });
234
+ return true;
235
+ }
236
+ // ── Instances (create / patch / delete) ──
237
+ const validName = (n) => !!n && /^[^\\/\x00]+$/.test(n);
238
+ // Create-or-merge an instance, blocking only on newly-introduced errors.
239
+ const commitInstance = (name, exists, body) => {
240
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
241
+ json(res, 400, { error: "expected an object" });
242
+ return;
243
+ }
244
+ const base = (exists ? cfg.instances[name] : {});
245
+ const mergedInst = { ...base, ...body };
246
+ const before = validateFleetConfig(cfg);
247
+ const after = validateFleetConfig({ ...cfg, instances: { ...cfg.instances, [name]: mergedInst } });
248
+ if (rejectIfWorse(res, before, after))
249
+ return;
250
+ cfg.instances[name] = mergedInst;
251
+ ctx.saveFleetConfig();
252
+ json(res, 200, { ok: true, warnings: saveWarnings(before, after) });
253
+ };
254
+ // POST /api/settings/fleet/instances — create, name taken from the body.
255
+ if (method === "POST" && path === "/api/settings/fleet/instances") {
256
+ if (!cfg) {
257
+ json(res, 503, { error: "fleet not loaded" });
258
+ return true;
259
+ }
260
+ readBody(req, 512 * 1024).then(buf => {
261
+ let body;
262
+ try {
263
+ body = JSON.parse(buf.toString("utf-8") || "{}");
264
+ }
265
+ catch {
266
+ return json(res, 400, { error: "invalid JSON" });
267
+ }
268
+ if (typeof body !== "object" || body === null || Array.isArray(body))
269
+ return json(res, 400, { error: "expected an object" });
270
+ const name = typeof body.name === "string" ? body.name.trim() : "";
271
+ if (!validName(name))
272
+ return json(res, 400, { error: "missing or invalid instance name (provide `name` in the body)" });
273
+ if (cfg.instances[name])
274
+ return json(res, 409, { error: "instance already exists" });
275
+ const { name: _n, ...instBody } = body;
276
+ commitInstance(name, false, instBody);
277
+ }).catch(() => json(res, 400, { error: "bad request" }));
278
+ return true;
279
+ }
280
+ // /api/settings/fleet/instances/:name — create / patch / delete.
281
+ const instPrefix = "/api/settings/fleet/instances/";
282
+ if (path.startsWith(instPrefix)) {
283
+ if (!cfg) {
284
+ json(res, 503, { error: "fleet not loaded" });
285
+ return true;
286
+ }
287
+ const name = decodeURIComponent(path.slice(instPrefix.length));
288
+ if (!validName(name)) {
289
+ json(res, 400, { error: "invalid instance name" });
290
+ return true;
291
+ }
292
+ if (method === "DELETE") {
293
+ if (!cfg.instances[name]) {
294
+ json(res, 404, { error: "instance not found" });
295
+ return true;
296
+ }
297
+ // DELETE never blocks on validation; surface any resulting warnings.
298
+ delete cfg.instances[name];
299
+ ctx.saveFleetConfig();
300
+ json(res, 200, { ok: true, warnings: validateFleetConfig(cfg).warnings });
301
+ return true;
302
+ }
303
+ if (method === "POST" || method === "PATCH") {
304
+ const exists = !!cfg.instances[name];
305
+ if (method === "POST" && exists) {
306
+ json(res, 409, { error: "instance already exists" });
307
+ return true;
308
+ }
309
+ if (method === "PATCH" && !exists) {
310
+ json(res, 404, { error: "instance not found" });
311
+ return true;
312
+ }
313
+ readBody(req, 512 * 1024).then(buf => {
314
+ let body;
315
+ try {
316
+ body = JSON.parse(buf.toString("utf-8") || "{}");
317
+ }
318
+ catch {
319
+ return json(res, 400, { error: "invalid JSON" });
320
+ }
321
+ commitInstance(name, exists, body);
322
+ }).catch(() => json(res, 400, { error: "bad request" }));
323
+ return true;
324
+ }
325
+ json(res, 405, { error: "method not allowed" });
326
+ return true;
327
+ }
328
+ json(res, 404, { error: "not found" });
329
+ return true;
330
+ }
331
+ //# 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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"}
@@ -28,6 +28,20 @@ export declare class TopicCommands {
28
28
  private getReplyAdapter;
29
29
  /** Parse and dispatch commands from the General topic */
30
30
  handleGeneralCommand(msg: InboundMessage): Promise<boolean>;
31
+ /**
32
+ * Build the dashboard URL text (View / Settings / Web UI). The Settings/Web UI
33
+ * URLs carry the web token; when `htmlSpoiler` is set they're wrapped in a
34
+ * Telegram HTML spoiler (`<tg-spoiler>`) so the token isn't shown in the clear
35
+ * in a shared topic (the caller must send with format: "html"). /view is
36
+ * public, so it's never spoilered. DC uses the plain form (ephemeral reply).
37
+ */
38
+ getDashboardText(htmlSpoiler?: boolean): string;
39
+ /**
40
+ * /dashboard (TG): admin-only. Replies directly in the topic; the token-
41
+ * bearing URLs are wrapped in a Telegram HTML spoiler so they aren't shown in
42
+ * the clear (the adapter supports plain/HTML, not MarkdownV2's `||…||`).
43
+ */
44
+ private handleDashboardCommand;
31
45
  /** Handle /ctx or /compact in any instance topic — returns true if handled */
32
46
  handleInstanceCommand(msg: InboundMessage, instanceName: string): Promise<boolean>;
33
47
  /** Get context usage text for an instance (shared by TG + DC) */
@@ -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, "");
@@ -42,7 +44,7 @@ export function parseContextPercent(pane) {
42
44
  const lines = pane.split("\n");
43
45
  for (let i = lines.length - 1; i >= 0; i--) {
44
46
  const line = lines[i];
45
- const m = line.match(/(\d+)%\s*[!❯>]/) || line.match(/◔\s*(\d+)%/) || line.match(/\[(\d+)%\]/);
47
+ const m = line.match(/(\d+)%.*[!❯>]/) || line.match(/◔\s*(\d+)%/) || line.match(/\[(\d+)%\]/);
46
48
  if (m)
47
49
  return parseInt(m[1], 10);
48
50
  }
@@ -86,8 +88,59 @@ export class TopicCommands {
86
88
  await this.handleUpdateCommand(msg);
87
89
  return true;
88
90
  }
91
+ if (text === "/dashboard" || text.startsWith("/dashboard@")) {
92
+ await this.handleDashboardCommand(msg);
93
+ return true;
94
+ }
89
95
  return false;
90
96
  }
97
+ /**
98
+ * Build the dashboard URL text (View / Settings / Web UI). The Settings/Web UI
99
+ * URLs carry the web token; when `htmlSpoiler` is set they're wrapped in a
100
+ * Telegram HTML spoiler (`<tg-spoiler>`) so the token isn't shown in the clear
101
+ * in a shared topic (the caller must send with format: "html"). /view is
102
+ * public, so it's never spoilered. DC uses the plain form (ephemeral reply).
103
+ */
104
+ getDashboardText(htmlSpoiler = false) {
105
+ const port = this.ctx.fleetConfig?.health_port ?? 19280;
106
+ const host = this.ctx.fleetConfig?.hostname || "localhost";
107
+ let token = "";
108
+ try {
109
+ token = readFileSync(join(getAgendHome(), "web.token"), "utf-8").trim();
110
+ }
111
+ catch { /* not started yet */ }
112
+ const base = `http://${host}:${port}`;
113
+ const hide = (u) => htmlSpoiler ? `<tg-spoiler>${u}</tg-spoiler>` : u;
114
+ return [
115
+ t("dashboard.title"),
116
+ "",
117
+ `• View: ${base}/view`,
118
+ `• Settings: ${hide(`${base}/settings?token=${token}`)}`,
119
+ `• Web UI: ${hide(`${base}/ui?token=${token}`)}`,
120
+ ].join("\n");
121
+ }
122
+ /**
123
+ * /dashboard (TG): admin-only. Replies directly in the topic; the token-
124
+ * bearing URLs are wrapped in a Telegram HTML spoiler so they aren't shown in
125
+ * the clear (the adapter supports plain/HTML, not MarkdownV2's `||…||`).
126
+ */
127
+ async handleDashboardCommand(msg) {
128
+ const adapter = this.getReplyAdapter(msg);
129
+ if (!adapter)
130
+ return;
131
+ const chatId = msg.chatId;
132
+ const threadId = msg.threadId;
133
+ const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
134
+ if (allowed.length === 0) {
135
+ await adapter.sendText(chatId, t("dashboard.disabled"), { threadId });
136
+ return;
137
+ }
138
+ if (!allowed.some(u => String(u) === String(msg.userId))) {
139
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
140
+ return;
141
+ }
142
+ await adapter.sendText(chatId, this.getDashboardText(true), { threadId, format: "html" });
143
+ }
91
144
  /** Handle /ctx or /compact in any instance topic — returns true if handled */
92
145
  async handleInstanceCommand(msg, instanceName) {
93
146
  const text = msg.text?.trim();
@@ -99,8 +152,8 @@ export class TopicCommands {
99
152
  return false;
100
153
  const isCollab = this.ctx.toggleFleetCollab(instanceName);
101
154
  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 });
155
+ ? t("collab.on")
156
+ : t("collab.off"), { threadId: msg.threadId });
104
157
  return true;
105
158
  }
106
159
  if (text === "/compact" || text.startsWith("/compact@")) {
@@ -116,7 +169,7 @@ export class TopicCommands {
116
169
  if (!adapter)
117
170
  return false;
118
171
  const ok = this.ctx.cancelInstance(instanceName);
119
- await adapter.sendText(msg.chatId, ok ? `🛑 已送出取消給 ${instanceName}。` : `❌ ${instanceName} 未在執行。`, { threadId: msg.threadId });
172
+ await adapter.sendText(msg.chatId, ok ? t("cancel.sent", instanceName) : t("cancel.not_running", instanceName), { threadId: msg.threadId });
120
173
  return true;
121
174
  }
122
175
  if (text === "/save" || text.startsWith("/save ") || text.startsWith("/save@")) {
@@ -125,11 +178,11 @@ export class TopicCommands {
125
178
  return false;
126
179
  const filename = parseSaveFilename(text);
127
180
  if (!filename) {
128
- await adapter.sendText(msg.chatId, "Usage: /save <filename>", { threadId: msg.threadId });
181
+ await adapter.sendText(msg.chatId, t("save.usage"), { threadId: msg.threadId });
129
182
  return true;
130
183
  }
131
184
  if (!SAVE_FILENAME_RE.test(filename)) {
132
- await adapter.sendText(msg.chatId, "⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.", { threadId: msg.threadId });
185
+ await adapter.sendText(msg.chatId, t("filename.invalid"), { threadId: msg.threadId });
133
186
  return true;
134
187
  }
135
188
  const result = await this.sendSave(instanceName, filename);
@@ -173,8 +226,8 @@ export class TopicCommands {
173
226
  catch { /* ignore */ }
174
227
  }
175
228
  return context != null
176
- ? `📊 Context: ${context}% used\nBackend: ${backend}\nInstance: ${instanceName}`
177
- : `Context info not available yet.\nBackend: ${backend}\nInstance: ${instanceName}`;
229
+ ? `${t("ctx.used", context)}\n${t("ctx.backend", backend)}\n${t("ctx.instance", instanceName)}`
230
+ : `${t("ctx.unavailable")}\n${t("ctx.backend", backend)}\n${t("ctx.instance", instanceName)}`;
178
231
  }
179
232
  /** Send /compact to an instance's tmux pane */
180
233
  async sendCompact(instanceName) {
@@ -207,10 +260,10 @@ export class TopicCommands {
207
260
  const threadId = msg.threadId;
208
261
  const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
209
262
  if (allowed.length > 0 && !allowed.some(u => String(u) === String(msg.userId))) {
210
- await adapter.sendText(chatId, "⛔ Not authorized", { threadId });
263
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
211
264
  return;
212
265
  }
213
- await adapter.sendText(chatId, "🔄 Graceful restart — waiting for instances to idle...", { threadId });
266
+ await adapter.sendText(chatId, t("restart.graceful"), { threadId });
214
267
  process.kill(process.pid, "SIGUSR2");
215
268
  }
216
269
  async handleStatusCommand(msg) {
@@ -314,14 +367,14 @@ export class TopicCommands {
314
367
  // Access control — only allowed users can trigger update; empty = disabled
315
368
  const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
316
369
  if (allowed.length === 0) {
317
- await adapter.sendText(chatId, "⛔ /update disabled — no allowed_users configured", { threadId });
370
+ await adapter.sendText(chatId, t("update.disabled"), { threadId });
318
371
  return;
319
372
  }
320
373
  if (!allowed.some(u => String(u) === String(msg.userId))) {
321
- await adapter.sendText(chatId, "⛔ Not authorized", { threadId });
374
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
322
375
  return;
323
376
  }
324
- await adapter.sendText(chatId, "📦 Updating AgEnD... Fleet will restart automatically.", { threadId });
377
+ await adapter.sendText(chatId, t("update.running"), { threadId });
325
378
  const currentVersion = createRequire(import.meta.url)("../package.json").version ?? "";
326
379
  const updateCmd = currentVersion.includes("beta") ? "agend update --beta" : "agend update";
327
380
  const { spawn } = await import("node:child_process");
@@ -336,10 +389,10 @@ export class TopicCommands {
336
389
  const threadId = msg.threadId;
337
390
  const allowed = this.ctx.fleetConfig?.channel?.access?.allowed_users ?? [];
338
391
  if (allowed.length > 0 && !allowed.some(u => String(u) === String(msg.userId))) {
339
- await adapter.sendText(chatId, "⛔ Not authorized", { threadId });
392
+ await adapter.sendText(chatId, t("not_authorized"), { threadId });
340
393
  return;
341
394
  }
342
- await adapter.sendText(chatId, "🩺 Running diagnostics...", { threadId });
395
+ await adapter.sendText(chatId, t("doctor.running"), { threadId });
343
396
  try {
344
397
  const { execSync } = await import("node:child_process");
345
398
  const backend = this.ctx.fleetConfig?.defaults?.backend || "claude-code";
@@ -419,7 +472,15 @@ export class TopicCommands {
419
472
  }
420
473
  if (platformType === "discord") {
421
474
  const ch = channels.find(c => c.type === "discord");
422
- config.topic_id = ch?.options?.general_channel_id ?? 1;
475
+ const gcid = ch?.options?.general_channel_id;
476
+ // A Discord general needs a real channel id — NOT the TG-convention
477
+ // "1", which makes the DC adapter throw fetching channel "1". Skip
478
+ // (leave unbound) if there's no valid channel to bind to.
479
+ if (gcid == null || !/^\d{17,}$/.test(String(gcid))) {
480
+ this.ctx.logger.warn({ name }, "Discord general has no valid general_channel_id — skipping topic bind (set channel.options.general_channel_id)");
481
+ continue;
482
+ }
483
+ config.topic_id = gcid;
423
484
  }
424
485
  else {
425
486
  config.topic_id = 1;
@@ -463,6 +524,7 @@ export class TopicCommands {
463
524
  commands: [
464
525
  { command: "status", description: "Show fleet status and costs" },
465
526
  { command: "sysinfo", description: "System diagnostics" },
527
+ { command: "dashboard", description: "🔒 Get dashboard URLs" },
466
528
  { command: "ctx", description: "Show context usage" },
467
529
  { command: "compact", description: "Compact agent context" },
468
530
  { command: "restart", description: "🔒 Graceful restart all instances" },