@songsid/agend 2.1.6-beta.9 → 2.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/backend/codex.d.ts +35 -0
  2. package/dist/backend/codex.js +588 -36
  3. package/dist/backend/codex.js.map +1 -1
  4. package/dist/backend/kiro.d.ts +9 -0
  5. package/dist/backend/kiro.js +129 -0
  6. package/dist/backend/kiro.js.map +1 -1
  7. package/dist/backend/muse.d.ts +37 -4
  8. package/dist/backend/muse.js +150 -61
  9. package/dist/backend/muse.js.map +1 -1
  10. package/dist/backend/types.d.ts +15 -1
  11. package/dist/backend/types.js.map +1 -1
  12. package/dist/daemon.d.ts +98 -10
  13. package/dist/daemon.js +836 -94
  14. package/dist/daemon.js.map +1 -1
  15. package/dist/fleet-manager.d.ts +30 -1
  16. package/dist/fleet-manager.js +152 -47
  17. package/dist/fleet-manager.js.map +1 -1
  18. package/dist/instance-lifecycle.d.ts +17 -0
  19. package/dist/instance-lifecycle.js +118 -5
  20. package/dist/instance-lifecycle.js.map +1 -1
  21. package/dist/locale.js +16 -0
  22. package/dist/locale.js.map +1 -1
  23. package/dist/muse-usage-relay.d.ts +72 -0
  24. package/dist/muse-usage-relay.js +431 -0
  25. package/dist/muse-usage-relay.js.map +1 -0
  26. package/dist/tmux-manager.d.ts +2 -0
  27. package/dist/tmux-manager.js +20 -0
  28. package/dist/tmux-manager.js.map +1 -1
  29. package/dist/tool-permissions.d.ts +27 -0
  30. package/dist/tool-permissions.js +64 -3
  31. package/dist/tool-permissions.js.map +1 -1
  32. package/dist/topic-commands.d.ts +9 -0
  33. package/dist/topic-commands.js +36 -2
  34. package/dist/topic-commands.js.map +1 -1
  35. package/dist/ui/view.html +2 -2
  36. package/dist/usage/i18n-keys.d.ts +1 -1
  37. package/dist/usage/i18n-keys.js +1 -1
  38. package/dist/usage/i18n-keys.js.map +1 -1
  39. package/dist/usage/providers.d.ts +4 -0
  40. package/dist/usage/providers.js +86 -9
  41. package/dist/usage/providers.js.map +1 -1
  42. package/dist/usage/usage-api.d.ts +0 -11
  43. package/dist/usage/usage-api.js +50 -8
  44. package/dist/usage/usage-api.js.map +1 -1
  45. package/package.json +2 -1
@@ -292,6 +292,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
292
292
  private eventLogPruneTimer;
293
293
  private logRotateTimer;
294
294
  private discordPresenceTimer;
295
+ private discordPresenceEagerTimer;
296
+ private discordPresenceEagerPending;
295
297
  private discordPresenceInFlight;
296
298
  private static readonly DISCORD_PRESENCE_REFRESH_MS;
297
299
  /** Days of event/activity history to keep. */
@@ -681,6 +683,12 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
681
683
  startAll(configPath: string): Promise<void>;
682
684
  /** Keep Discord profile activity aligned with the same cached usage source as /usage. */
683
685
  private startDiscordUsagePresence;
686
+ /**
687
+ * Request one prompt presence refresh after an instance/adapter comes online.
688
+ * Startup can bring several instances up together; a short coalescing window
689
+ * keeps that herd on the existing shared refresh path and one usage fetch.
690
+ */
691
+ private requestDiscordUsagePresenceRefresh;
684
692
  private refreshDiscordUsagePresence;
