@songsid/agend 2.3.2 → 2.3.3-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.
- package/dist/agent-cli-instructions.md +2 -2
- package/dist/agent-whats-new.d.ts +21 -0
- package/dist/agent-whats-new.js +52 -23
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/backend/claude-code.d.ts +13 -1
- package/dist/backend/claude-code.js +20 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/kiro-reconcile-store.js +8 -1
- package/dist/backend/kiro-reconcile-store.js.map +1 -1
- package/dist/backend/kiro.d.ts +0 -5
- package/dist/backend/kiro.js +0 -10
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +7 -4
- package/dist/backend/types.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +3 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +10 -5
- package/dist/cross-instance-envelope.js +20 -17
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +59 -63
- package/dist/daemon.js +275 -313
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +27 -22
- package/dist/delivery-outbox.js +109 -161
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js +10 -25
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/fleet-manager.d.ts +4 -12
- package/dist/fleet-manager.js +28 -92
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +1 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +1 -1
- package/dist/instance-lifecycle.js +21 -5
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +6 -7
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-reconcile.js +3 -3
- package/dist/kiro-reconcile.js.map +1 -1
- package/dist/locale.js +4 -16
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/outbound-handlers.js +1 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/scheduler/task-board.d.ts +17 -12
- package/dist/scheduler/task-board.js +29 -25
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.d.ts +13 -14
- package/dist/sd-notify.js +62 -28
- package/dist/sd-notify.js.map +1 -1
- package/dist/settings-api.d.ts +0 -1
- package/dist/settings-api.js +1 -14
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-effect.js +0 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/task-board-waker.d.ts +40 -1
- package/dist/task-board-waker.js +110 -9
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.d.ts +1 -1
- package/dist/tmux-control.js +3 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.js +18 -16
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +2 -0
- package/dist/tmux-read.js +7 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/topic-commands.d.ts +2 -0
- package/dist/topic-commands.js +2 -1
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +1 -5
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/settings-appearance.js +3 -35
- package/dist/ui/settings-registry.js +0 -1
- package/dist/ui/settings-strings.js +2 -2
- package/dist/update-check.d.ts +13 -4
- package/dist/update-check.js +17 -4
- package/dist/update-check.js.map +1 -1
- package/dist/web-experimental.d.ts +6 -2
- package/dist/web-experimental.js +8 -4
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-settings-patch.d.ts +0 -1
- package/dist/web-settings-patch.js +3 -4
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/workflow-templates/default.md +2 -2
- package/dist/working-recovery.d.ts +0 -2
- package/dist/working-recovery.js +2 -23
- package/dist/working-recovery.js.map +1 -1
- package/package.json +1 -1
- package/dist/upgrade-notices.d.ts +0 -17
- package/dist/upgrade-notices.js +0 -93
- package/dist/upgrade-notices.js.map +0 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -52,7 +52,7 @@ import { applyKiroMigration, kiroMigrationPrompt, planKiroMigration } from "./ki
|
|
|
52
52
|
import { fleetLabel } from "./fleet-label.js";
|
|
53
53
|
import { beginFullRestartProgress as persistFullRestartProgress, beginUpdateProgress as persistUpdateProgress, clearUpdateMarker, isUpdateInProgress, readUpdateProgress, sameUpdateMarker, setUpdateProgressStage, updateMarkerIdentity, updateProgressOperation, updateReceiptOf, } from "./update-marker.js";
|
|
54
54
|
import { formatUpdateProgress } from "./update-progress.js";
|
|
55
|
-
import { sdNotify, sdNotifyBlocking, setSdNotifyWarn } from "./sd-notify.js";
|
|
55
|
+
import { sdNotify, sdNotifyBlocking, setSdNotifyWarn, startSdWatchdog } from "./sd-notify.js";
|
|
56
56
|
import { StartupMemoryDeadline, fleetStopConcurrency } from "./startup-memory-deadline.js";
|
|
57
57
|
import { readFleetMemory } from "./process-memory.js";
|
|
58
58
|
import { MemoryPressure } from "./memory-pressure.js";
|
|
@@ -94,7 +94,7 @@ import { processAttachments } from "./channel/attachment-handler.js";
|
|
|
94
94
|
import { routeToolCall } from "./channel/tool-router.js";
|
|
95
95
|
import { Scheduler } from "./scheduler/index.js";
|
|
96
96
|
import { boardActivity, boardCardLimit, boardToolAction } from "./task-board-tool.js";
|
|
97
|
-
import { TaskBoardWaker } from "./task-board-waker.js";
|
|
97
|
+
import { TaskBoardWaker, namesCard } from "./task-board-waker.js";
|
|
98
98
|
import { TaskBoardError, waitsForPerson, wrapUpGap } from "./scheduler/task-board.js";
|
|
99
99
|
import { escapeTelegramHtml, scheduleClock, scheduleRetryLabel } from "./scheduler/retry-label.js";
|
|
100
100
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
@@ -254,7 +254,6 @@ import { isSetupComplete, markSetupComplete } from "./setup-marker.js";
|
|
|
254
254
|
import { manualCleanupMessage, reapStaleTunnel } from "./tunnel/lease.js";
|
|
255
255
|
import { buildToolPermissionsNotice } from "./tool-permissions-notice.js";
|
|
256
256
|
import { NeedsYouHub } from "./needs-you-hub.js";
|
|
257
|
-
import { WEB_CHAT_NOTICE, claimNotice, hasWebChat, releaseNotice, upgradeNoticesPath } from "./upgrade-notices.js";
|
|
258
257
|
import { GENERAL_NAME, GENERAL_PAUSE_ERROR, adoptGeneralByName, freeGeneralName, isGeneralInstance } from "./general-instance.js";
|
|
259
258
|
import { buildOrgChart } from "./web-org.js";
|
|
260
259
|
import { topMemoryHogs } from "./memory-hogs.js";
|
|
@@ -612,7 +611,6 @@ export function deriveSpawnConcurrency(freeMemMB, cores) {
|
|
|
612
611
|
/** #926: how often reply obligations are checked, the owner-reminder grace, and the default overdue window. */
|
|
613
612
|
const REPLY_OBLIGATION_SWEEP_MS = 30_000;
|
|
614
613
|
const REPLY_REMINDER_GRACE_MS = 60_000;
|
|
615
|
-
const DEFAULT_REPLY_OVERDUE_MINUTES = 15;
|
|
616
614
|
/**
|
|
617
615
|
* The filter `list_emojis` and `list_stickers` take (#1226). Values may come from the agent CLI as strings, so a
|
|
618
616
|
* limit is read as a number and the flags accept "true". No limit means all of them — never zero.
|
|
@@ -676,32 +674,6 @@ export class FleetManager {
|
|
|
676
674
|
laneFree: name => !this.isInstanceRestarting(name) && !this.activeDurableTargets.has(name)
|
|
677
675
|
&& outbox.countForTarget(name, ["queued", "retry_wait", "delivering", "submission_started", "reconciliation_pending"]) === 0,
|
|
678
676
|
admit: (episode, tasks, advisory) => outbox.admitWorkingRecovery(episode, buildWorkingRecoveryPrompt(tasks, this.workingRecoveryCause, advisory), advisory),
|
|
679
|
-
summary: (group, rows) => {
|
|
680
|
-
const general = this.findGeneralInstance();
|
|
681
|
-
if (!general || !this.daemons.has(general))
|
|
682
|
-
return;
|
|
683
|
-
// Our own informational notice must not supersede General's recovery
|
|
684
|
-
// as unrelated pending work before it crosses the input boundary.
|
|
685
|
-
const generalEpisode = store.get(general);
|
|
686
|
-
if (generalEpisode?.outcome === "admitted" && generalEpisode.deliveryId) {
|
|
687
|
-
const state = outbox.get(generalEpisode.deliveryId)?.state;
|
|
688
|
-
if (!state || !["delivered", "uncertain", "failed", "cancelled"].includes(state))
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
const deferredAdvisories = rows.filter(row => row.outcome === "deferred"
|
|
692
|
-
&& (this.daemons.get(row.instance)?.getWorkingRecoveryState?.().background ?? null) === null)
|
|
693
|
-
.map(row => `${row.instance} (${row.reason})`);
|
|
694
|
-
const posted = store.summary(group, () => outbox.admitSystemNotice(general, `working-recovery-summary:${group}`, `[system:working-recovery-summary] Observed cause: unknown interruption${this.workingRecoveryCause === "unknown_interruption" ? "" : `; prior requested action: ${this.workingRecoveryCause}`}. ${rows.length} interrupted instances; continuation queued ${rows.filter(r => r.outcome === "admitted").length}, excluded ${rows.filter(r => r.outcome === "excluded").length}, still deferred ${rows.filter(r => r.outcome === "deferred").length}. ${rows.filter(r => r.reason === "advisory_background_unknown").length ? `Advisory, background unknown: ${rows.filter(r => r.reason === "advisory_background_unknown").map(r => r.instance).join(", ")}. ` : ""}${deferredAdvisories.length ? `Deferred advisory candidates (background unknown; all other gates still apply): ${deferredAdvisories.join(", ")}. ` : ""}Details are in the Log. Deferred work remains eligible; no action or confirmation is requested.`));
|
|
695
|
-
if (posted) {
|
|
696
|
-
const counts = { interrupted: rows.length, queued: rows.filter(row => row.outcome === "admitted").length,
|
|
697
|
-
excluded: rows.filter(row => row.outcome === "excluded").length, deferred: rows.filter(row => row.outcome === "deferred").length };
|
|
698
|
-
const requestedAction = this.workingRecoveryCause === "unknown_interruption" ? null : this.workingRecoveryCause;
|
|
699
|
-
this.logger.info({ group, ...counts, observedCause: "unknown_interruption", requestedAction }, "Interrupted work recovery summary queued for General");
|
|
700
|
-
this.eventLog?.insert(general, "working_state", { reason: "recovery_summary", group, ...counts,
|
|
701
|
-
observedCause: "unknown_interruption", requestedAction,
|
|
702
|
-
message: `Interrupted work summary queued for General: ${counts.interrupted} interrupted; ${counts.queued} queued, ${counts.excluded} excluded, ${counts.deferred} deferred.` });
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
677
|
log: (episode, outcome, reason) => {
|
|
706
678
|
const requestedAction = this.workingRecoveryCause === "unknown_interruption" ? null : this.workingRecoveryCause;
|
|
707
679
|
this.logger.info({ name: episode.instance, episode: episode.key, outcome, reason,
|
|
@@ -2604,27 +2576,8 @@ export class FleetManager {
|
|
|
2604
2576
|
const daemon = this.daemons.get(name);
|
|
2605
2577
|
if (daemon?.bootId === daemonBootId)
|
|
2606
2578
|
this.listenForWorkingState(name, daemon);
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
return;
|
|
2610
|
-
for (const prior of outbox.getUnansweredAccepted(name, daemonBootId)) {
|
|
2611
|
-
try {
|
|
2612
|
-
outbox.admitPostRestartOutcomeNotice(prior, daemonBootId);
|
|
2613
|
-
}
|
|
2614
|
-
catch (err) {
|
|
2615
|
-
this.logger.error({ err, source: name, operationId: prior.operationId }, "Could not persist post-restart delivery outcome notice");
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
// #926: a restart kills the turn that was working on a request, and a
|
|
2619
|
-
// resumed CLI does not pick it back up. Remind the owner once per restart.
|
|
2620
|
-
try {
|
|
2621
|
-
const now = new Date();
|
|
2622
|
-
this.expireReplyObligations(now);
|
|
2623
|
-
outbox.remindReplyObligations(name, { reason: "restart", graceMs: 0, now, since: now, restartId: daemonBootId });
|
|
2624
|
-
}
|
|
2625
|
-
catch (err) {
|
|
2626
|
-
this.logger.error({ err, owner: name }, "Could not persist the post-restart reply reminder");
|
|
2627
|
-
}
|
|
2579
|
+
// #2802: no post-restart notices. The outbox still delivers or fails a row accepted before the restart, and an open
|
|
2580
|
+
// request is reminded by the ordinary turn-ended nudge (sweepReplyObligations) once the owner is idle.
|
|
2628
2581
|
this.scheduleDeliveryOutboxPump();
|
|
2629
2582
|
}
|
|
2630
2583
|
workingStateListeners;
|
|
@@ -2708,9 +2661,8 @@ export class FleetManager {
|
|
|
2708
2661
|
}
|
|
2709
2662
|
}
|
|
2710
2663
|
/**
|
|
2711
|
-
* #926:
|
|
2712
|
-
*
|
|
2713
|
-
* notice when the owner is not working and nothing came back in time.
|
|
2664
|
+
* #926: idle owners with an unanswered request get one reminder (after the grace). #2802: the only reply notice —
|
|
2665
|
+
* requesters are no longer told about overdue or expired asks (they can check delivery_status / describe_instance).
|
|
2714
2666
|
*/
|
|
2715
2667
|
sweepReplyObligations(now = new Date()) {
|
|
2716
2668
|
const outbox = this.deliveryOutbox;
|
|
@@ -2721,10 +2673,8 @@ export class FleetManager {
|
|
|
2721
2673
|
this.expireReplyObligations(now);
|
|
2722
2674
|
for (const name of this.daemons.keys()) {
|
|
2723
2675
|
if (stateOf(name) === "idle")
|
|
2724
|
-
outbox.remindReplyObligations(name, { now, graceMs: REPLY_REMINDER_GRACE_MS
|
|
2676
|
+
outbox.remindReplyObligations(name, { now, graceMs: REPLY_REMINDER_GRACE_MS });
|
|
2725
2677
|
}
|
|
2726
|
-
const minutes = this.fleetConfig?.defaults?.reply_overdue_minutes ?? DEFAULT_REPLY_OVERDUE_MINUTES;
|
|
2727
|
-
outbox.notifyOverdueReplyObligations({ now, overdueMs: minutes * 60_000, ownerIdle: name => stateOf(name) !== "working" });
|
|
2728
2678
|
}
|
|
2729
2679
|
catch (err) {
|
|
2730
2680
|
this.logger.error({ err }, "Reply-obligation sweep failed");
|
|
@@ -8170,7 +8120,7 @@ export class FleetManager {
|
|
|
8170
8120
|
setSdNotifyWarn((fields, message) => this.logger.warn(fields, message));
|
|
8171
8121
|
if (this.watchdogTimer)
|
|
8172
8122
|
clearInterval(this.watchdogTimer);
|
|
8173
|
-
this.watchdogTimer =
|
|
8123
|
+
this.watchdogTimer = startSdWatchdog();
|
|
8174
8124
|
this.eventLoopWatch?.stop();
|
|
8175
8125
|
this.eventLoopWatch = startEventLoopWatch({ logger: this.logger });
|
|
8176
8126
|
try {
|
|
@@ -8734,8 +8684,6 @@ export class FleetManager {
|
|
|
8734
8684
|
}
|
|
8735
8685
|
await this.sendFleetStartCompletionFallback(String(fleet.channel.group_id), text, generalThreadId != null ? String(generalThreadId) : undefined);
|
|
8736
8686
|
}
|
|
8737
|
-
// After "fleet ready", so the notice is not the first thing people see of a restart.
|
|
8738
|
-
void this.announceWebChatOnce(agendVersion);
|
|
8739
8687
|
// #named-link-ttl: a named link with auto_open comes back now — after the web listener and the chat connections.
|
|
8740
8688
|
this.startPublicLinkAutoOpen();
|
|
8741
8689
|
}
|
|
@@ -10870,34 +10818,6 @@ export class FleetManager {
|
|
|
10870
10818
|
* their fleet is organised. Both stay theirs — this only makes sure they are
|
|
10871
10819
|
* not discovered by an agent failing at three in the morning.
|
|
10872
10820
|
*/
|
|
10873
|
-
/**
|
|
10874
|
-
* #1366: the first time a fleet runs 2.2 or later, tell each chat platform's General — once — that its agents can
|
|
10875
|
-
* now be talked to from a browser. Claimed in upgrade-notices.json before the send, released if the send fails.
|
|
10876
|
-
* Platforms with nowhere to post fleet notices (no group, or a Discord fleet with no General channel) are skipped
|
|
10877
|
-
* and not recorded, so they are told once they have one.
|
|
10878
|
-
*/
|
|
10879
|
-
async announceWebChatOnce(version) {
|
|
10880
|
-
if (!hasWebChat(version))
|
|
10881
|
-
return;
|
|
10882
|
-
const path = upgradeNoticesPath(this.dataDir);
|
|
10883
|
-
for (const [adapterId, world] of this.worlds) {
|
|
10884
|
-
if (this.shuttingDown)
|
|
10885
|
-
return;
|
|
10886
|
-
const target = this.fleetNoticeTarget(adapterId);
|
|
10887
|
-
if (!target)
|
|
10888
|
-
continue;
|
|
10889
|
-
if (!claimNotice(path, WEB_CHAT_NOTICE, adapterId))
|
|
10890
|
-
continue;
|
|
10891
|
-
try {
|
|
10892
|
-
await world.adapter.sendText(target.chatId, t("upgrade.web_chat"), target.opts);
|
|
10893
|
-
this.logger.info({ adapterId, notice: WEB_CHAT_NOTICE }, "Announced web chat in General");
|
|
10894
|
-
}
|
|
10895
|
-
catch (err) {
|
|
10896
|
-
const released = releaseNotice(path, WEB_CHAT_NOTICE, adapterId);
|
|
10897
|
-
this.logger.warn({ err, adapterId, released }, "Could not announce web chat in General — will try at the next start");
|
|
10898
|
-
}
|
|
10899
|
-
}
|
|
10900
|
-
}
|
|
10901
10821
|
announceToolPermissionsChange() {
|
|
10902
10822
|
try {
|
|
10903
10823
|
const thirtyDaysAgo = new Date(Date.now() - 30 * 864e5).toISOString().slice(0, 19).replace("T", " ");
|
|
@@ -18321,8 +18241,8 @@ export class FleetManager {
|
|
|
18321
18241
|
* The task board's live side: every committed change goes to the open pages (never to a guest) — the board's new
|
|
18322
18242
|
* revision and the card numbers it touched; not who touched them (the page does not need it, and a public-link page
|
|
18323
18243
|
* must not learn a hidden instance's name from it). Its news goes to the instances whose cards someone else changed:
|
|
18324
|
-
* one "[Task board]" notice per instance, gathered for 10 s, held by the delivery outbox until it is taken
|
|
18325
|
-
* (task-board-waker.ts).
|
|
18244
|
+
* one "[Task board]" notice per instance, gathered for 10 s, held by the delivery outbox until it is taken, or
|
|
18245
|
+
* withdrawn once its cards changed again (#2801; task-board-waker.ts).
|
|
18326
18246
|
*/
|
|
18327
18247
|
attachTaskBoard(db) {
|
|
18328
18248
|
this.taskBoardWaker?.stop();
|
|
@@ -18331,6 +18251,19 @@ export class FleetManager {
|
|
|
18331
18251
|
const waker = this.taskBoardWaker = new TaskBoardWaker({
|
|
18332
18252
|
deliver: (instance, message, key) => this.admitTaskBoardNotice(instance, message, key),
|
|
18333
18253
|
isInstance: (name) => this.fleetConfig?.instances?.[name] != null,
|
|
18254
|
+
// #2785 (c), #2801: the maker's own message already told it; the board's line would repeat it.
|
|
18255
|
+
messagedAbout: (from, to, codes, sinceMs) => {
|
|
18256
|
+
const outbox = this.deliveryOutbox;
|
|
18257
|
+
if (!outbox?.isOpen)
|
|
18258
|
+
return false;
|
|
18259
|
+
return outbox.recentTextsFrom(to, from, new Date(sinceMs).toISOString()).some((text) => codes.some((code) => namesCard(text, code)));
|
|
18260
|
+
},
|
|
18261
|
+
// #2801: a notice still waiting whose cards have all changed again is not delivered.
|
|
18262
|
+
withdraw: (prefix, stale, reason) => {
|
|
18263
|
+
const outbox = this.deliveryOutbox;
|
|
18264
|
+
return outbox?.isOpen ? outbox.withdrawWaitingNotices(prefix, stale, reason).length : 0;
|
|
18265
|
+
},
|
|
18266
|
+
changedAt: (uuid) => db.board.lastChangeAt(uuid),
|
|
18334
18267
|
holds: (wake) => db.board.wakeHolds(wake),
|
|
18335
18268
|
card: (uuid) => db.board.findCard(uuid),
|
|
18336
18269
|
// #2352: the card's Needs you buttons are in this instance's own channel (it is the card's lane).
|
|
@@ -18347,12 +18280,13 @@ export class FleetManager {
|
|
|
18347
18280
|
},
|
|
18348
18281
|
log: (level, message, extra) => this.logger[level](extra ?? {}, message),
|
|
18349
18282
|
});
|
|
18350
|
-
db.board.onChange(({ rev, ids, projects, wakes }) => {
|
|
18283
|
+
db.board.onChange(({ rev, ids, projects, wakes, changed }) => {
|
|
18351
18284
|
// Refresh both old and new owners on reassignment; wakes are not an ownership index.
|
|
18352
18285
|
if (ids.length)
|
|
18353
18286
|
for (const name of this.daemons.keys())
|
|
18354
18287
|
this.observeWorkingState(name, "tasks");
|
|
18355
18288
|
this.emitSseEvent("tasks", { rev, ids, projects });
|
|
18289
|
+
waker.changed(changed);
|
|
18356
18290
|
waker.note(wakes);
|
|
18357
18291
|
// step 3: cards waiting for the person are Needs you items; #2355 review: a project's lead routes them too.
|
|
18358
18292
|
if (ids.length || projects)
|
|
@@ -23506,7 +23440,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23506
23440
|
// From the install's own channel tag, this is what /update installs — even a stable the release pointed the
|
|
23507
23441
|
// tag at (2.2.0 on @alpha / @beta): the plain /update text, never "/update stays on … use --stable" (#1928).
|
|
23508
23442
|
const key = fromChannel ? "update.available_current" : updateNoticeKey(currentVersion, target);
|
|
23509
|
-
|
|
23443
|
+
// #2775: null = a beta install and a stable target; beta users are never told to switch.
|
|
23444
|
+
if (key)
|
|
23445
|
+
this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
|
|
23510
23446
|
}
|
|
23511
23447
|
}
|
|
23512
23448
|
}
|