@songsid/agend 2.1.5-beta.5 → 2.1.5-beta.7
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 +13 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/cli.js +37 -37
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +38 -0
- package/dist/daemon.js +95 -43
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +9 -0
- package/dist/fleet-manager.js +179 -20
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.d.ts +20 -0
- package/dist/full-restart.js +39 -0
- package/dist/full-restart.js.map +1 -0
- package/dist/locale.js +24 -2
- package/dist/locale.js.map +1 -1
- package/dist/restart-progress.d.ts +4 -1
- package/dist/restart-progress.js +30 -20
- package/dist/restart-progress.js.map +1 -1
- package/dist/service-restart-selection.d.ts +27 -0
- package/dist/service-restart-selection.js +25 -0
- package/dist/service-restart-selection.js.map +1 -0
- package/dist/topic-commands.js +22 -7
- package/dist/topic-commands.js.map +1 -1
- package/dist/update-marker.d.ts +11 -1
- package/dist/update-marker.js +19 -4
- package/dist/update-marker.js.map +1 -1
- package/dist/update-progress.d.ts +1 -1
- package/dist/update-progress.js +12 -0
- package/dist/update-progress.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-context.d.ts
CHANGED
|
@@ -103,6 +103,8 @@ export interface FleetContext {
|
|
|
103
103
|
/** Persist and edit one `/update` message across the fleet process restart. */
|
|
104
104
|
beginUpdateProgress?(adapter: import("./channel/types.js").ChannelAdapter, chatId: string, threadId: string | undefined, messageId: string): void;
|
|
105
105
|
failUpdateProgress?(message: string): void;
|
|
106
|
+
/** Persist `/restart full`, wait for idle, then launch the environment-aware service restart. */
|
|
107
|
+
requestFullRestart?(adapter: import("./channel/types.js").ChannelAdapter, chatId: string, threadId: string | undefined, messageId: string): Promise<boolean>;
|
|
106
108
|
/** Post one random, not-yet-dismissed tip with a dismiss button. */
|
|
107
109
|
promptTip?(generalName: string, adapter: import("./channel/types.js").ChannelAdapter, chatId: string, threadId?: string): Promise<"posted" | "empty" | "unavailable">;
|
|
108
110
|
/** Persistently enable advanced tips without waiting for the dismissal threshold. */
|
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -221,6 +221,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
221
221
|
private updateProgressEditRunning;
|
|
222
222
|
private lastUpdateProgressText;
|
|
223
223
|
private updateCompletionTipText;
|
|
224
|
+
/** Injectable only to keep the chat→CLI reload hand-off deterministic in tests. */
|
|
225
|
+
private fullRestartLauncher;
|
|
224
226
|
private eventLogPruneTimer;
|
|
225
227
|
private logRotateTimer;
|
|
226
228
|
/** Days of event/activity history to keep. */
|
|
@@ -309,6 +311,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
309
311
|
private isModelAdmin;
|
|
310
312
|
private handlePauseWakeSlash;
|
|
311
313
|
private handleUpdateSlash;
|
|
314
|
+
private handleRestartSlash;
|
|
312
315
|
private handleTipsSlash;
|
|
313
316
|
/** Admin-only full conversation reset for fleet-topic and Classic instances. */
|
|
314
317
|
private handleClearSlash;
|
|
@@ -508,6 +511,12 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
508
511
|
token: string | null;
|
|
509
512
|
};
|
|
510
513
|
beginUpdateProgress(adapter: ChannelAdapter, chatId: string, threadId: string | undefined, messageId: string): void;
|
|
514
|
+
/** Persist the public response, wait for idle, then start the canonical service restart. */
|
|
515
|
+
requestFullRestart(adapter: ChannelAdapter, chatId: string, threadId: string | undefined, messageId: string): Promise<boolean>;
|
|
516
|
+
private isOwnedFullRestartMarker;
|
|
517
|
+
/** Give current work the same bounded idle grace used by graceful reload. */
|
|
518
|
+
private waitForFullRestartIdleGrace;
|
|
519
|
+
private reportFullRestartFailure;
|
|
511
520
|
failUpdateProgress(message: string): void;
|
|
512
521
|
/** The old fleet edits CLI install stages; the new fleet adopts the same message below. */
|
|
513
522
|
private startUpdateProgressMonitor;
|
package/dist/fleet-manager.js
CHANGED
|
@@ -7,7 +7,7 @@ import { join, dirname, basename } from "node:path";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { isDeepStrictEqual } from "node:util";
|
|
9
9
|
import { getAgendHome, ensureWorkspaceGit } from "./paths.js";
|
|
10
|
-
import { beginUpdateProgress as persistUpdateProgress, clearUpdateMarker, isUpdateInProgress, readUpdateProgress, setUpdateProgressStage, } from "./update-marker.js";
|
|
10
|
+
import { beginFullRestartProgress as persistFullRestartProgress, beginUpdateProgress as persistUpdateProgress, clearUpdateMarker, isUpdateInProgress, readUpdateProgress, setUpdateProgressStage, updateProgressOperation, } from "./update-marker.js";
|
|
11
11
|
import { formatUpdateProgress } from "./update-progress.js";
|
|
12
12
|
import { sdNotify, sdNotifyBlocking } from "./sd-notify.js";
|
|
13
13
|
import { readFleetMemory } from "./process-memory.js";
|
|
@@ -60,7 +60,8 @@ import { clearPausedMarker } from "./pause-marker.js";
|
|
|
60
60
|
import { releaseProcessFleetLock } from "./fleet-lock.js";
|
|
61
61
|
import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
|
|
62
62
|
import { loadOrCreateWebToken, WEB_TOKEN_INVALID_MESSAGE } from "./web-auth.js";
|
|
63
|
-
import { RESTART_PROGRESS_TERMINAL_TIMEOUT_MS, RestartProgress, } from "./restart-progress.js";
|
|
63
|
+
import { formatRestartProgressCompletion, RESTART_PROGRESS_TERMINAL_TIMEOUT_MS, RestartProgress, } from "./restart-progress.js";
|
|
64
|
+
import { launchFullRestartHelper } from "./full-restart.js";
|
|
64
65
|
import { collectRedundantInstanceDefaultPaths } from "./fleet-yaml-slim.js";
|
|
65
66
|
import { StormWindow } from "./storm-window.js";
|
|
66
67
|
import { SpawnGate } from "./spawn-gate.js";
|
|
@@ -411,6 +412,8 @@ export class FleetManager {
|
|
|
411
412
|
updateProgressEditRunning = false;
|
|
412
413
|
lastUpdateProgressText = null;
|
|
413
414
|
updateCompletionTipText = null;
|
|
415
|
+
/** Injectable only to keep the chat→CLI reload hand-off deterministic in tests. */
|
|
416
|
+
fullRestartLauncher = launchFullRestartHelper;
|
|
414
417
|
eventLogPruneTimer = null;
|
|
415
418
|
logRotateTimer = null;
|
|
416
419
|
/** Days of event/activity history to keep. */
|
|
@@ -797,6 +800,28 @@ export class FleetManager {
|
|
|
797
800
|
child.once("error", err => this.failUpdateProgress(err.message));
|
|
798
801
|
child.unref();
|
|
799
802
|
}
|
|
803
|
+
async handleRestartSlash(data, adapterId) {
|
|
804
|
+
if (!this.isFleetAdmin(data.userId, adapterId)) {
|
|
805
|
+
await data.respond(t("not_authorized"));
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
if (data.options?.mode !== "full") {
|
|
809
|
+
await data.respond(t("restart.graceful"));
|
|
810
|
+
process.kill(process.pid, "SIGUSR2");
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
// Discord exposes only `full` as a choice. Keep a runtime check anyway: a
|
|
814
|
+
// briefly stale command schema must never turn an unknown value into SIGUSR1.
|
|
815
|
+
const messageId = await data.respond(t("restart.full_preparing"));
|
|
816
|
+
const adapter = this.adapters.get(adapterId) ?? this.adapter;
|
|
817
|
+
if (!messageId || !adapter) {
|
|
818
|
+
this.logger.error({ adapterId, hasMessageId: !!messageId }, "Full restart response could not be persisted — reload refused");
|
|
819
|
+
await data.respond(t("restart.full_launch_failed"));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
const chatId = String(this.getChannelConfig(adapterId)?.group_id ?? data.channelId);
|
|
823
|
+
await this.requestFullRestart(adapter, chatId, data.channelId, messageId);
|
|
824
|
+
}
|
|
800
825
|
async handleTipsSlash(data, adapterId) {
|
|
801
826
|
if (!this.fleetConfig)
|
|
802
827
|
return;
|
|
@@ -1982,6 +2007,130 @@ export class FleetManager {
|
|
|
1982
2007
|
this.updateCompletionTipText = null;
|
|
1983
2008
|
this.startUpdateProgressMonitor(adapter);
|
|
1984
2009
|
}
|
|
2010
|
+
/** Persist the public response, wait for idle, then start the canonical service restart. */
|
|
2011
|
+
async requestFullRestart(adapter, chatId, threadId, messageId) {
|
|
2012
|
+
const target = {
|
|
2013
|
+
adapterId: adapter.id,
|
|
2014
|
+
chatId,
|
|
2015
|
+
...(threadId ? { threadId } : {}),
|
|
2016
|
+
messageId,
|
|
2017
|
+
};
|
|
2018
|
+
// This check is synchronous with the marker write below. A second command
|
|
2019
|
+
// cannot slip through between them on Node's event loop and replace the
|
|
2020
|
+
// first command's delivery target or launch a competing restart helper.
|
|
2021
|
+
if (this.shuttingDown || isUpdateInProgress(this.dataDir)) {
|
|
2022
|
+
this.logger.warn({ adapterId: adapter.id }, "Full restart refused because another planned restart is active");
|
|
2023
|
+
await this.reportFullRestartFailure(adapter, chatId, threadId, messageId, t("restart.full_busy"));
|
|
2024
|
+
return false;
|
|
2025
|
+
}
|
|
2026
|
+
if (!persistFullRestartProgress(this.dataDir, target)) {
|
|
2027
|
+
this.logger.error({ adapterId: adapter.id }, "Full restart marker could not be persisted — reload refused");
|
|
2028
|
+
await this.reportFullRestartFailure(adapter, chatId, threadId, messageId, t("restart.full_launch_failed"));
|
|
2029
|
+
return false;
|
|
2030
|
+
}
|
|
2031
|
+
const ownedMarker = readUpdateProgress(this.dataDir);
|
|
2032
|
+
if (!ownedMarker) {
|
|
2033
|
+
this.logger.error({ adapterId: adapter.id }, "Full restart marker disappeared after persistence — reload refused");
|
|
2034
|
+
await this.reportFullRestartFailure(adapter, chatId, threadId, messageId, t("restart.full_launch_failed"));
|
|
2035
|
+
return false;
|
|
2036
|
+
}
|
|
2037
|
+
this.lastUpdateProgressText = null;
|
|
2038
|
+
this.updateCompletionTipText = null;
|
|
2039
|
+
this.startUpdateProgressMonitor(adapter);
|
|
2040
|
+
await this.waitForFullRestartIdleGrace();
|
|
2041
|
+
// An update can begin while the idle wait yields. Never launch a second
|
|
2042
|
+
// process replacement against a marker we no longer own.
|
|
2043
|
+
if (this.shuttingDown || !this.isOwnedFullRestartMarker(ownedMarker.startedAt, target)) {
|
|
2044
|
+
this.logger.warn({ adapterId: adapter.id }, "Full restart superseded during idle wait — reload refused");
|
|
2045
|
+
if (this.isOwnedFullRestartMarker(ownedMarker.startedAt, target))
|
|
2046
|
+
clearUpdateMarker(this.dataDir);
|
|
2047
|
+
await this.reportFullRestartFailure(adapter, chatId, threadId, messageId, t("restart.full_busy"));
|
|
2048
|
+
return false;
|
|
2049
|
+
}
|
|
2050
|
+
let helper;
|
|
2051
|
+
try {
|
|
2052
|
+
helper = await this.fullRestartLauncher();
|
|
2053
|
+
}
|
|
2054
|
+
catch (err) {
|
|
2055
|
+
this.logger.error({ err }, "Full restart helper failed to spawn — reload refused");
|
|
2056
|
+
if (this.isOwnedFullRestartMarker(ownedMarker.startedAt, target))
|
|
2057
|
+
clearUpdateMarker(this.dataDir);
|
|
2058
|
+
await this.reportFullRestartFailure(adapter, chatId, threadId, messageId, t("restart.full_launch_failed"));
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
2061
|
+
void helper.completion.then(result => {
|
|
2062
|
+
// A zero exit means the service manager accepted the restart. launchd
|
|
2063
|
+
// may report that before its SIGTERM reaches us, so only an explicit
|
|
2064
|
+
// helper error/non-zero exit is evidence of failure. A signal is also
|
|
2065
|
+
// ambiguous under systemd because this helper shares the old cgroup.
|
|
2066
|
+
if (this.shuttingDown || !this.isOwnedFullRestartMarker(ownedMarker.startedAt, target))
|
|
2067
|
+
return;
|
|
2068
|
+
if (!result.error && (result.code === 0 || result.code === null))
|
|
2069
|
+
return;
|
|
2070
|
+
const detail = result.error
|
|
2071
|
+
? "reload helper failed after launch"
|
|
2072
|
+
: `reload helper exited before process hand-off (code ${result.code ?? "null"}, signal ${result.signal ?? "none"})`;
|
|
2073
|
+
this.logger.error({ result }, "Full restart helper exited before the fleet began shutting down");
|
|
2074
|
+
setUpdateProgressStage(this.dataDir, "failed", { error: detail });
|
|
2075
|
+
});
|
|
2076
|
+
return true;
|
|
2077
|
+
}
|
|
2078
|
+
isOwnedFullRestartMarker(startedAt, target) {
|
|
2079
|
+
const marker = readUpdateProgress(this.dataDir);
|
|
2080
|
+
if (!marker || marker.startedAt !== startedAt || marker.pid !== process.pid)
|
|
2081
|
+
return false;
|
|
2082
|
+
if (updateProgressOperation(marker.progress) !== "full-restart")
|
|
2083
|
+
return false;
|
|
2084
|
+
const current = marker.progress.target;
|
|
2085
|
+
return current.adapterId === target.adapterId
|
|
2086
|
+
&& current.chatId === target.chatId
|
|
2087
|
+
&& current.threadId === target.threadId
|
|
2088
|
+
&& current.messageId === target.messageId;
|
|
2089
|
+
}
|
|
2090
|
+
/** Give current work the same bounded idle grace used by graceful reload. */
|
|
2091
|
+
async waitForFullRestartIdleGrace() {
|
|
2092
|
+
const instanceNames = [...this.daemons.keys()];
|
|
2093
|
+
if (instanceNames.length === 0)
|
|
2094
|
+
return;
|
|
2095
|
+
const IDLE_TIMEOUT_MS = 5 * 60_000;
|
|
2096
|
+
let timeoutHandle;
|
|
2097
|
+
const deadline = new Promise((_, reject) => {
|
|
2098
|
+
timeoutHandle = setTimeout(() => reject(new Error("Idle wait timed out after 5 minutes")), IDLE_TIMEOUT_MS);
|
|
2099
|
+
});
|
|
2100
|
+
try {
|
|
2101
|
+
await Promise.race([
|
|
2102
|
+
Promise.all(instanceNames.map(async (name) => {
|
|
2103
|
+
const daemon = this.daemons.get(name);
|
|
2104
|
+
if (!daemon)
|
|
2105
|
+
return;
|
|
2106
|
+
this.logger.info(`Full restart: waiting for ${name} to idle...`);
|
|
2107
|
+
await daemon.waitForIdle(10_000);
|
|
2108
|
+
})),
|
|
2109
|
+
deadline,
|
|
2110
|
+
]);
|
|
2111
|
+
}
|
|
2112
|
+
catch (err) {
|
|
2113
|
+
this.logger.warn({ err }, "Full restart idle wait timed out — continuing with service restart");
|
|
2114
|
+
}
|
|
2115
|
+
finally {
|
|
2116
|
+
clearTimeout(timeoutHandle);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
async reportFullRestartFailure(adapter, chatId, threadId, messageId, text) {
|
|
2120
|
+
try {
|
|
2121
|
+
await adapter.editMessage(chatId, messageId, text, threadId);
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
catch (err) {
|
|
2125
|
+
this.logger.warn({ err, adapterId: adapter.id }, "Failed to edit rejected full-restart request; posting a fresh notice");
|
|
2126
|
+
}
|
|
2127
|
+
try {
|
|
2128
|
+
await adapter.sendText(chatId, text, { threadId });
|
|
2129
|
+
}
|
|
2130
|
+
catch (err) {
|
|
2131
|
+
this.logger.error({ err, adapterId: adapter.id }, "Failed to deliver full-restart rejection");
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
1985
2134
|
failUpdateProgress(message) {
|
|
1986
2135
|
setUpdateProgressStage(this.dataDir, "failed", { error: message });
|
|
1987
2136
|
}
|
|
@@ -2064,6 +2213,9 @@ export class FleetManager {
|
|
|
2064
2213
|
&& savedUpdateProgress.progress.stage !== "complete"
|
|
2065
2214
|
? savedUpdateProgress
|
|
2066
2215
|
: null;
|
|
2216
|
+
const pendingProgressOperation = pendingUpdateProgress
|
|
2217
|
+
? updateProgressOperation(pendingUpdateProgress.progress)
|
|
2218
|
+
: null;
|
|
2067
2219
|
if (pendingUpdateProgress) {
|
|
2068
2220
|
setUpdateProgressStage(this.dataDir, "starting", { version: pendingUpdateProgress.progress.version });
|
|
2069
2221
|
}
|
|
@@ -2344,7 +2496,7 @@ export class FleetManager {
|
|
|
2344
2496
|
const allEntries = Object.entries(fleet.instances);
|
|
2345
2497
|
const generals = allEntries.filter(([_, cfg]) => cfg.general_topic);
|
|
2346
2498
|
const others = allEntries.filter(([_, cfg]) => !cfg.general_topic);
|
|
2347
|
-
const startupProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), pendingUpdateProgress?.startedAt ?? startupStartedAt, this.logger, { mode: pendingUpdateProgress ? "update" : "restart" });
|
|
2499
|
+
const startupProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), pendingUpdateProgress?.startedAt ?? startupStartedAt, this.logger, { mode: pendingProgressOperation === "full-restart" ? "reload" : pendingUpdateProgress ? "update" : "restart" });
|
|
2348
2500
|
if (generals.length > 0) {
|
|
2349
2501
|
for (const [name, cfg] of generals) {
|
|
2350
2502
|
try {
|
|
@@ -2513,7 +2665,7 @@ export class FleetManager {
|
|
|
2513
2665
|
version: agendVersion,
|
|
2514
2666
|
pausedNames,
|
|
2515
2667
|
failedNames,
|
|
2516
|
-
tipText:
|
|
2668
|
+
tipText: pendingProgressOperation === "update" && this.tipsEnabled()
|
|
2517
2669
|
? (() => {
|
|
2518
2670
|
const tip = this.pickAvailableTip();
|
|
2519
2671
|
return tip ? this.formatTip(tip) : undefined;
|
|
@@ -2522,7 +2674,16 @@ export class FleetManager {
|
|
|
2522
2674
|
});
|
|
2523
2675
|
if (!progressCompleted && fleet.channel?.group_id) {
|
|
2524
2676
|
let text;
|
|
2525
|
-
if (
|
|
2677
|
+
if (pendingProgressOperation === "full-restart") {
|
|
2678
|
+
text = formatRestartProgressCompletion("reload", {
|
|
2679
|
+
running: started,
|
|
2680
|
+
total,
|
|
2681
|
+
version: agendVersion,
|
|
2682
|
+
pausedNames,
|
|
2683
|
+
failedNames,
|
|
2684
|
+
}, pendingUpdateProgress.startedAt);
|
|
2685
|
+
}
|
|
2686
|
+
else if (failedNames.length === 0 && pausedNames.length === 0) {
|
|
2526
2687
|
text = t("fleet.ready", started, total, agendVersion);
|
|
2527
2688
|
}
|
|
2528
2689
|
else if (failedNames.length === 0) {
|
|
@@ -3068,13 +3229,7 @@ export class FleetManager {
|
|
|
3068
3229
|
await data.respond(this.topicCommands.getDashboardText());
|
|
3069
3230
|
}
|
|
3070
3231
|
else if (data.command === "restart") {
|
|
3071
|
-
|
|
3072
|
-
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
3073
|
-
await data.respond(t("not_authorized"));
|
|
3074
|
-
return;
|
|
3075
|
-
}
|
|
3076
|
-
await data.respond(t("restart.graceful"));
|
|
3077
|
-
process.kill(process.pid, "SIGUSR2");
|
|
3232
|
+
await this.handleRestartSlash(data, adapterId);
|
|
3078
3233
|
}
|
|
3079
3234
|
else if (data.command === "compact") {
|
|
3080
3235
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
@@ -3417,13 +3572,7 @@ export class FleetManager {
|
|
|
3417
3572
|
await data.respond(this.topicCommands.getDashboardText());
|
|
3418
3573
|
}
|
|
3419
3574
|
else if (data.command === "restart") {
|
|
3420
|
-
|
|
3421
|
-
if (allowed.length > 0 && !allowed.some(u => String(u) === String(data.userId))) {
|
|
3422
|
-
await data.respond(t("not_authorized"));
|
|
3423
|
-
return;
|
|
3424
|
-
}
|
|
3425
|
-
await data.respond(t("restart.graceful"));
|
|
3426
|
-
process.kill(process.pid, "SIGUSR2");
|
|
3575
|
+
await this.handleRestartSlash(data, adapterId);
|
|
3427
3576
|
}
|
|
3428
3577
|
else if (data.command === "compact") {
|
|
3429
3578
|
const name = this.resolveSlashTarget(data.channelId, adapterId);
|
|
@@ -9725,8 +9874,18 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
9725
9874
|
return;
|
|
9726
9875
|
}
|
|
9727
9876
|
this.logger.info(`Full restart: waiting for ${instanceNames.length} instances to idle...`);
|
|
9877
|
+
const trackedProgress = readUpdateProgress(this.dataDir);
|
|
9878
|
+
const trackedFullRestart = trackedProgress
|
|
9879
|
+
&& updateProgressOperation(trackedProgress.progress) === "full-restart"
|
|
9880
|
+
&& trackedProgress.progress.stage !== "failed"
|
|
9881
|
+
&& trackedProgress.progress.stage !== "complete";
|
|
9882
|
+
if (trackedFullRestart) {
|
|
9883
|
+
// `/restart full` already posted and persisted one public progress message.
|
|
9884
|
+
// Keep that single message; the new process will adopt and finish it.
|
|
9885
|
+
setUpdateProgressStage(this.dataDir, "stopping");
|
|
9886
|
+
}
|
|
9728
9887
|
const groupId = this.fleetConfig?.channel?.group_id;
|
|
9729
|
-
if (groupId && this.adapter) {
|
|
9888
|
+
if (!trackedFullRestart && groupId && this.adapter) {
|
|
9730
9889
|
await this.adapter.sendText(String(groupId), t("restart.full_initiated"))
|
|
9731
9890
|
.catch(e => this.logger.warn({ err: e }, "Failed to post full restart notification"));
|
|
9732
9891
|
}
|