685
693
  /**
686
694
  * Delete inbox files older than retentionDays (by mtime). Cleans the shared
@@ -918,6 +926,19 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
918
926
  private handleSetDisplayName;
919
927
  private handleSetDescription;
920
928
  handleScheduleCrudHttp(instance: string, op: string, args: Record<string, unknown>): Promise<unknown>;
929
+ /**
930
+ * The only place an agent's request creates, changes or removes a schedule
931
+ * (#895). The IPC handler (MCP calls, direct channel.sock writes) and the
932
+ * agent endpoint (agent-cli, HTTP agent mode) are adapters over this, so the
933
+ * target check cannot be present on one face and missing on the other — the
934
+ * shape #804 had to close twice.
935
+ *
936
+ * `caller` is the instance the server resolved for the request; any
937
+ * `source` in `args` is ignored, and a schedule's source is always its caller.
938
+ * A refusal is a ToolNotPermittedError: 403 on the agent endpoint, the error
939
+ * of the schedule response on IPC.
940
+ */
941
+ private performScheduleOp;
921
942
  handleDecisionCrudHttp(instance: string, op: string, args: Record<string, unknown>): Promise<unknown>;
922
943
  handleTaskCrudHttp(instance: string, args: Record<string, unknown>): Promise<unknown>;
923
944
  handleSetDisplayNameHttp(instance: string, name: string): Promise<unknown>;
@@ -1655,7 +1676,15 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1655
1676
  private customProviderFor;
1656
1677
  /** Ask a backend for its catalog using ONE instance's real config. Never throws. */
1657
1678
  private instanceScopedModels;
1658
- /** Account-wide catalog: probe cache first, live probe on miss. */
1679
+ /**
1680
+ * Account-wide catalog: probe cache first, live probe when the cache is
1681
+ * missing or older than the same ~1h window `/model` uses (#888).
1682
+ *
1683
+ * Previously this served anything inside the 24h hard TTL, so `list_models`
1684
+ * could answer with a day-old list `/model` had already re-probed past.
1685
+ * The probe runs under the same deadline `/model` uses: a vendor that stops
1686
+ * answering degrades to the previous list, never stalls the tool call.
1687
+ */
1659
1688
  private globalModelCatalog;
1660
1689
  /** `/model` slash handler (admin only). No arg → DC menu; `/model <name>` → apply directly. */
1661
1690
  /** Label an effort choice, marking the one currently configured. */
@@ -52,7 +52,7 @@ import { runBeforeDeadline } from "./deadline.js";
52
52
  import { LoginWindowLock } from "./login-window-lock.js";
53
53
  import { handleSettingsRequest } from "./settings-api.js";
54
54
  import { setLocale, detectLocale, getLocale, t } from "./locale.js";
55
- import { handleAgentRequest } from "./agent-endpoint.js";
55
+ import { handleAgentRequest, ToolNotPermittedError } from "./agent-endpoint.js";
56
56
  import { ClassicChannelManager, getClassicBackendChoices, isSelectableClassicBackend, readClassicLastActivityAt } from "./classic-channel-manager.js";
57
57
  import { assertExplicitInstanceRemoval } from "./instance-removal.js";
58
58
  import { validateFleetConfig } from "./config-validator.js";
@@ -63,7 +63,7 @@ import { isSetupComplete, markSetupComplete } from "./setup-marker.js";
63
63
  import { manualCleanupMessage, reapStaleTunnel } from "./tunnel/lease.js";
64
64
  import { buildToolPermissionsNotice } from "./tool-permissions-notice.js";
65
65
  import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
66
- import { mayUseTool, resolveToolSet, toolForIpcType, toolRefusedMessage, } from "./tool-permissions.js";
66
+ import { mayUseTool, resolveToolSet, scheduleOpRefusal, toolForIpcType, toolRefusedMessage, } from "./tool-permissions.js";
67
67
  import { decideWebGate, loadOrCreateWebToken, readWebToken } from "./web-auth.js";
68
68
  import { fleetLevelDifferences, fleetLevelSignature } from "./fleet-level-config.js";
69
69
  import { checkSelfRestartAllowance, recordSelfRestartAttempt } from "./self-restart-limit.js";
