@songsid/agend 2.0.11-beta.30 β 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.
- package/dist/channel/adapters/discord.js +16 -15
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/fleet-manager.d.ts +2 -0
- package/dist/fleet-manager.js +100 -74
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.d.ts +19 -0
- package/dist/locale.js +137 -0
- package/dist/locale.js.map +1 -0
- package/dist/quickstart.js +7 -0
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +1 -1
- package/dist/scheduler/scheduler.js +15 -4
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/settings-api.d.ts +30 -0
- package/dist/settings-api.js +48 -0
- package/dist/settings-api.js.map +1 -1
- package/dist/topic-commands.js +18 -17
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/ui/settings.html +197 -42
- package/dist/ui/view.html +60 -9
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -37,6 +37,7 @@ import { outboundHandlers } from "./outbound-handlers.js";
|
|
|
37
37
|
import { handleWebRequest, broadcastSseEvent } from "./web-api.js";
|
|
38
38
|
import { handleViewRequest, isViewPath } from "./view-api.js";
|
|
39
39
|
import { handleSettingsRequest } from "./settings-api.js";
|
|
40
|
+
import { setLocale, detectLocale, t } from "./locale.js";
|
|
40
41
|
import { handleAgentRequest } from "./agent-endpoint.js";
|
|
41
42
|
import { ClassicChannelManager } from "./classic-channel-manager.js";
|
|
42
43
|
import { getTmuxSession } from "./config.js";
|
|
@@ -208,6 +209,15 @@ export class FleetManager {
|
|
|
208
209
|
getInstanceDir(name) {
|
|
209
210
|
return join(this.dataDir, "instances", name);
|
|
210
211
|
}
|
|
212
|
+
/** AgEnD package version (for the Settings "current version" / What's New). */
|
|
213
|
+
get currentVersion() {
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version ?? "unknown";
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return "unknown";
|
|
219
|
+
}
|
|
220
|
+
}
|
|
211
221
|
/**
|
|
212
222
|
* Resolve a slash-command target in a channel. Classic channels are looked up
|
|
213
223
|
* per-bot (same-channel multi-bot); a fleet-topic instance is found via the
|
|
@@ -388,6 +398,7 @@ export class FleetManager {
|
|
|
388
398
|
const { rotateLogIfNeeded } = await import("./logger.js");
|
|
389
399
|
rotateLogIfNeeded(join(this.dataDir, "fleet.log"));
|
|
390
400
|
const fleet = this.loadConfig(configPath);
|
|
401
|
+
setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
|
|
391
402
|
const topicMode = fleet.channel?.mode === "topic" || !!fleet.channels?.some(ch => ch.mode === "topic");
|
|
392
403
|
// Set tmux socket isolation for custom AGEND_HOME
|
|
393
404
|
const { getTmuxSocketName: getSocket } = await import("./paths.js");
|
|
@@ -905,12 +916,12 @@ export class FleetManager {
|
|
|
905
916
|
else if (data.command === "chat") {
|
|
906
917
|
const text = data.text ?? "";
|
|
907
918
|
if (!text) {
|
|
908
|
-
await data.respond("
|
|
919
|
+
await data.respond(t("chat.usage"));
|
|
909
920
|
return;
|
|
910
921
|
}
|
|
911
922
|
const name = this.classicChannels?.getInstanceByChannel(data.channelId, adapterId);
|
|
912
923
|
if (!name) {
|
|
913
|
-
await data.respond("
|
|
924
|
+
await data.respond(t("classic.no_agent_start"));
|
|
914
925
|
return;
|
|
915
926
|
}
|
|
916
927
|
const replyMsgId = await data.respond("π");
|
|
@@ -932,26 +943,26 @@ export class FleetManager {
|
|
|
932
943
|
else if (data.command === "load") {
|
|
933
944
|
// load is kiro-cli/classic only β no claude-code equivalent.
|
|
934
945
|
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
935
|
-
await data.respond("
|
|
946
|
+
await data.respond(t("admin.required"));
|
|
936
947
|
return;
|
|
937
948
|
}
|
|
938
949
|
const name = this.classicChannels?.getInstanceByChannel(data.channelId, adapterId);
|
|
939
950
|
if (!name) {
|
|
940
|
-
await data.respond("
|
|
951
|
+
await data.respond(t("classic.no_agent_start"));
|
|
941
952
|
return;
|
|
942
953
|
}
|
|
943
954
|
const filename = data.options?.filename;
|
|
944
955
|
if (!SAVE_FILENAME_RE.test(filename ?? "")) {
|
|
945
|
-
await data.respond("
|
|
956
|
+
await data.respond(t("filename.invalid"));
|
|
946
957
|
return;
|
|
947
958
|
}
|
|
948
959
|
this.pasteRawToClassicInstance(name, `/chat load ${filename}`);
|
|
949
|
-
await data.respond(
|
|
960
|
+
await data.respond(t("save.sent", `/chat load ${filename}`, name));
|
|
950
961
|
}
|
|
951
962
|
else if (data.command === "compact") {
|
|
952
963
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
953
964
|
if (!name) {
|
|
954
|
-
await data.respond("
|
|
965
|
+
await data.respond(t("classic.no_agent"));
|
|
955
966
|
return;
|
|
956
967
|
}
|
|
957
968
|
const result = await this.topicCommands.sendCompact(name);
|
|
@@ -960,16 +971,16 @@ export class FleetManager {
|
|
|
960
971
|
else if (data.command === "cancel") {
|
|
961
972
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
962
973
|
if (!name) {
|
|
963
|
-
await data.respond("
|
|
974
|
+
await data.respond(t("classic.no_agent"));
|
|
964
975
|
return;
|
|
965
976
|
}
|
|
966
977
|
const ok = this.cancelInstance(name);
|
|
967
|
-
await data.respond(ok ?
|
|
978
|
+
await data.respond(ok ? t("cancel.sent", name) : t("cancel.not_running", name));
|
|
968
979
|
}
|
|
969
980
|
else if (data.command === "ctx") {
|
|
970
981
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
971
982
|
if (!name) {
|
|
972
|
-
await data.respond("
|
|
983
|
+
await data.respond(t("classic.no_agent"));
|
|
973
984
|
return;
|
|
974
985
|
}
|
|
975
986
|
// Single source of truth (statusline.json + robust tmux pane fallback).
|
|
@@ -982,33 +993,33 @@ export class FleetManager {
|
|
|
982
993
|
if (collabTarget) {
|
|
983
994
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
984
995
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
985
|
-
await data.respond("
|
|
996
|
+
await data.respond(t("not_authorized"));
|
|
986
997
|
return;
|
|
987
998
|
}
|
|
988
999
|
const isCollab = this.toggleFleetCollab(collabTarget.name);
|
|
989
|
-
await data.respond(isCollab ? "
|
|
1000
|
+
await data.respond(isCollab ? t("collab.on") : t("collab.off"));
|
|
990
1001
|
return;
|
|
991
1002
|
}
|
|
992
1003
|
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
993
|
-
await data.respond("
|
|
1004
|
+
await data.respond(t("admin.required"));
|
|
994
1005
|
return;
|
|
995
1006
|
}
|
|
996
1007
|
if (!this.classicChannels.isClassicChannel(data.channelId, adapterId)) {
|
|
997
|
-
await data.respond("
|
|
1008
|
+
await data.respond(t("classic.no_agent_start"));
|
|
998
1009
|
return;
|
|
999
1010
|
}
|
|
1000
1011
|
const newState = this.classicChannels.toggleCollab(data.channelId, adapterId);
|
|
1001
1012
|
await data.respond(newState
|
|
1002
|
-
? "
|
|
1003
|
-
: "
|
|
1013
|
+
? t("collab.on.classic")
|
|
1014
|
+
: t("collab.off.classic"));
|
|
1004
1015
|
}
|
|
1005
1016
|
else if (data.command === "update") {
|
|
1006
1017
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1007
1018
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1008
|
-
await data.respond("
|
|
1019
|
+
await data.respond(t("not_authorized"));
|
|
1009
1020
|
return;
|
|
1010
1021
|
}
|
|
1011
|
-
await data.respond("
|
|
1022
|
+
await data.respond(t("update.running"));
|
|
1012
1023
|
const { spawn } = await import("node:child_process");
|
|
1013
1024
|
const _cv = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8")).version ?? "";
|
|
1014
1025
|
const _cmd = _cv.includes("beta") ? "agend update --beta" : "agend update";
|
|
@@ -1018,7 +1029,7 @@ export class FleetManager {
|
|
|
1018
1029
|
else if (data.command === "doctor") {
|
|
1019
1030
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1020
1031
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1021
|
-
await data.respond("
|
|
1032
|
+
await data.respond(t("not_authorized"));
|
|
1022
1033
|
return;
|
|
1023
1034
|
}
|
|
1024
1035
|
try {
|
|
@@ -1045,11 +1056,11 @@ export class FleetManager {
|
|
|
1045
1056
|
// the web-token-bearing URLs are only visible to the caller.
|
|
1046
1057
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1047
1058
|
if (allowed.length === 0) {
|
|
1048
|
-
await data.respond("
|
|
1059
|
+
await data.respond(t("dashboard.disabled"));
|
|
1049
1060
|
return;
|
|
1050
1061
|
}
|
|
1051
1062
|
if (!allowed.some(u => String(u) === String(data.userId))) {
|
|
1052
|
-
await data.respond("
|
|
1063
|
+
await data.respond(t("not_authorized"));
|
|
1053
1064
|
return;
|
|
1054
1065
|
}
|
|
1055
1066
|
await data.respond(this.topicCommands.getDashboardText());
|
|
@@ -1057,16 +1068,16 @@ export class FleetManager {
|
|
|
1057
1068
|
else if (data.command === "restart") {
|
|
1058
1069
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1059
1070
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1060
|
-
await data.respond("
|
|
1071
|
+
await data.respond(t("not_authorized"));
|
|
1061
1072
|
return;
|
|
1062
1073
|
}
|
|
1063
|
-
await data.respond("
|
|
1074
|
+
await data.respond(t("restart.graceful"));
|
|
1064
1075
|
process.kill(process.pid, "SIGUSR2");
|
|
1065
1076
|
}
|
|
1066
1077
|
else if (data.command === "compact") {
|
|
1067
1078
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1068
1079
|
if (!name) {
|
|
1069
|
-
await data.respond("
|
|
1080
|
+
await data.respond(t("classic.no_agent"));
|
|
1070
1081
|
return;
|
|
1071
1082
|
}
|
|
1072
1083
|
const result = await this.topicCommands.sendCompact(name);
|
|
@@ -1184,12 +1195,12 @@ export class FleetManager {
|
|
|
1184
1195
|
else if (data.command === "chat") {
|
|
1185
1196
|
const text = data.text ?? "";
|
|
1186
1197
|
if (!text) {
|
|
1187
|
-
await data.respond("
|
|
1198
|
+
await data.respond(t("chat.usage"));
|
|
1188
1199
|
return;
|
|
1189
1200
|
}
|
|
1190
1201
|
const name = this.classicChannels?.getInstanceByChannel(data.channelId, adapterId);
|
|
1191
1202
|
if (!name) {
|
|
1192
|
-
await data.respond("
|
|
1203
|
+
await data.respond(t("classic.no_agent_start"));
|
|
1193
1204
|
return;
|
|
1194
1205
|
}
|
|
1195
1206
|
const replyMsgId = await data.respond("π");
|
|
@@ -1211,35 +1222,35 @@ export class FleetManager {
|
|
|
1211
1222
|
else if (data.command === "load") {
|
|
1212
1223
|
// load is kiro-cli/classic only β no claude-code equivalent.
|
|
1213
1224
|
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
1214
|
-
await data.respond("
|
|
1225
|
+
await data.respond(t("admin.required"));
|
|
1215
1226
|
return;
|
|
1216
1227
|
}
|
|
1217
1228
|
const name = this.classicChannels?.getInstanceByChannel(data.channelId, adapterId);
|
|
1218
1229
|
if (!name) {
|
|
1219
|
-
await data.respond("
|
|
1230
|
+
await data.respond(t("classic.no_agent_start"));
|
|
1220
1231
|
return;
|
|
1221
1232
|
}
|
|
1222
1233
|
const filename = data.options?.filename;
|
|
1223
1234
|
if (!SAVE_FILENAME_RE.test(filename ?? "")) {
|
|
1224
|
-
await data.respond("
|
|
1235
|
+
await data.respond(t("filename.invalid"));
|
|
1225
1236
|
return;
|
|
1226
1237
|
}
|
|
1227
1238
|
this.pasteRawToClassicInstance(name, `/chat load ${filename}`);
|
|
1228
|
-
await data.respond(
|
|
1239
|
+
await data.respond(t("save.sent", `/chat load ${filename}`, name));
|
|
1229
1240
|
}
|
|
1230
1241
|
else if (data.command === "cancel") {
|
|
1231
1242
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1232
1243
|
if (!name) {
|
|
1233
|
-
await data.respond("
|
|
1244
|
+
await data.respond(t("classic.no_agent"));
|
|
1234
1245
|
return;
|
|
1235
1246
|
}
|
|
1236
1247
|
const ok = this.cancelInstance(name);
|
|
1237
|
-
await data.respond(ok ?
|
|
1248
|
+
await data.respond(ok ? t("cancel.sent", name) : t("cancel.not_running", name));
|
|
1238
1249
|
}
|
|
1239
1250
|
else if (data.command === "ctx") {
|
|
1240
1251
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1241
1252
|
if (!name) {
|
|
1242
|
-
await data.respond("
|
|
1253
|
+
await data.respond(t("classic.no_agent"));
|
|
1243
1254
|
return;
|
|
1244
1255
|
}
|
|
1245
1256
|
// Single source of truth (statusline.json + robust tmux pane fallback).
|
|
@@ -1252,33 +1263,33 @@ export class FleetManager {
|
|
|
1252
1263
|
if (collabTarget2) {
|
|
1253
1264
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1254
1265
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1255
|
-
await data.respond("
|
|
1266
|
+
await data.respond(t("not_authorized"));
|
|
1256
1267
|
return;
|
|
1257
1268
|
}
|
|
1258
1269
|
const isCollab = this.toggleFleetCollab(collabTarget2.name);
|
|
1259
|
-
await data.respond(isCollab ? "
|
|
1270
|
+
await data.respond(isCollab ? t("collab.on") : t("collab.off"));
|
|
1260
1271
|
return;
|
|
1261
1272
|
}
|
|
1262
1273
|
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
1263
|
-
await data.respond("
|
|
1274
|
+
await data.respond(t("admin.required"));
|
|
1264
1275
|
return;
|
|
1265
1276
|
}
|
|
1266
1277
|
if (!this.classicChannels.isClassicChannel(data.channelId, adapterId)) {
|
|
1267
|
-
await data.respond("
|
|
1278
|
+
await data.respond(t("classic.no_agent_start"));
|
|
1268
1279
|
return;
|
|
1269
1280
|
}
|
|
1270
1281
|
const newState = this.classicChannels.toggleCollab(data.channelId, adapterId);
|
|
1271
1282
|
await data.respond(newState
|
|
1272
|
-
? "
|
|
1273
|
-
: "
|
|
1283
|
+
? t("collab.on.classic")
|
|
1284
|
+
: t("collab.off.classic"));
|
|
1274
1285
|
}
|
|
1275
1286
|
else if (data.command === "update") {
|
|
1276
1287
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1277
1288
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1278
|
-
await data.respond("
|
|
1289
|
+
await data.respond(t("not_authorized"));
|
|
1279
1290
|
return;
|
|
1280
1291
|
}
|
|
1281
|
-
await data.respond("
|
|
1292
|
+
await data.respond(t("update.running"));
|
|
1282
1293
|
const { spawn } = await import("node:child_process");
|
|
1283
1294
|
const _cv = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8")).version ?? "";
|
|
1284
1295
|
const _cmd = _cv.includes("beta") ? "agend update --beta" : "agend update";
|
|
@@ -1288,7 +1299,7 @@ export class FleetManager {
|
|
|
1288
1299
|
else if (data.command === "doctor") {
|
|
1289
1300
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1290
1301
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1291
|
-
await data.respond("
|
|
1302
|
+
await data.respond(t("not_authorized"));
|
|
1292
1303
|
return;
|
|
1293
1304
|
}
|
|
1294
1305
|
try {
|
|
@@ -1315,11 +1326,11 @@ export class FleetManager {
|
|
|
1315
1326
|
// the web-token-bearing URLs are only visible to the caller.
|
|
1316
1327
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1317
1328
|
if (allowed.length === 0) {
|
|
1318
|
-
await data.respond("
|
|
1329
|
+
await data.respond(t("dashboard.disabled"));
|
|
1319
1330
|
return;
|
|
1320
1331
|
}
|
|
1321
1332
|
if (!allowed.some(u => String(u) === String(data.userId))) {
|
|
1322
|
-
await data.respond("
|
|
1333
|
+
await data.respond(t("not_authorized"));
|
|
1323
1334
|
return;
|
|
1324
1335
|
}
|
|
1325
1336
|
await data.respond(this.topicCommands.getDashboardText());
|
|
@@ -1327,16 +1338,16 @@ export class FleetManager {
|
|
|
1327
1338
|
else if (data.command === "restart") {
|
|
1328
1339
|
const allowed = this.fleetConfig?.channel?.access?.allowed_users ?? [];
|
|
1329
1340
|
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
1330
|
-
await data.respond("
|
|
1341
|
+
await data.respond(t("not_authorized"));
|
|
1331
1342
|
return;
|
|
1332
1343
|
}
|
|
1333
|
-
await data.respond("
|
|
1344
|
+
await data.respond(t("restart.graceful"));
|
|
1334
1345
|
process.kill(process.pid, "SIGUSR2");
|
|
1335
1346
|
}
|
|
1336
1347
|
else if (data.command === "compact") {
|
|
1337
1348
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
1338
1349
|
if (!name) {
|
|
1339
|
-
await data.respond("
|
|
1350
|
+
await data.respond(t("classic.no_agent"));
|
|
1340
1351
|
return;
|
|
1341
1352
|
}
|
|
1342
1353
|
const result = await this.topicCommands.sendCompact(name);
|
|
@@ -1677,7 +1688,7 @@ export class FleetManager {
|
|
|
1677
1688
|
if (generalId) {
|
|
1678
1689
|
this.notifyInstanceTopic(generalId, `π Unauthorized user tried /start in private chat:\nβ’ Name: ${msg.username}\nβ’ ID: ${msg.userId}\nβ’ Platform: ${msg.source}\n\nTo allow: add \`${msg.userId}\` to classicBot.yaml \`allowed_users\``);
|
|
1679
1690
|
}
|
|
1680
|
-
await msgAdapter?.sendText(chatId, "
|
|
1691
|
+
await msgAdapter?.sendText(chatId, t("classic.not_allowed_user"));
|
|
1681
1692
|
return;
|
|
1682
1693
|
}
|
|
1683
1694
|
}
|
|
@@ -1690,11 +1701,11 @@ export class FleetManager {
|
|
|
1690
1701
|
if (generalId) {
|
|
1691
1702
|
this.notifyInstanceTopic(generalId, adminMsg);
|
|
1692
1703
|
}
|
|
1693
|
-
await msgAdapter?.sendText(chatId, "
|
|
1704
|
+
await msgAdapter?.sendText(chatId, t("classic.access_requested"));
|
|
1694
1705
|
return;
|
|
1695
1706
|
}
|
|
1696
1707
|
if (!this.classicChannels.isAdmin(msg.userId)) {
|
|
1697
|
-
await msgAdapter?.sendText(chatId, "
|
|
1708
|
+
await msgAdapter?.sendText(chatId, t("classic.admin_only_start"));
|
|
1698
1709
|
const generalId = this.findGeneralInstance(msg.adapterId);
|
|
1699
1710
|
if (generalId) {
|
|
1700
1711
|
this.notifyInstanceTopic(generalId, `π User wants to /start but is not admin:\nβ’ Name: ${msg.username}\nβ’ ID: ${msg.userId}\nβ’ Platform: ${msg.source}\nβ’ Group: ${chatId}\n\nTo approve: add \`${msg.userId}\` to classicBot.yaml \`admin_users\``);
|
|
@@ -1711,7 +1722,7 @@ export class FleetManager {
|
|
|
1711
1722
|
// Handle /stop command
|
|
1712
1723
|
if (text === "/stop" || text.startsWith("/stop ")) {
|
|
1713
1724
|
if (!this.classicChannels.isAdmin(msg.userId)) {
|
|
1714
|
-
await msgAdapter?.sendText(chatId, "
|
|
1725
|
+
await msgAdapter?.sendText(chatId, t("classic.admin_only_stop"));
|
|
1715
1726
|
const generalId = this.findGeneralInstance(msg.adapterId);
|
|
1716
1727
|
if (generalId) {
|
|
1717
1728
|
this.notifyInstanceTopic(generalId, `π User wants to /stop but is not admin:\nβ’ Name: ${msg.username}\nβ’ ID: ${msg.userId}\nβ’ Platform: ${msg.source}\nβ’ Group: ${chatId}\n\nTo approve: add \`${msg.userId}\` to classicBot.yaml \`admin_users\``);
|
|
@@ -1725,12 +1736,12 @@ export class FleetManager {
|
|
|
1725
1736
|
// Handle /compact command (admin only)
|
|
1726
1737
|
if (text === "/compact" || text.startsWith("/compact@")) {
|
|
1727
1738
|
if (!this.classicChannels.isAdmin(msg.userId)) {
|
|
1728
|
-
await msgAdapter?.sendText(chatId, "
|
|
1739
|
+
await msgAdapter?.sendText(chatId, t("cmd.admin_required", "/compact"));
|
|
1729
1740
|
return;
|
|
1730
1741
|
}
|
|
1731
1742
|
const compactName = this.classicChannels.getInstanceByChannel(chatId, msg.adapterId);
|
|
1732
1743
|
if (!compactName) {
|
|
1733
|
-
await msgAdapter?.sendText(chatId, "
|
|
1744
|
+
await msgAdapter?.sendText(chatId, t("classic.no_agent_start"));
|
|
1734
1745
|
return;
|
|
1735
1746
|
}
|
|
1736
1747
|
const result = await this.topicCommands.sendCompact(compactName);
|
|
@@ -1741,7 +1752,7 @@ export class FleetManager {
|
|
|
1741
1752
|
if (text === "/cancel" || text.startsWith("/cancel@")) {
|
|
1742
1753
|
const cancelName = this.classicChannels.getInstanceByChannel(chatId, msg.adapterId);
|
|
1743
1754
|
if (!cancelName) {
|
|
1744
|
-
await msgAdapter?.sendText(chatId, "
|
|
1755
|
+
await msgAdapter?.sendText(chatId, t("classic.no_agent_start"));
|
|
1745
1756
|
return;
|
|
1746
1757
|
}
|
|
1747
1758
|
const ok = this.cancelInstance(cancelName);
|
|
@@ -1752,7 +1763,7 @@ export class FleetManager {
|
|
|
1752
1763
|
if (text === "/ctx" || text.startsWith("/ctx@")) {
|
|
1753
1764
|
const ctxName = this.classicChannels.getInstanceByChannel(chatId, msg.adapterId);
|
|
1754
1765
|
if (!ctxName) {
|
|
1755
|
-
await msgAdapter?.sendText(chatId, "
|
|
1766
|
+
await msgAdapter?.sendText(chatId, t("classic.no_agent_start"));
|
|
1756
1767
|
return;
|
|
1757
1768
|
}
|
|
1758
1769
|
const reply = await this.topicCommands.getCtxText(ctxName);
|
|
@@ -1762,21 +1773,21 @@ export class FleetManager {
|
|
|
1762
1773
|
// Handle /save command (admin only)
|
|
1763
1774
|
if (text === "/save" || text.startsWith("/save ") || text.startsWith("/save@")) {
|
|
1764
1775
|
if (!this.classicChannels.isAdmin(msg.userId)) {
|
|
1765
|
-
await msgAdapter?.sendText(chatId, "
|
|
1776
|
+
await msgAdapter?.sendText(chatId, t("cmd.admin_required", "/save"));
|
|
1766
1777
|
return;
|
|
1767
1778
|
}
|
|
1768
1779
|
const saveName = this.classicChannels.getInstanceByChannel(chatId, msg.adapterId);
|
|
1769
1780
|
if (!saveName) {
|
|
1770
|
-
await msgAdapter?.sendText(chatId, "
|
|
1781
|
+
await msgAdapter?.sendText(chatId, t("classic.no_agent_start"));
|
|
1771
1782
|
return;
|
|
1772
1783
|
}
|
|
1773
1784
|
const filename = parseSaveFilename(text);
|
|
1774
1785
|
if (!filename) {
|
|
1775
|
-
await msgAdapter?.sendText(chatId, "
|
|
1786
|
+
await msgAdapter?.sendText(chatId, t("save.usage"));
|
|
1776
1787
|
return;
|
|
1777
1788
|
}
|
|
1778
1789
|
if (!SAVE_FILENAME_RE.test(filename)) {
|
|
1779
|
-
await msgAdapter?.sendText(chatId, "
|
|
1790
|
+
await msgAdapter?.sendText(chatId, t("filename.invalid"));
|
|
1780
1791
|
return;
|
|
1781
1792
|
}
|
|
1782
1793
|
const backend = this.classicChannels.getBackendByInstance(saveName, this.fleetConfig?.defaults?.backend);
|
|
@@ -1786,7 +1797,7 @@ export class FleetManager {
|
|
|
1786
1797
|
return;
|
|
1787
1798
|
}
|
|
1788
1799
|
this.pasteRawToClassicInstance(saveName, cmd);
|
|
1789
|
-
await msgAdapter?.sendText(chatId,
|
|
1800
|
+
await msgAdapter?.sendText(chatId, t("save.sent", cmd, saveName));
|
|
1790
1801
|
return;
|
|
1791
1802
|
}
|
|
1792
1803
|
// Route to classic channel if this bot has an agent here (per-bot).
|
|
@@ -1804,7 +1815,7 @@ export class FleetManager {
|
|
|
1804
1815
|
// Strip @bot from text and forward as /chat
|
|
1805
1816
|
const cleanText = botUser ? text.replace(new RegExp(`@${botUser}`, "gi"), "").trim() : text;
|
|
1806
1817
|
if (cleanText.startsWith("/raw") && !this.classicChannels.isAdmin(msg.userId)) {
|
|
1807
|
-
await msgAdapter?.sendText(chatId, "
|
|
1818
|
+
await msgAdapter?.sendText(chatId, t("cmd.admin_required", "/raw"));
|
|
1808
1819
|
return;
|
|
1809
1820
|
}
|
|
1810
1821
|
const syntheticMsg = { ...msg, threadId: chatId, text: `/chat ${cleanText}` };
|
|
@@ -1813,7 +1824,7 @@ export class FleetManager {
|
|
|
1813
1824
|
}
|
|
1814
1825
|
// Handle @bot without active agent
|
|
1815
1826
|
if (isBotMentioned) {
|
|
1816
|
-
await msgAdapter?.sendText(chatId, "
|
|
1827
|
+
await msgAdapter?.sendText(chatId, t("classic.no_agent_start"));
|
|
1817
1828
|
return;
|
|
1818
1829
|
}
|
|
1819
1830
|
// Unregistered private chat: ignore (don't fall through to General)
|
|
@@ -2097,6 +2108,21 @@ export class FleetManager {
|
|
|
2097
2108
|
// ===================== Scheduler =====================
|
|
2098
2109
|
async handleScheduleTrigger(schedule) {
|
|
2099
2110
|
const { target, reply_chat_id, reply_thread_id, message, label, id, source } = schedule;
|
|
2111
|
+
// Reserved auto-update schedule: run `agend update` instead of delivering a
|
|
2112
|
+
// message to an instance. message === "beta" selects the beta channel.
|
|
2113
|
+
if (target === "__fleet_update__") {
|
|
2114
|
+
const cmd = message === "beta" ? "agend update --beta" : "agend update";
|
|
2115
|
+
this.logger.info({ cmd, scheduleId: id }, "Auto-update schedule fired");
|
|
2116
|
+
try {
|
|
2117
|
+
const { spawn } = await import("node:child_process");
|
|
2118
|
+
spawn("sh", ["-c", `sleep 1 && ${cmd}`], { detached: true, stdio: "ignore" }).unref();
|
|
2119
|
+
this.scheduler?.recordRun(id, "delivered", `ran ${cmd}`);
|
|
2120
|
+
}
|
|
2121
|
+
catch (err) {
|
|
2122
|
+
this.scheduler?.recordRun(id, "retry", `update spawn failed: ${err.message}`);
|
|
2123
|
+
}
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2100
2126
|
const RATE_LIMIT_DEFER_THRESHOLD = 85;
|
|
2101
2127
|
const rl = this.statuslineWatcher.getRateLimits(target);
|
|
2102
2128
|
if (rl && rl.five_hour_pct > RATE_LIMIT_DEFER_THRESHOLD) {
|
|
@@ -2762,7 +2788,7 @@ export class FleetManager {
|
|
|
2762
2788
|
*/
|
|
2763
2789
|
async handleSlashSave(data, adapterId) {
|
|
2764
2790
|
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
2765
|
-
await data.respond("
|
|
2791
|
+
await data.respond(t("admin.required"));
|
|
2766
2792
|
return;
|
|
2767
2793
|
}
|
|
2768
2794
|
// Classic resolves per-bot (same-channel multi-bot); otherwise a fleet topic.
|
|
@@ -2771,12 +2797,12 @@ export class FleetManager {
|
|
|
2771
2797
|
? { kind: "classic", name: classicName }
|
|
2772
2798
|
: this.routing.resolve(data.channelId);
|
|
2773
2799
|
if (!target) {
|
|
2774
|
-
await data.respond("
|
|
2800
|
+
await data.respond(t("classic.no_agent_start"));
|
|
2775
2801
|
return;
|
|
2776
2802
|
}
|
|
2777
2803
|
const filename = data.options?.filename ?? "";
|
|
2778
2804
|
if (!SAVE_FILENAME_RE.test(filename)) {
|
|
2779
|
-
await data.respond("
|
|
2805
|
+
await data.respond(t("filename.invalid"));
|
|
2780
2806
|
return;
|
|
2781
2807
|
}
|
|
2782
2808
|
const backend = target.kind === "classic"
|
|
@@ -2795,7 +2821,7 @@ export class FleetManager {
|
|
|
2795
2821
|
else {
|
|
2796
2822
|
this.instanceIpcClients.get(target.name)?.send({ type: "raw_paste", content: cmd });
|
|
2797
2823
|
}
|
|
2798
|
-
await data.respond(
|
|
2824
|
+
await data.respond(t("save.sent", cmd, target.name));
|
|
2799
2825
|
}
|
|
2800
2826
|
/** Whether the instance currently has at least one live cancel button. */
|
|
2801
2827
|
hasCancelButton(instanceName) {
|
|
@@ -2837,7 +2863,7 @@ export class FleetManager {
|
|
|
2837
2863
|
type: "cancel",
|
|
2838
2864
|
instanceName,
|
|
2839
2865
|
message: "π θηδΈβ¦",
|
|
2840
|
-
choices: [{ id: `cancel:${instanceName}`, label: "
|
|
2866
|
+
choices: [{ id: `cancel:${instanceName}`, label: t("cancel.button") }],
|
|
2841
2867
|
}, threadId ? { threadId } : undefined);
|
|
2842
2868
|
// A concurrent sendCancelButton for the same instance may have posted its
|
|
2843
2869
|
// own button while we awaited notifyAlert. Retire any other buttons for
|
|
@@ -3619,15 +3645,15 @@ When users create specialized instances, suggest these configurations:
|
|
|
3619
3645
|
if (generalId) {
|
|
3620
3646
|
this.notifyInstanceTopic(generalId, `π Unauthorized guild tried /start:\nβ’ Guild ID: ${guildId}\nβ’ User: ${userId}\nβ’ Platform: discord\n\nTo allow: add \`${guildId}\` to classicBot.yaml \`allowed_guilds\``);
|
|
3621
3647
|
}
|
|
3622
|
-
return "
|
|
3648
|
+
return t("classic.not_authorized_guild");
|
|
3623
3649
|
}
|
|
3624
3650
|
// Per-bot check: a second bot may /start in the same channel (own agent).
|
|
3625
3651
|
if (this.classicChannels.isClassicChannel(channelId, adapterId))
|
|
3626
|
-
return "
|
|
3652
|
+
return t("classic.already_active");
|
|
3627
3653
|
// Classic no longer lives in the routing engine, so this only guards against
|
|
3628
3654
|
// a fleet topic-mode instance colliding with the channel.
|
|
3629
3655
|
if (this.routing.resolve(channelId))
|
|
3630
|
-
return "
|
|
3656
|
+
return t("classic.topic_bound");
|
|
3631
3657
|
const instanceName = this.classicChannels.deriveInstanceName(channelName || channelId, channelId, adapterId);
|
|
3632
3658
|
this.classicChannels.register(channelId, adapterId, instanceName, channelName || channelId, userId);
|
|
3633
3659
|
// Bind this classic instance to the bot that started it (authoritative), so
|
|
@@ -3642,7 +3668,7 @@ When users create specialized instances, suggest these configurations:
|
|
|
3642
3668
|
this.classicChannels.toggleCollab(channelId, adapterId);
|
|
3643
3669
|
}
|
|
3644
3670
|
this.logger.info({ channelId, adapterId, instanceName, userId }, "Classic channel started");
|
|
3645
|
-
return
|
|
3671
|
+
return t("classic.started");
|
|
3646
3672
|
}
|
|
3647
3673
|
/** Handle /stop slash command β unregister classic channel */
|
|
3648
3674
|
async handleClassicStop(channelId, adapterId) {
|
|
@@ -3650,12 +3676,12 @@ When users create specialized instances, suggest these configurations:
|
|
|
3650
3676
|
return "Classic channel manager not initialized.";
|
|
3651
3677
|
const ch = this.classicChannels.unregister(channelId, adapterId);
|
|
3652
3678
|
if (!ch)
|
|
3653
|
-
return "
|
|
3679
|
+
return t("classic.no_agent");
|
|
3654
3680
|
this.instanceWorldBinding.delete(ch.instanceName);
|
|
3655
3681
|
await this.stopInstance(ch.instanceName).catch(err => this.logger.warn({ err, instanceName: ch.instanceName }, "Failed to stop classic instance"));
|
|
3656
3682
|
this.reregisterClassicChannels();
|
|
3657
3683
|
this.logger.info({ channelId, adapterId, instanceName: ch.instanceName }, "Classic channel stopped");
|
|
3658
|
-
return
|
|
3684
|
+
return t("classic.stopped");
|
|
3659
3685
|
}
|
|
3660
3686
|
async stopAll() {
|
|
3661
3687
|
this.ipcStoppingInstances.add("__fleet_stopping__");
|
|
@@ -4066,7 +4092,7 @@ When users create specialized instances, suggest these configurations:
|
|
|
4066
4092
|
if (target && this.semverGt(target, currentVersion)) {
|
|
4067
4093
|
const generalId = this.findGeneralInstance();
|
|
4068
4094
|
if (generalId) {
|
|
4069
|
-
this.notifyInstanceTopic(generalId,
|
|
4095
|
+
this.notifyInstanceTopic(generalId, t("update.available", `v${target}`) + ` (current: v${currentVersion})`);
|
|
4070
4096
|
}
|
|
4071
4097
|
}
|
|
4072
4098
|
}
|