@songsid/agend 2.1.5-beta.19 → 2.1.5-beta.20
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.d.ts +2 -2
- package/dist/channel/adapters/discord.js +38 -5
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +2 -2
- package/dist/channel/adapters/telegram.js +8 -8
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +22 -1
- package/dist/fleet-context.d.ts +8 -1
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-manager.d.ts +30 -2
- package/dist/fleet-manager.js +182 -33
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +3 -2
- package/dist/instance-lifecycle.js +5 -2
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-removal.d.ts +13 -0
- package/dist/instance-removal.js +14 -0
- package/dist/instance-removal.js.map +1 -0
- package/dist/locale.js +6 -0
- package/dist/locale.js.map +1 -1
- package/dist/topic-commands.d.ts +9 -2
- package/dist/topic-commands.js +6 -4
- package/dist/topic-commands.js.map +1 -1
- package/dist/web-api.d.ts +2 -1
- package/dist/web-api.js +2 -1
- package/dist/web-api.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-context.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { Scheduler } from "./scheduler/index.js";
|
|
|
5
5
|
import type { Logger } from "./logger.js";
|
|
6
6
|
import type { CostGuard } from "./cost-guard.js";
|
|
7
7
|
import type { ClassicChannelManager } from "./classic-channel-manager.js";
|
|
8
|
+
import type { ExplicitInstanceRemoval } from "./instance-removal.js";
|
|
8
9
|
export type RouteTarget = {
|
|
9
10
|
kind: "instance";
|
|
10
11
|
name: string;
|
|
@@ -88,7 +89,13 @@ export interface FleetContext {
|
|
|
88
89
|
saveFleetConfig(): void;
|
|
89
90
|
getInstanceDir(name: string): string;
|
|
90
91
|
createForumTopic(topicName: string, adapterId?: string): Promise<number | string>;
|
|
91
|
-
removeInstance(name: string): Promise<void>;
|
|
92
|
+
removeInstance(name: string, authorization: ExplicitInstanceRemoval): Promise<void>;
|
|
93
|
+
/** Quarantine a provider-confirmed missing topic without deleting user data. */
|
|
94
|
+
quarantineMissingTopic(threadId: string, target: RouteTarget, evidence: {
|
|
95
|
+
source: "provider-event" | "provider-probe";
|
|
96
|
+
adapterId?: string;
|
|
97
|
+
generation?: number;
|
|
98
|
+
}): void;
|
|
92
99
|
getAdapterStates?(): Map<string, {
|
|
93
100
|
status: string;
|
|
94
101
|
retryCount: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../src/fleet-context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fleet-context.js","sourceRoot":"","sources":["../src/fleet-context.ts"],"names":[],"mappings":"AAiCA,MAAM,UAAU,sBAAsB,CAAC,MAAmB;IACxD,OAAO,MAAM,CAAC,IAAI,KAAK,UAAU,CAAC;AACpC,CAAC"}
|
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { type OutboundContext } from "./outbound-handlers.js";
|
|
|
19
19
|
import { type RawConfigPatch } from "./settings-api.js";
|
|
20
20
|
import { type AgentEndpointContext } from "./agent-endpoint.js";
|
|
21
21
|
import { ClassicChannelManager } from "./classic-channel-manager.js";
|
|
22
|
+
import { type ExplicitInstanceRemoval } from "./instance-removal.js";
|
|
22
23
|
import type { InstanceState } from "./backend/types.js";
|
|
23
24
|
import { StormWindow } from "./storm-window.js";
|
|
24
25
|
import { SpawnGate } from "./spawn-gate.js";
|
|
@@ -134,6 +135,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
134
135
|
private reloadPending;
|
|
135
136
|
/** A running reconciliation; only one may mutate lifecycle/config state at a time. */
|
|
136
137
|
private reconcileInFlight;
|
|
138
|
+
/** Topology checks are serialized separately from config reconciliation. */
|
|
139
|
+
private topicCleanupInFlight;
|
|
140
|
+
private topicCleanupGeneration;
|
|
141
|
+
private topicProbeWarnings;
|
|
137
142
|
logger: Logger;
|
|
138
143
|
private topicCommands;
|
|
139
144
|
sessionRegistry: Map<string, string>;
|
|
@@ -759,8 +764,31 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
759
764
|
private sessionPruneTimer;
|
|
760
765
|
private classicReloadTimer;
|
|
761
766
|
private botUserId;
|
|
762
|
-
/** Periodically check if bound topics still exist */
|
|
767
|
+
/** Periodically check if bound topics still exist. Never deletes user data. */
|
|
763
768
|
private startTopicCleanupPoller;
|
|
769
|
+
/** Coalesce timer ticks; an outage must not create overlapping destructive-looking scans. */
|
|
770
|
+
private scheduleTopicCleanup;
|
|
771
|
+
private confirmedProbeFence;
|
|
772
|
+
private sameProbeFence;
|
|
773
|
+
private warnTopicProbeUnknown;
|
|
774
|
+
/** One fixed-snapshot topology pass. Automatic evidence can only quarantine. */
|
|
775
|
+
private runTopicCleanup;
|
|
776
|
+
/**
|
|
777
|
+
* A gateway event is only a hint. Confirm it through the passive REST probe;
|
|
778
|
+
* reconnecting gateways have emitted false channelDelete events in practice.
|
|
779
|
+
*/
|
|
780
|
+
private handleProviderTopicClosed;
|
|
781
|
+
private bindTopicClosedHandler;
|
|
782
|
+
/**
|
|
783
|
+
* Remove only the volatile route. The instance config, daemon, schedules,
|
|
784
|
+
* teams, metadata directory, and working tree remain untouched until an
|
|
785
|
+
* authenticated explicit deletion is requested.
|
|
786
|
+
*/
|
|
787
|
+
quarantineMissingTopic(threadId: string, target: RouteTarget, evidence: {
|
|
788
|
+
source: "provider-event" | "provider-probe";
|
|
789
|
+
adapterId?: string;
|
|
790
|
+
generation?: number;
|
|
791
|
+
}): void;
|
|
764
792
|
/**
|
|
765
793
|
* Patch only values changed in the effective config into the original YAML
|
|
766
794
|
* document. Unknown keys and comments remain untouched; redundant
|
|
@@ -771,7 +799,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
771
799
|
private slimFleetConfigAtStartup;
|
|
772
800
|
private writeFleetConfigBackup;
|
|
773
801
|
private patchFleetDocument;
|
|
774
|
-
removeInstance(name: string): Promise<void>;
|
|
802
|
+
removeInstance(name: string, authorization: ExplicitInstanceRemoval): Promise<void>;
|
|
775
803
|
startStatuslineWatcher(name: string): void;
|
|
776
804
|
stopStatuslineWatcher(name: string): void;
|
|
777
805
|
reactMessageStatus(instanceName: string, chatId: string, messageId: string, emoji: string): void;
|
package/dist/fleet-manager.js
CHANGED
|
@@ -55,6 +55,7 @@ import { handleSettingsRequest } from "./settings-api.js";
|
|
|
55
55
|
import { setLocale, detectLocale, getLocale, t } from "./locale.js";
|
|
56
56
|
import { handleAgentRequest } from "./agent-endpoint.js";
|
|
57
57
|
import { ClassicChannelManager, getClassicBackendChoices, isSelectableClassicBackend, readClassicLastActivityAt } from "./classic-channel-manager.js";
|
|
58
|
+
import { assertExplicitInstanceRemoval } from "./instance-removal.js";
|
|
58
59
|
import { validateFleetConfig } from "./config-validator.js";
|
|
59
60
|
import { readLastInboundAt } from "./daemon.js";
|
|
60
61
|
import { clearPausedMarker } from "./pause-marker.js";
|
|
@@ -316,6 +317,10 @@ export class FleetManager {
|
|
|
316
317
|
reloadPending = false;
|
|
317
318
|
/** A running reconciliation; only one may mutate lifecycle/config state at a time. */
|
|
318
319
|
reconcileInFlight = null;
|
|
320
|
+
/** Topology checks are serialized separately from config reconciliation. */
|
|
321
|
+
topicCleanupInFlight = null;
|
|
322
|
+
topicCleanupGeneration = 0;
|
|
323
|
+
topicProbeWarnings = new Map();
|
|
319
324
|
logger = createLogger("info");
|
|
320
325
|
topicCommands;
|
|
321
326
|
// sessionName → instanceName mapping for external sessions
|
|
@@ -3018,10 +3023,11 @@ export class FleetManager {
|
|
|
3018
3023
|
accessManager,
|
|
3019
3024
|
inboxDir,
|
|
3020
3025
|
});
|
|
3021
|
-
const
|
|
3026
|
+
const adapter = this.adapter;
|
|
3027
|
+
const world = new AdapterWorld(adapterId, adapter, accessManager, channelConfig);
|
|
3022
3028
|
this.worlds.set(adapterId, world);
|
|
3023
|
-
this.adapters.set(adapterId,
|
|
3024
|
-
this.bindAdapterHealth(
|
|
3029
|
+
this.adapters.set(adapterId, adapter);
|
|
3030
|
+
this.bindAdapterHealth(adapter, adapterId);
|
|
3025
3031
|
this.adapter.on("message", safeHandler(async (msg) => {
|
|
3026
3032
|
await this.handleInboundMessage(msg);
|
|
3027
3033
|
}, this.logger, "adapter.message"));
|
|
@@ -3066,12 +3072,7 @@ export class FleetManager {
|
|
|
3066
3072
|
return;
|
|
3067
3073
|
}
|
|
3068
3074
|
}, this.logger, "adapter.callback_query"));
|
|
3069
|
-
this.adapter.
|
|
3070
|
-
// Skip unbind if we archived this topic ourselves
|
|
3071
|
-
if (this.topicArchiver.isArchived(data.threadId))
|
|
3072
|
-
return;
|
|
3073
|
-
await this.topicCommands.handleTopicDeleted(data.threadId);
|
|
3074
|
-
}, this.logger, "adapter.topic_closed"));
|
|
3075
|
+
this.bindTopicClosedHandler(adapter, adapterId, "adapter.topic_closed");
|
|
3075
3076
|
// Handle classic bot slash commands (/start, /stop, /chat, /compact, /save, /load)
|
|
3076
3077
|
this.adapter.on("slash_command", safeHandler(async (data) => {
|
|
3077
3078
|
if (data.command === "start") {
|
|
@@ -3462,11 +3463,7 @@ export class FleetManager {
|
|
|
3462
3463
|
return;
|
|
3463
3464
|
}
|
|
3464
3465
|
}, this.logger, `adapter[${adapterId}].callback_query`));
|
|
3465
|
-
|
|
3466
|
-
if (this.topicArchiver.isArchived(data.threadId))
|
|
3467
|
-
return;
|
|
3468
|
-
await this.topicCommands.handleTopicDeleted(data.threadId);
|
|
3469
|
-
}, this.logger, `adapter[${adapterId}].topic_closed`));
|
|
3466
|
+
this.bindTopicClosedHandler(adapter, adapterId, `adapter[${adapterId}].topic_closed`);
|
|
3470
3467
|
// Slash commands: classic bot + admin commands
|
|
3471
3468
|
adapter.on("slash_command", safeHandler(async (data) => {
|
|
3472
3469
|
if (data.command === "start") {
|
|
@@ -5456,26 +5453,176 @@ export class FleetManager {
|
|
|
5456
5453
|
sessionPruneTimer = null;
|
|
5457
5454
|
classicReloadTimer = null;
|
|
5458
5455
|
botUserId;
|
|
5459
|
-
/** Periodically check if bound topics still exist */
|
|
5456
|
+
/** Periodically check if bound topics still exist. Never deletes user data. */
|
|
5460
5457
|
startTopicCleanupPoller() {
|
|
5461
|
-
this.topicCleanupTimer
|
|
5462
|
-
|
|
5458
|
+
if (this.topicCleanupTimer)
|
|
5459
|
+
clearInterval(this.topicCleanupTimer);
|
|
5460
|
+
const generation = ++this.topicCleanupGeneration;
|
|
5461
|
+
this.topicCleanupTimer = setInterval(() => { void this.scheduleTopicCleanup(generation); }, 5 * 60_000);
|
|
5462
|
+
}
|
|
5463
|
+
/** Coalesce timer ticks; an outage must not create overlapping destructive-looking scans. */
|
|
5464
|
+
scheduleTopicCleanup(generation = this.topicCleanupGeneration) {
|
|
5465
|
+
if (this.topicCleanupInFlight)
|
|
5466
|
+
return this.topicCleanupInFlight;
|
|
5467
|
+
const run = this.runTopicCleanup(generation).finally(() => {
|
|
5468
|
+
if (this.topicCleanupInFlight === run)
|
|
5469
|
+
this.topicCleanupInFlight = null;
|
|
5470
|
+
});
|
|
5471
|
+
this.topicCleanupInFlight = run;
|
|
5472
|
+
return run;
|
|
5473
|
+
}
|
|
5474
|
+
confirmedProbeFence(adapterId, adapter) {
|
|
5475
|
+
const health = adapter.getHealthSnapshot?.();
|
|
5476
|
+
if (health) {
|
|
5477
|
+
return health.status === "connected" && health.isReady
|
|
5478
|
+
? { generation: health.generation }
|
|
5479
|
+
: null;
|
|
5480
|
+
}
|
|
5481
|
+
return this.adapterState.get(adapterId)?.status === "connected" ? {} : null;
|
|
5482
|
+
}
|
|
5483
|
+
sameProbeFence(adapterId, adapter, before, result) {
|
|
5484
|
+
const after = this.confirmedProbeFence(adapterId, adapter);
|
|
5485
|
+
if (!after)
|
|
5486
|
+
return false;
|
|
5487
|
+
if (before.generation !== after.generation)
|
|
5488
|
+
return false;
|
|
5489
|
+
return result?.generation === undefined || result.generation === after.generation;
|
|
5490
|
+
}
|
|
5491
|
+
warnTopicProbeUnknown(instanceName, threadId, adapterId, reason) {
|
|
5492
|
+
const key = `${adapterId ?? "unbound"}:${reason}`;
|
|
5493
|
+
const now = Date.now();
|
|
5494
|
+
const last = this.topicProbeWarnings.get(key) ?? 0;
|
|
5495
|
+
if (now - last < FleetManager.FLEET_ERROR_THROTTLE_MS)
|
|
5496
|
+
return;
|
|
5497
|
+
this.topicProbeWarnings.set(key, now);
|
|
5498
|
+
this.logger.error({ instanceName, threadId, adapterId, reason }, "Topic presence could not be confirmed — retaining instance and all data");
|
|
5499
|
+
this.notifyFleetError(t("fleet.topic_probe_unknown", instanceName, adapterId ?? "unbound"));
|
|
5500
|
+
}
|
|
5501
|
+
/** One fixed-snapshot topology pass. Automatic evidence can only quarantine. */
|
|
5502
|
+
async runTopicCleanup(generation) {
|
|
5503
|
+
if (generation !== this.topicCleanupGeneration || this.shuttingDown)
|
|
5504
|
+
return;
|
|
5505
|
+
const snapshot = [...this.routing.entries()].filter(([, target]) => isProbeableRouteTarget(target));
|
|
5506
|
+
const missing = [];
|
|
5507
|
+
for (const [threadId, target] of snapshot) {
|
|
5508
|
+
if (generation !== this.topicCleanupGeneration || this.shuttingDown)
|
|
5463
5509
|
return;
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5510
|
+
// The route may have been replaced while an earlier probe was in flight.
|
|
5511
|
+
if (this.routing.resolve(threadId) !== target)
|
|
5512
|
+
continue;
|
|
5513
|
+
const adapterId = this.getInstanceAdapterId(target.name);
|
|
5514
|
+
const adapter = adapterId ? this.adapters.get(adapterId) : undefined;
|
|
5515
|
+
if (!adapterId || !adapter?.probeTopicPresence) {
|
|
5516
|
+
this.warnTopicProbeUnknown(target.name, threadId, adapterId, "owner-adapter-unavailable");
|
|
5517
|
+
continue;
|
|
5518
|
+
}
|
|
5519
|
+
const before = this.confirmedProbeFence(adapterId, adapter);
|
|
5520
|
+
if (!before) {
|
|
5521
|
+
this.warnTopicProbeUnknown(target.name, threadId, adapterId, "owner-adapter-not-ready");
|
|
5522
|
+
continue;
|
|
5477
5523
|
}
|
|
5478
|
-
|
|
5524
|
+
let result;
|
|
5525
|
+
try {
|
|
5526
|
+
result = await adapter.probeTopicPresence(threadId);
|
|
5527
|
+
}
|
|
5528
|
+
catch {
|
|
5529
|
+
result = { status: "unknown", reason: "provider-probe-threw" };
|
|
5530
|
+
}
|
|
5531
|
+
if (generation !== this.topicCleanupGeneration || this.shuttingDown)
|
|
5532
|
+
return;
|
|
5533
|
+
if (!this.sameProbeFence(adapterId, adapter, before, result)) {
|
|
5534
|
+
this.warnTopicProbeUnknown(target.name, threadId, adapterId, "owner-adapter-generation-changed");
|
|
5535
|
+
continue;
|
|
5536
|
+
}
|
|
5537
|
+
if (result.status === "unknown") {
|
|
5538
|
+
this.warnTopicProbeUnknown(target.name, threadId, adapterId, result.reason);
|
|
5539
|
+
}
|
|
5540
|
+
else if (result.status === "missing") {
|
|
5541
|
+
missing.push({ threadId, target, adapterId, adapter, generation: result.generation });
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
if (generation !== this.topicCleanupGeneration || this.shuttingDown || missing.length === 0)
|
|
5545
|
+
return;
|
|
5546
|
+
if (missing.length > 1) {
|
|
5547
|
+
this.logger.error({ missing: missing.map(item => ({ instanceName: item.target.name, threadId: item.threadId, adapterId: item.adapterId })) }, "Multiple topics appeared missing in one pass — treating topology evidence as untrusted and retaining all data");
|
|
5548
|
+
this.notifyFleetError(t("fleet.topic_probe_bulk", missing.length));
|
|
5549
|
+
return;
|
|
5550
|
+
}
|
|
5551
|
+
const item = missing[0];
|
|
5552
|
+
if (this.routing.resolve(item.threadId) !== item.target
|
|
5553
|
+
|| this.getInstanceAdapterId(item.target.name) !== item.adapterId
|
|
5554
|
+
|| this.adapters.get(item.adapterId) !== item.adapter)
|
|
5555
|
+
return;
|
|
5556
|
+
const current = this.confirmedProbeFence(item.adapterId, item.adapter);
|
|
5557
|
+
if (!current || (item.generation !== undefined && current.generation !== item.generation)) {
|
|
5558
|
+
this.warnTopicProbeUnknown(item.target.name, item.threadId, item.adapterId, "owner-adapter-changed-before-action");
|
|
5559
|
+
return;
|
|
5560
|
+
}
|
|
5561
|
+
this.topicCommands.handleTopicDeleted(item.threadId, {
|
|
5562
|
+
source: "provider-probe",
|
|
5563
|
+
adapterId: item.adapterId,
|
|
5564
|
+
generation: item.generation,
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
/**
|
|
5568
|
+
* A gateway event is only a hint. Confirm it through the passive REST probe;
|
|
5569
|
+
* reconnecting gateways have emitted false channelDelete events in practice.
|
|
5570
|
+
*/
|
|
5571
|
+
async handleProviderTopicClosed(threadId, adapterId, adapter) {
|
|
5572
|
+
const target = this.routing.resolve(threadId);
|
|
5573
|
+
if (!target || !isProbeableRouteTarget(target))
|
|
5574
|
+
return;
|
|
5575
|
+
if (this.getInstanceAdapterId(target.name) !== adapterId)
|
|
5576
|
+
return;
|
|
5577
|
+
if (this.adapters.get(adapterId) !== adapter)
|
|
5578
|
+
return;
|
|
5579
|
+
const before = this.confirmedProbeFence(adapterId, adapter);
|
|
5580
|
+
if (!before || !adapter.probeTopicPresence) {
|
|
5581
|
+
this.warnTopicProbeUnknown(target.name, threadId, adapterId, "topic-close-from-unready-adapter");
|
|
5582
|
+
return;
|
|
5583
|
+
}
|
|
5584
|
+
let result;
|
|
5585
|
+
try {
|
|
5586
|
+
result = await adapter.probeTopicPresence(threadId);
|
|
5587
|
+
}
|
|
5588
|
+
catch {
|
|
5589
|
+
result = { status: "unknown", reason: "provider-probe-threw" };
|
|
5590
|
+
}
|
|
5591
|
+
if (this.routing.resolve(threadId) !== target
|
|
5592
|
+
|| this.getInstanceAdapterId(target.name) !== adapterId
|
|
5593
|
+
|| this.adapters.get(adapterId) !== adapter
|
|
5594
|
+
|| !this.sameProbeFence(adapterId, adapter, before, result)) {
|
|
5595
|
+
this.warnTopicProbeUnknown(target.name, threadId, adapterId, "topic-close-generation-changed");
|
|
5596
|
+
return;
|
|
5597
|
+
}
|
|
5598
|
+
if (result.status !== "missing") {
|
|
5599
|
+
this.warnTopicProbeUnknown(target.name, threadId, adapterId, result.status === "unknown" ? result.reason : "topic-close-not-confirmed-missing");
|
|
5600
|
+
return;
|
|
5601
|
+
}
|
|
5602
|
+
this.topicCommands.handleTopicDeleted(threadId, {
|
|
5603
|
+
source: "provider-event",
|
|
5604
|
+
adapterId,
|
|
5605
|
+
generation: result.generation,
|
|
5606
|
+
});
|
|
5607
|
+
}
|
|
5608
|
+
bindTopicClosedHandler(adapter, adapterId, label) {
|
|
5609
|
+
adapter.on("topic_closed", safeHandler(async (data) => {
|
|
5610
|
+
if (this.topicArchiver.isArchived(data.threadId))
|
|
5611
|
+
return;
|
|
5612
|
+
await this.handleProviderTopicClosed(data.threadId, adapterId, adapter);
|
|
5613
|
+
}, this.logger, label));
|
|
5614
|
+
}
|
|
5615
|
+
/**
|
|
5616
|
+
* Remove only the volatile route. The instance config, daemon, schedules,
|
|
5617
|
+
* teams, metadata directory, and working tree remain untouched until an
|
|
5618
|
+
* authenticated explicit deletion is requested.
|
|
5619
|
+
*/
|
|
5620
|
+
quarantineMissingTopic(threadId, target, evidence) {
|
|
5621
|
+
if (!isProbeableRouteTarget(target) || this.routing.resolve(threadId) !== target)
|
|
5622
|
+
return;
|
|
5623
|
+
this.routing.unregister(threadId);
|
|
5624
|
+
this.logger.error({ instanceName: target.name, threadId, ...evidence }, "Topic is confirmed missing — route quarantined; instance configuration and user data were retained");
|
|
5625
|
+
this.notifyFleetError(t("fleet.topic_quarantined", target.name, threadId));
|
|
5479
5626
|
}
|
|
5480
5627
|
/**
|
|
5481
5628
|
* Patch only values changed in the effective config into the original YAML
|
|
@@ -5628,7 +5775,8 @@ export class FleetManager {
|
|
|
5628
5775
|
}
|
|
5629
5776
|
}
|
|
5630
5777
|
}
|
|
5631
|
-
async removeInstance(name) {
|
|
5778
|
+
async removeInstance(name, authorization) {
|
|
5779
|
+
assertExplicitInstanceRemoval(authorization);
|
|
5632
5780
|
// Drop cached pane context — the map is keyed by instance name and nothing
|
|
5633
5781
|
// else evicted deleted entries, so it grew for the life of the process.
|
|
5634
5782
|
forgetInstanceContext(name);
|
|
@@ -5654,7 +5802,7 @@ export class FleetManager {
|
|
|
5654
5802
|
}
|
|
5655
5803
|
}
|
|
5656
5804
|
}
|
|
5657
|
-
await this.lifecycle.remove(name);
|
|
5805
|
+
await this.lifecycle.remove(name, authorization);
|
|
5658
5806
|
// Clean up per-instance tracking maps so they don't grow unbounded
|
|
5659
5807
|
// as instances are created and deleted over the lifetime of the fleet.
|
|
5660
5808
|
this.lastActivity.delete(name);
|
|
@@ -9995,6 +10143,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
9995
10143
|
clearInterval(this.topicCleanupTimer);
|
|
9996
10144
|
this.topicCleanupTimer = null;
|
|
9997
10145
|
}
|
|
10146
|
+
this.topicCleanupGeneration++;
|
|
9998
10147
|
if (this.sessionPruneTimer) {
|
|
9999
10148
|
clearInterval(this.sessionPruneTimer);
|
|
10000
10149
|
this.sessionPruneTimer = null;
|