@@ -488,6 +488,8 @@ export class FleetManager {
488
488
  eventLogPruneTimer = null;
489
489
  logRotateTimer = null;
490
490
  discordPresenceTimer = null;
491
+ discordPresenceEagerTimer = null;
492
+ discordPresenceEagerPending = false;
491
493
  discordPresenceInFlight = null;
492
494
  static DISCORD_PRESENCE_REFRESH_MS = 15 * 60_000;
493
495
  /** Days of event/activity history to keep. */
@@ -1839,6 +1841,7 @@ export class FleetManager {
1839
1841
  catch { /* consumed or absent */ }
1840
1842
  // Auto-connect IPC — daemon.start() ensures socket is ready before resolving
1841
1843
  await this.connectIpcToInstance(name);
1844
+ this.requestDiscordUsagePresenceRefresh();
1842
1845
  }
1843
1846
  /** Recreate a daemon for a marker-only paused instance after an explicit wake/delivery. */
1844
1847
  async startPersistedPausedInstance(name) {
@@ -3058,12 +3061,37 @@ export class FleetManager {
3058
3061
  startDiscordUsagePresence() {
3059
3062
  if (this.discordPresenceTimer)
3060
3063
  clearInterval(this.discordPresenceTimer);
3064
+ if (this.discordPresenceEagerTimer) {
3065
+ clearTimeout(this.discordPresenceEagerTimer);
3066
+ this.discordPresenceEagerTimer = null;
3067
+ }
3068
+ this.discordPresenceEagerPending = false;
3061
3069
  void this.refreshDiscordUsagePresence();
3062
3070
  this.discordPresenceTimer = setInterval(() => {
3063
3071
  void this.refreshDiscordUsagePresence();
3064
3072
  }, FleetManager.DISCORD_PRESENCE_REFRESH_MS);
3065
3073
  this.discordPresenceTimer.unref?.();
3066
3074
  }
3075
+ /**
3076
+ * Request one prompt presence refresh after an instance/adapter comes online.
3077
+ * Startup can bring several instances up together; a short coalescing window
3078
+ * keeps that herd on the existing shared refresh path and one usage fetch.
3079
+ */
3080
+ requestDiscordUsagePresenceRefresh() {
3081
+ this.discordPresenceEagerPending = true;
3082
+ // During early fleet startup the interval is not installed yet; its first
3083
+ // refresh in startDiscordUsagePresence already covers all pending starts.
3084
+ if (!this.discordPresenceTimer || this.discordPresenceEagerTimer)
3085
+ return;
3086
+ this.discordPresenceEagerTimer = setTimeout(() => {
3087
+ this.discordPresenceEagerTimer = null;
3088
+ if (!this.discordPresenceEagerPending || !this.discordPresenceTimer)
3089
+ return;
3090
+ this.discordPresenceEagerPending = false;
3091
+ void this.refreshDiscordUsagePresence();
3092
+ }, 50);
3093
+ this.discordPresenceEagerTimer.unref?.();
3094
+ }
3067
3095
  refreshDiscordUsagePresence() {
3068
3096
  if (this.discordPresenceInFlight)
3069
3097
  return this.discordPresenceInFlight;
@@ -3706,7 +3734,7 @@ export class FleetManager {
3706
3734
  this.adapter.setChatId(String(fleet.channel.group_id));
3707
3735
  }
3708
3736
  if (this.discordPresenceTimer && this.adapter.type === "discord") {
3709
- void this.refreshDiscordUsagePresence();
3737
+ this.requestDiscordUsagePresenceRefresh();
3710
3738
  }
3711
3739
  this.startTopicCleanupPoller();
3712
3740
  // Prune stale external sessions every 5 minutes
@@ -4051,7 +4079,7 @@ export class FleetManager {
4051
4079
  adapter.setChatId(String(channelConfig.group_id));
4052
4080
  }
4053
4081
  if (this.discordPresenceTimer && adapter.type === "discord") {
4054
- void this.refreshDiscordUsagePresence();
4082
+ this.requestDiscordUsagePresenceRefresh();
4055
4083
  }
4056
4084
  this.logger.info({ adapterId, type: channelConfig.type }, "Additional adapter started");
4057
4085
  }
@@ -4296,7 +4324,7 @@ export class FleetManager {
4296
4324
  await adapter.reconnectGateway(previous?.lastError ?? "fleet adapter restart");
4297
4325
  this.adapterState.set(id, { status: "connected", retryCount: 0 });
4298
4326
  if (this.discordPresenceTimer && adapter.type === "discord") {
4299
- void this.refreshDiscordUsagePresence();
4327
+ this.requestDiscordUsagePresenceRefresh();
4300
4328
  }
4301
4329
  this.logger.info({ id }, "Adapter gateway rebuilt successfully");
4302
4330
  }
@@ -4324,7 +4352,7 @@ export class FleetManager {
4324
4352
  this.logger.info({ id, attempt }, "Adapter restarted successfully");
4325
4353
  this.adapterState.set(id, { status: "connected", retryCount: 0 });
4326
4354
  if (this.discordPresenceTimer && adapter.type === "discord") {
4327
- void this.refreshDiscordUsagePresence();
4355
+ this.requestDiscordUsagePresenceRefresh();
4328
4356
  }
4329
4357
  return;
4330
4358
  }
@@ -5550,36 +5578,18 @@ export class FleetManager {
5550
5578
  return;
5551
5579
  }
5552
5580
  try {
5553
- let result;
5554
- switch (msg.type) {
5555
- case "fleet_schedule_create": {
5556
- const params = {
5557
- cron: payload.cron,
5558
- at: payload.at,
5559
- message: payload.message,
5560
- source: instanceName,
5561
- target: payload.target || instanceName,
5562
- reply_chat_id: meta.chat_id,
5563
- reply_thread_id: meta.thread_id || null,
5564
- reply_adapter_id: meta.adapter_id || null,
5565
- label: payload.label,
5566
- timezone: payload.timezone,
5567
- silent: !!(payload.silent),
5568
- };
5569
- result = this.scheduler.create(params);
5570
- break;
5571
- }
5572
- case "fleet_schedule_list":
5573
- result = this.scheduler.list(payload.target);
5574
- break;
5575
- case "fleet_schedule_update":
5576
- result = this.scheduler.update(payload.id, payload);
5577
- break;
5578
- case "fleet_schedule_delete":
5579
- this.scheduler.delete(payload.id);
5580
- result = "ok";
5581
- break;
5582
- }
5581
+ const op = String(msg.type).replace("fleet_schedule_", "");
5582
+ const result = this.performScheduleOp(instanceName, op, payload, {
5583
+ // The daemon sends its last chat id, which is unset until the instance
5584
+ // has had a chat message — and cross-instance traffic never sets it. So
5585
+ // a worker that only takes delegated tasks, the very instance #895 lets
5586
+ // self-schedule, hit "NOT NULL constraint failed: schedules.reply_chat_id".
5587
+ // No chat means no reply chat, exactly as on the agent endpoint.
5588
+ chatId: meta.chat_id ?? "",
5589
+ threadId: meta.thread_id || null,
5590
+ adapterId: meta.adapter_id || null,
5591
+ silent: !!(payload.silent),
5592
+ });
5583
5593
  ipc.send({ type: "fleet_schedule_response", fleetRequestId, result });
5584
5594
  }
5585
5595
  catch (err) {
@@ -5715,23 +5725,76 @@ export class FleetManager {
5715
5725
  async handleScheduleCrudHttp(instance, op, args) {
5716
5726
  if (!this.scheduler)
5717
5727
  return { error: "Scheduler not available" };
5728
+ if (op !== "create" && op !== "list" && op !== "update" && op !== "delete") {
5729
+ return { error: `Unknown schedule op: ${op}` };
5730
+ }
5731
+ // No bound chat on this path, as before: a schedule made through the agent
5732
+ // endpoint has nowhere of its own to reply.
5733
+ return this.performScheduleOp(instance, op, args, { chatId: "", threadId: null });
5734
+ }
5735
+ /**
5736
+ * The only place an agent's request creates, changes or removes a schedule
5737
+ * (#895). The IPC handler (MCP calls, direct channel.sock writes) and the
5738
+ * agent endpoint (agent-cli, HTTP agent mode) are adapters over this, so the
5739
+ * target check cannot be present on one face and missing on the other — the
5740
+ * shape #804 had to close twice.
5741
+ *
5742
+ * `caller` is the instance the server resolved for the request; any
5743
+ * `source` in `args` is ignored, and a schedule's source is always its caller.
5744
+ * A refusal is a ToolNotPermittedError: 403 on the agent endpoint, the error
5745
+ * of the schedule response on IPC.
5746
+ */
5747
+ performScheduleOp(caller, op, args, reply) {
5748
+ const scheduler = this.scheduler;
5749
+ // Shape first, so the value the permission decision reads is the value the
5750
+ // scheduler gets. `target` used to be filtered through typeof for the
5751
+ // decision and passed raw to the scheduler, whose `target.startsWith`
5752
+ // then threw a TypeError for `target: 123` (#897). A non-string is refused
5753
+ // — never coerced: turning 123 into "123" would decide an identity
5754
+ // question on a value the caller did not send. `null` means absent, as
5755
+ // omitting it always did, and is removed before the scheduler sees it.
5756
+ if (args.target === null) {
5757
+ args = { ...args };
5758
+ delete args.target;
5759
+ }
5760
+ if (args.target !== undefined && typeof args.target !== "string") {
5761
+ throw new Error(`${op}_schedule: "target" must be an instance name (a string), not ${typeof args.target}.`);
5762
+ }
5763
+ if ((op === "update" || op === "delete") && typeof args.id !== "string") {
5764
+ throw new Error(`${op}_schedule: "id" must be a schedule id (a string) — get one from list_schedules.`);
5765
+ }
5766
+ const requestedTarget = args.target;
5767
+ if (op !== "list") {
5768
+ const profile = resolveToolSet(this.fleetConfig?.instances[caller], caller);
5769
+ const existing = op === "create" ? null : scheduler.get(args.id);
5770
+ const refusal = scheduleOpRefusal(profile, caller, op, { requestedTarget, existing });
5771
+ if (refusal) {
5772
+ this.logger.warn({ instance: caller, profile, op, target: requestedTarget ?? existing?.target, scheduleId: args.id }, "tool-permissions: schedule op refused");
5773
+ throw new ToolNotPermittedError(refusal);
5774
+ }
5775
+ }
5718
5776
  switch (op) {
5719
5777
  case "create":
5720
- return this.scheduler.create({
5778
+ return scheduler.create({
5721
5779
  cron: args.cron,
5722
5780
  at: args.at,
5723
5781
  message: args.message,
5724
- source: instance, target: args.target || instance,
5725
- reply_chat_id: "", reply_thread_id: null,
5782
+ source: caller,
5783
+ target: requestedTarget || caller,
5784
+ reply_chat_id: reply.chatId,
5785
+ reply_thread_id: reply.threadId,
5786
+ ...(reply.adapterId !== undefined ? { reply_adapter_id: reply.adapterId } : {}),
5726
5787
  label: args.label,
5727
5788
  timezone: args.timezone,
5789
+ ...(reply.silent !== undefined ? { silent: reply.silent } : {}),
5728
5790
  });
5729
- case "list": return this.scheduler.list(args.target);
5730
- case "update": return this.scheduler.update(args.id, args);
5791
+ case "list":
5792
+ return scheduler.list(requestedTarget);
5793
+ case "update":
5794
+ return scheduler.update(args.id, args);
5731
5795
  case "delete":
5732
- this.scheduler.delete(args.id);
5796
+ scheduler.delete(args.id);
5733
5797
  return "ok";
5734
- default: return { error: `Unknown schedule op: ${op}` };
5735
5798
  }
5736
5799
  }
5737
5800
  async handleDecisionCrudHttp(instance, op, args) {
@@ -6396,6 +6459,9 @@ export class FleetManager {
6396
6459
  }
6397
6460
  }
6398
6461
  }
6462
+ // Capture instance dir BEFORE lifecycle.remove, which deletes the instance
6463
+ // from fleetConfig. (The backend would be unresolvable afterwards.)
6464
+ const instanceDirToClean = this.getInstanceDir(name);
6399
6465
  await this.lifecycle.remove(name, authorization);
6400
6466
  // Clean up per-instance tracking maps so they don't grow unbounded
6401
6467
  // as instances are created and deleted over the lifetime of the fleet.
@@ -6404,12 +6470,29 @@ export class FleetManager {
6404
6470
  this.rateLimitWarnedAt.delete(name);
6405
6471
  // Clean up statusline watcher + instance directory
6406
6472
  this.statuslineWatcher.unwatch(name);
6473
+ // instanceDirToClean and effectiveBackend are captured before lifecycle.remove
6474
+ // (which deletes the instance from fleetConfig).
6407
6475
  try {
6408
- rmSync(this.getInstanceDir(name), { recursive: true, force: true });
6476
+ rmSync(instanceDirToClean, { recursive: true, force: true });
6409
6477
  }
6410
6478
  catch (err) {
6411
6479
  this.logger.debug({ err, name }, "Instance dir cleanup failed");
6412
6480
  }
6481
+ // Remove the codex short home (~/.agend/cx/<hash>/) unconditionally.
6482
+ // (#953). CodexBackend.shortHomeFor is a pure path computation; the existsSync
6483
+ // guard makes this a no-op for instances that never used a short home (non-codex,
6484
+ // or codex instances on a short-enough path). Unconditional also covers instances
6485
+ // whose backend was switched away from codex before deletion — they still have a
6486
+ // short home that would otherwise be inherited by a later same-name instance.
6487
+ try {
6488
+ const { CodexBackend } = await import("./backend/codex.js");
6489
+ const shortHome = CodexBackend.shortHomeFor(instanceDirToClean);
6490
+ if (existsSync(shortHome))
6491
+ rmSync(shortHome, { recursive: true, force: true });
6492
+ }
6493
+ catch (err) {
6494
+ this.logger.debug({ err, name }, "Codex short home cleanup failed");
6495
+ }
6413
6496
  }
6414
6497
  startStatuslineWatcher(name) {
6415
6498
  if (this.lifecycle.isPaused(name))
@@ -9993,17 +10076,25 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
9993
10076
  return [];
9994
10077
  }
9995
10078
  }
9996
- /** Account-wide catalog: probe cache first, live probe on miss. */
10079
+ /**
10080
+ * Account-wide catalog: probe cache first, live probe when the cache is
10081
+ * missing or older than the same ~1h window `/model` uses (#888).
10082
+ *
10083
+ * Previously this served anything inside the 24h hard TTL, so `list_models`
10084
+ * could answer with a day-old list `/model` had already re-probed past.
10085
+ * The probe runs under the same deadline `/model` uses: a vendor that stops
10086
+ * answering degrades to the previous list, never stalls the tool call.
10087
+ */
9997
10088
  async globalModelCatalog(backend) {
9998
10089
  const cached = this.readCliEnv(backend);
9999
- if (cached?.models?.length) {
10090
+ if (cached?.models?.length && !this.cliEnvNeedsRefresh(cached)) {
10000
10091
  return {
10001
10092
  backend, scope: "global", current_model: cached.currentModel ?? null,
10002
10093
  models: cached.models, source: "cache",
10003
10094
  probed_at: new Date(cached.probedAt).toISOString(),
10004
10095
  };
10005
10096
  }
10006
- const env = await this.probeBackend(backend);
10097
+ const env = await this.probeBackendBounded(backend);
10007
10098
  if (env?.models?.length) {
10008
10099
  return {
10009
10100
  backend, scope: "global", current_model: env.currentModel ?? null,
@@ -10011,6 +10102,15 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10011
10102
  probed_at: new Date(env.probedAt).toISOString(),
10012
10103
  };
10013
10104
  }
10105
+ // Probe failed or timed out: like `/model`, the previous list is still the
10106
+ // best answer — a stale list beats an empty one.
10107
+ if (cached?.models?.length) {
10108
+ return {
10109
+ backend, scope: "global", current_model: cached.currentModel ?? null,
10110
+ models: cached.models, source: "cache",
10111
+ probed_at: new Date(cached.probedAt).toISOString(),
10112
+ };
10113
+ }
10014
10114
  // Reported rather than thrown: "we could not enumerate" is a useful answer,
10015
10115
  // and the caller can still set a model by name (AgEnD passes it through).
10016
10116
  return {
@@ -10952,6 +11052,11 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
10952
11052
  clearInterval(this.discordPresenceTimer);
10953
11053
  this.discordPresenceTimer = null;
10954
11054
  }
11055
+ if (this.discordPresenceEagerTimer) {
11056
+ clearTimeout(this.discordPresenceEagerTimer);
11057
+ this.discordPresenceEagerTimer = null;
11058
+ }
11059
+ this.discordPresenceEagerPending = false;
10955
11060
  // Cancel-button timers were never cleared here. The idle-check interval is not
10956
11061
  // unref'd, so it held the event loop open past shutdown and kept retrying
10957
11062
  // deletes against an adapter that was already gone.