dsh-lark-bot 0.19.10 → 0.19.11

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/cli.js CHANGED
@@ -3700,6 +3700,10 @@ var DEFAULTS = {
3700
3700
  guardianBridgeProfile: "default",
3701
3701
  upgradeNotify: false,
3702
3702
  upgradeCheckIntervalMs: 6 * 60 * 6e4,
3703
+ channelPingTimeoutSec: 30,
3704
+ channelKeepalive: true,
3705
+ channelKeepaliveMs: 15e3,
3706
+ channelHealthPollMs: 5e3,
3703
3707
  sessionBackfillMessages: 20,
3704
3708
  sessionBackfillBytes: 64 * 1024,
3705
3709
  sessionStreamUpdateMs: 800
@@ -3930,6 +3934,22 @@ function loadRuntimeEnv(source = process.env) {
3930
3934
  source.DSH_LARK_GUARDIAN_ENGINE_DEAD_MS,
3931
3935
  DEFAULTS.guardianEngineDeadMs,
3932
3936
  "DSH_LARK_GUARDIAN_ENGINE_DEAD_MS"
3937
+ ),
3938
+ channelPingTimeoutSec: parsePositiveIntMin(
3939
+ source.DSH_LARK_CHANNEL_PING_TIMEOUT_SEC,
3940
+ DEFAULTS.channelPingTimeoutSec,
3941
+ "DSH_LARK_CHANNEL_PING_TIMEOUT_SEC"
3942
+ ),
3943
+ channelKeepalive: parseBoolean(source.DSH_LARK_CHANNEL_KEEPALIVE, DEFAULTS.channelKeepalive),
3944
+ channelKeepaliveMs: parsePositiveIntMin(
3945
+ source.DSH_LARK_CHANNEL_KEEPALIVE_MS,
3946
+ DEFAULTS.channelKeepaliveMs,
3947
+ "DSH_LARK_CHANNEL_KEEPALIVE_MS"
3948
+ ),
3949
+ channelHealthPollMs: parsePositiveIntMin(
3950
+ source.DSH_LARK_CHANNEL_HEALTH_POLL_MS,
3951
+ DEFAULTS.channelHealthPollMs,
3952
+ "DSH_LARK_CHANNEL_HEALTH_POLL_MS"
3933
3953
  )
3934
3954
  };
3935
3955
  }
@@ -4477,15 +4497,6 @@ function guardianStatePath(root) {
4477
4497
  return join11(root, ".dsh-lark", "guardian.json");
4478
4498
  }
4479
4499
 
4480
- // src/upgrade/detect.ts
4481
- init_own_package();
4482
- import { readFile as readFile10 } from "fs/promises";
4483
- import { join as join12 } from "path";
4484
- import { homedir as homedir7 } from "os";
4485
- init_dsh_runtime();
4486
- init_process();
4487
- import { existsSync as existsSync6 } from "fs";
4488
-
4489
4500
  // src/guardian/heartbeat.ts
4490
4501
  import { readFile as readFile9 } from "fs/promises";
4491
4502
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 5e3;
@@ -4496,15 +4507,25 @@ async function readHeartbeat(file) {
4496
4507
  if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string" || typeof parsed.ts !== "number") {
4497
4508
  return void 0;
4498
4509
  }
4499
- return {
4510
+ const payload = {
4500
4511
  pid: parsed.pid,
4501
4512
  startedAt: parsed.startedAt,
4502
4513
  ts: parsed.ts
4503
4514
  };
4515
+ const channel = parsed.channel;
4516
+ if (isChannelSnapshot(channel)) {
4517
+ payload.channel = channel;
4518
+ }
4519
+ return payload;
4504
4520
  } catch {
4505
4521
  return void 0;
4506
4522
  }
4507
4523
  }
4524
+ function isChannelSnapshot(value) {
4525
+ if (!value || typeof value !== "object") return false;
4526
+ const snapshot = value;
4527
+ return typeof snapshot.state === "string" && typeof snapshot.ready === "boolean";
4528
+ }
4508
4529
  function heartbeatAgeMs(payload, now = Date.now()) {
4509
4530
  return Math.max(0, now - payload.ts);
4510
4531
  }
@@ -4512,12 +4533,34 @@ function isHeartbeatFresh(payload, maxAgeMs, now = Date.now()) {
4512
4533
  if (payload === void 0) return false;
4513
4534
  return heartbeatAgeMs(payload, now) < maxAgeMs;
4514
4535
  }
4515
- function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS) {
4536
+ function channelHealthLabel(channel) {
4537
+ if (channel === void 0) return "\u672A\u4E0A\u62A5";
4538
+ if (channel.ready) {
4539
+ return `ready (generation ${channel.generation ?? "-"})`;
4540
+ }
4541
+ switch (channel.state) {
4542
+ case "connecting":
4543
+ return "connecting";
4544
+ case "reconnecting":
4545
+ return `reconnecting (attempt ${channel.reconnectAttempts ?? "-"})`;
4546
+ case "failed":
4547
+ return "failed";
4548
+ case "stopped":
4549
+ return "stopped";
4550
+ default:
4551
+ return channel.state;
4552
+ }
4553
+ }
4554
+ function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS, getChannelHealth) {
4516
4555
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4517
4556
  let stopped = false;
4518
4557
  const beat = async () => {
4519
4558
  if (stopped) return;
4520
4559
  const payload = { pid, startedAt, ts: Date.now() };
4560
+ const channel = getChannelHealth?.();
4561
+ if (channel !== void 0) {
4562
+ payload.channel = channel;
4563
+ }
4521
4564
  try {
4522
4565
  await writeFileAtomic(file, `${JSON.stringify(payload)}
4523
4566
  `, {
@@ -4540,6 +4583,13 @@ function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS) {
4540
4583
  }
4541
4584
 
4542
4585
  // src/upgrade/detect.ts
4586
+ init_own_package();
4587
+ import { readFile as readFile10 } from "fs/promises";
4588
+ import { join as join12 } from "path";
4589
+ import { homedir as homedir7 } from "os";
4590
+ init_dsh_runtime();
4591
+ init_process();
4592
+ import { existsSync as existsSync6 } from "fs";
4543
4593
  async function readInstalledPackage(dshHome, profile, packageName) {
4544
4594
  const manifest = join12(
4545
4595
  dshHome,
@@ -5918,6 +5968,18 @@ async function runDoctorChecks(options) {
5918
5968
  } catch (error) {
5919
5969
  lines.push(`service: \u26A0\uFE0F \u72B6\u6001\u68C0\u67E5\u5931\u8D25\uFF08${error instanceof Error ? error.message : String(error)}\uFF09`);
5920
5970
  }
5971
+ try {
5972
+ const heartbeat = await readHeartbeat(
5973
+ paths.profilePath(env.guardianBridgeProfile, "guardian", "heartbeat.json")
5974
+ );
5975
+ lines.push(`channel: ${channelHealthLabel(heartbeat?.channel)}`);
5976
+ if (heartbeat?.channel && !heartbeat.channel.ready) {
5977
+ lines.push(
5978
+ "channel: \u26A0\uFE0F \u5F15\u64CE\u8FDB\u7A0B\u5B58\u6D3B\u4F46\u901A\u9053\u672A\u5C31\u7EEA\uFF08\u534A\u5F00/\u91CD\u8FDE/\u5931\u8D25\uFF09\uFF1B\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8DEF\u7531\u6216\u91CD\u542F managed engine"
5979
+ );
5980
+ }
5981
+ } catch {
5982
+ }
5921
5983
  try {
5922
5984
  const state = await loadUpgradeState(upgradeStatePath(env.home));
5923
5985
  if (state?.lastUpgrade.pendingRestart === true) {
@@ -7057,6 +7119,106 @@ var LanguagePolicyStore = class {
7057
7119
  // src/bridge/channel.ts
7058
7120
  import { createLarkChannel as createLarkChannel2 } from "@larksuite/channel";
7059
7121
 
7122
+ // src/bridge/channel-health.ts
7123
+ function mapState(state) {
7124
+ switch (state) {
7125
+ case "connected":
7126
+ return "ready";
7127
+ case "reconnecting":
7128
+ return "reconnecting";
7129
+ case "failed":
7130
+ return "failed";
7131
+ case "connecting":
7132
+ return "connecting";
7133
+ case "idle":
7134
+ default:
7135
+ return "connecting";
7136
+ }
7137
+ }
7138
+ var ChannelHealthMonitor = class {
7139
+ channel;
7140
+ pollMs;
7141
+ onUpdate;
7142
+ generation = 0;
7143
+ connectedAt;
7144
+ lastInboundAt;
7145
+ lastReconnectAt;
7146
+ lastError;
7147
+ observedState = "connecting";
7148
+ reconnectAttempts = 0;
7149
+ at = 0;
7150
+ pollTimer;
7151
+ constructor(channel, options = {}) {
7152
+ this.channel = channel;
7153
+ this.pollMs = options.pollMs ?? 5e3;
7154
+ this.onUpdate = options.onUpdate;
7155
+ }
7156
+ /** Latest snapshot (always a copy; safe to persist). */
7157
+ snapshot() {
7158
+ return {
7159
+ state: this.observedState,
7160
+ ready: this.observedState === "ready",
7161
+ generation: this.generation,
7162
+ ...this.connectedAt !== void 0 ? { connectedAt: this.connectedAt } : {},
7163
+ reconnectAttempts: this.reconnectAttempts,
7164
+ ...this.lastInboundAt !== void 0 ? { lastInboundAt: this.lastInboundAt } : {},
7165
+ ...this.lastReconnectAt !== void 0 ? { lastReconnectAt: this.lastReconnectAt } : {},
7166
+ ...this.lastError !== void 0 ? { lastError: this.lastError } : {},
7167
+ at: this.at
7168
+ };
7169
+ }
7170
+ /** Bind transport hooks from the bridge event subscription. */
7171
+ observeMessage() {
7172
+ this.lastInboundAt = Date.now();
7173
+ this.markChanged();
7174
+ }
7175
+ observeReconnecting() {
7176
+ this.observedState = "reconnecting";
7177
+ this.markChanged();
7178
+ }
7179
+ observeReconnected() {
7180
+ this.generation += 1;
7181
+ const now = Date.now();
7182
+ this.connectedAt = now;
7183
+ this.lastReconnectAt = now;
7184
+ this.observedState = "ready";
7185
+ this.markChanged();
7186
+ }
7187
+ observeError(error) {
7188
+ this.lastError = error instanceof Error ? error.message : String(error);
7189
+ if (this.observedState !== "reconnecting" && this.observedState !== "failed") {
7190
+ this.observedState = "failed";
7191
+ }
7192
+ this.markChanged();
7193
+ }
7194
+ /** Poll the SDK's connection-status snapshot. */
7195
+ start() {
7196
+ if (this.pollTimer) return;
7197
+ this.refresh();
7198
+ this.pollTimer = setInterval(() => this.refresh(), this.pollMs);
7199
+ this.pollTimer.unref?.();
7200
+ }
7201
+ stop() {
7202
+ if (this.pollTimer) {
7203
+ clearInterval(this.pollTimer);
7204
+ this.pollTimer = void 0;
7205
+ }
7206
+ this.observedState = "stopped";
7207
+ this.markChanged();
7208
+ }
7209
+ refresh() {
7210
+ const status = this.channel.getConnectionStatus?.();
7211
+ this.observedState = mapState(status?.state);
7212
+ this.reconnectAttempts = status?.reconnectAttempts ?? this.reconnectAttempts;
7213
+ this.markChanged();
7214
+ }
7215
+ markChanged() {
7216
+ const now = Date.now();
7217
+ this.at = now;
7218
+ this.onUpdate?.(this.snapshot());
7219
+ }
7220
+ };
7221
+
7060
7222
  // src/commands/index.ts
7061
7223
  import { stat as stat8 } from "fs/promises";
7062
7224
  import { homedir as homedir14 } from "os";
@@ -13751,6 +13913,8 @@ The value is written only by the local bridge and is not sent to the agent.` },
13751
13913
  }
13752
13914
 
13753
13915
  // src/bridge/channel.ts
13916
+ var DEFAULT_CHANNEL_PING_TIMEOUT_SEC = 30;
13917
+ var DEFAULT_CHANNEL_KEEPALIVE_MS = 15e3;
13754
13918
  async function startChannel(deps) {
13755
13919
  const channel = (deps.createChannel ?? createLarkChannel2)({
13756
13920
  appId: deps.appId,
@@ -13777,7 +13941,26 @@ async function startChannel(deps) {
13777
13941
  resolveChatMode: true,
13778
13942
  handshakeTimeoutMs: 8e3,
13779
13943
  httpTimeoutMs: 3e4,
13780
- respectProxyEnv: true
13944
+ respectProxyEnv: true,
13945
+ // Issue #108: detect a half-open WebSocket (TCP ESTABLISHED but Feishu no
13946
+ // longer delivering). `pingTimeout` force-reconnects when no inbound frame
13947
+ // arrives after the last ping; the app-level `keepalive` probes and
13948
+ // force-reconnects, and `onUnrecoverable` fires when even that fails so the
13949
+ // engine can exit and let the managed service / guardian restart it.
13950
+ wsConfig: { pingTimeout: deps.channelPingTimeoutSec ?? DEFAULT_CHANNEL_PING_TIMEOUT_SEC },
13951
+ keepalive: {
13952
+ enabled: deps.channelKeepalive ?? true,
13953
+ intervalMs: deps.channelKeepaliveMs ?? DEFAULT_CHANNEL_KEEPALIVE_MS,
13954
+ onUnrecoverable: (error) => {
13955
+ log.fail("channel", "unrecoverable", {
13956
+ error: error instanceof Error ? error.message : String(error)
13957
+ });
13958
+ deps.onChannelUnrecoverable?.(error);
13959
+ }
13960
+ }
13961
+ });
13962
+ const channelHealth = new ChannelHealthMonitor(channel, {
13963
+ ...deps.channelHealthPollMs !== void 0 ? { pollMs: deps.channelHealthPollMs } : {}
13781
13964
  });
13782
13965
  const streaming = adaptLarkChannel(channel);
13783
13966
  const commandChannel = streaming;
@@ -13818,6 +14001,7 @@ async function startChannel(deps) {
13818
14001
  const isolationStore = deps.isolationStore ?? EMPTY_ISOLATION_STORE;
13819
14002
  let groupPoller;
13820
14003
  const processMessage = async (msg, alreadyClaimed = false) => {
14004
+ channelHealth.observeMessage();
13821
14005
  if (groupPoller && !alreadyClaimed && !groupPoller.claim(msg.messageId)) return;
13822
14006
  const chatMode = msg.chatMode ?? msg.chatType;
13823
14007
  const botSender = msg.senderType === "bot";
@@ -14394,21 +14578,25 @@ ${msg.content}`,
14394
14578
  },
14395
14579
  reconnecting: () => {
14396
14580
  log.warn("channel", "reconnecting", {});
14581
+ channelHealth.observeReconnecting();
14397
14582
  void reconnectNotifier.reconnecting().catch((error) => {
14398
14583
  log.fail("channel-reconnect-notice", error);
14399
14584
  });
14400
14585
  },
14401
14586
  reconnected: () => {
14402
14587
  log.info("channel", "reconnected", {});
14588
+ channelHealth.observeReconnected();
14403
14589
  void reconnectNotifier.reconnected().catch((error) => {
14404
14590
  log.fail("channel-reconnect-notice", error);
14405
14591
  });
14406
14592
  },
14407
14593
  error: (error) => {
14408
14594
  log.fail("channel", error);
14595
+ channelHealth.observeError(error);
14409
14596
  }
14410
14597
  });
14411
14598
  await channel.connect();
14599
+ channelHealth.start();
14412
14600
  if (sessionProjectionBridge) {
14413
14601
  void sessionProjectionBridge.start().catch((error) => {
14414
14602
  log.fail("session-projection", error, { step: "start" });
@@ -14425,8 +14613,10 @@ ${msg.content}`,
14425
14613
  }
14426
14614
  return {
14427
14615
  channel,
14616
+ channelHealth: () => channelHealth.snapshot(),
14428
14617
  disconnect: async () => {
14429
14618
  await groupPoller?.stop();
14619
+ channelHealth.stop();
14430
14620
  sessionProjection?.close();
14431
14621
  await sessionProjectionBridge?.close();
14432
14622
  await channel.disconnect();
@@ -18586,7 +18776,18 @@ async function startBridgeEngine(options) {
18586
18776
  },
18587
18777
  allowedUsers: activeProfile.access.allowedUsers,
18588
18778
  allowedChats: activeProfile.access.allowedChats,
18589
- ...options.createChannel ? { createChannel: options.createChannel } : {}
18779
+ ...options.createChannel ? { createChannel: options.createChannel } : {},
18780
+ channelPingTimeoutSec: env.channelPingTimeoutSec,
18781
+ channelKeepalive: env.channelKeepalive,
18782
+ channelKeepaliveMs: env.channelKeepaliveMs,
18783
+ channelHealthPollMs: env.channelHealthPollMs,
18784
+ onChannelUnrecoverable: (error) => {
18785
+ log.fail("engine", "channel-unrecoverable", {
18786
+ error: error instanceof Error ? error.message : String(error)
18787
+ });
18788
+ process.exitCode = 1;
18789
+ setTimeout(() => process.exit(1), 300);
18790
+ }
18590
18791
  };
18591
18792
  if (activeProfile.preferences.stopGraceMs !== void 0) {
18592
18793
  channelInput.stopGraceMs = activeProfile.preferences.stopGraceMs;
@@ -18651,7 +18852,8 @@ async function startBridgeEngine(options) {
18651
18852
  const heartbeat = startHeartbeat(
18652
18853
  paths.profilePath(profileName, "guardian", "heartbeat.json"),
18653
18854
  process.pid,
18654
- env.heartbeatMs
18855
+ env.heartbeatMs,
18856
+ () => bridge.channelHealth?.()
18655
18857
  );
18656
18858
  await updateHandoff.reconcile(currentVersion());
18657
18859
  void deliverUpdateResult().catch((error) => log.fail("upgrade", error, { step: "deliver-result" }));
@@ -19730,6 +19932,7 @@ var GuardianService = class {
19730
19932
  dshUp: this.dshUp,
19731
19933
  heartbeatAgeMs: this.lastHeartbeatAgeMs,
19732
19934
  channelConnected: this.channel !== void 0,
19935
+ channelState: this.lastHeartbeatChannel?.state,
19733
19936
  safeEngine: this.safeEngine?.kind,
19734
19937
  safeRuns: this.safeRuns.size,
19735
19938
  pid: process.pid,
@@ -19740,6 +19943,9 @@ var GuardianService = class {
19740
19943
  }
19741
19944
  dshUp = false;
19742
19945
  lastHeartbeatAgeMs;
19946
+ lastHeartbeatChannel;
19947
+ /** When the engine reported a non-ready channel while its heartbeat was fresh. */
19948
+ channelUnhealthySinceMs;
19743
19949
  log() {
19744
19950
  return this.options.logger ?? log;
19745
19951
  }
@@ -19774,7 +19980,24 @@ var GuardianService = class {
19774
19980
  const now = (this.options.now ?? Date.now)();
19775
19981
  const heartbeatFresh = isHeartbeatFresh(heartbeat, this.options.staleMs, now);
19776
19982
  this.lastHeartbeatAgeMs = heartbeat ? heartbeatAgeMs(heartbeat, now) : void 0;
19983
+ this.lastHeartbeatChannel = heartbeat?.channel;
19777
19984
  if (heartbeatFresh) this.lastHeartbeatFreshAt = now;
19985
+ const channelSnapshot = this.lastHeartbeatChannel;
19986
+ const channelUnhealthy = heartbeatFresh && channelSnapshot !== void 0 && channelSnapshot.ready === false;
19987
+ if (channelUnhealthy) {
19988
+ if (this.channelUnhealthySinceMs === void 0) {
19989
+ this.channelUnhealthySinceMs = now;
19990
+ this.log().warn("guardian", "channel-unhealthy", {
19991
+ dshProfile: this.state.dshProfile,
19992
+ state: channelSnapshot.state,
19993
+ generation: channelSnapshot.generation,
19994
+ reconnectAttempts: channelSnapshot.reconnectAttempts,
19995
+ lastError: channelSnapshot.lastError
19996
+ });
19997
+ }
19998
+ } else {
19999
+ this.channelUnhealthySinceMs = void 0;
20000
+ }
19778
20001
  const processFound = await (this.options.findProcess ?? findProfileProcess)(
19779
20002
  this.state.dshProfile
19780
20003
  );
@@ -20822,6 +21045,7 @@ async function statusGuardianCommand(options = {}, deps = {}) {
20822
21045
  `\u6865\u63A5 profile\uFF1A${state.bridgeProfile}`,
20823
21046
  `dsh \u662F\u5426\u5728\u7EBF\uFF1A${up ? "\u662F" : "\u5426"}${processFound ? `\uFF08pid ${processFound.pid}\uFF09` : ""}`,
20824
21047
  `\u5FC3\u8DF3\u9F84\uFF1A${heartbeat ? `${heartbeatAgeMs(heartbeat)}ms` : "\u65E0"}`,
21048
+ `\u98DE\u4E66\u901A\u9053\uFF1A${channelHealthLabel(heartbeat?.channel)}`,
20825
21049
  `\u5DF2\u89C2\u5BDF\u8FC7 dsh \u8FD0\u884C\uFF1A${state.profileSeenUp ? "\u662F" : "\u5426"}`,
20826
21050
  guardianProcess === void 0 ? "\u5B88\u62A4\u8FDB\u7A0B pid\uFF1A\u672A\u53D1\u73B0\uFF08\u65E0\u6CD5\u552F\u4E00\u8BC1\u660E resident guardian \u8EAB\u4EFD\uFF09" : `\u5B88\u62A4\u8FDB\u7A0B pid\uFF1A${guardianProcess.pid}`,
20827
21051
  `\u72B6\u6001\u6587\u4EF6\uFF1A${layout.stateFile}`,
@@ -20860,7 +21084,7 @@ function managerFor(options, version) {
20860
21084
  version
20861
21085
  });
20862
21086
  }
20863
- function formatServiceStatus(status, heartbeatAge2) {
21087
+ function formatServiceStatus(status, heartbeatAge2, channel) {
20864
21088
  return [
20865
21089
  "dsh-lark-bot \u6B63\u5E38\u5F15\u64CE\u670D\u52A1",
20866
21090
  ` name: ${status.name}`,
@@ -20871,7 +21095,8 @@ function formatServiceStatus(status, heartbeatAge2) {
20871
21095
  ` detail: ${status.detail}`,
20872
21096
  ` pid: ${status.pid ?? "-"}`,
20873
21097
  ` restarts: ${status.restarts ?? "-"}`,
20874
- ` heartbeat: ${heartbeatAge2 === void 0 ? "\u6682\u65E0" : `${heartbeatAge2}ms`}`
21098
+ ` heartbeat: ${heartbeatAge2 === void 0 ? "\u6682\u65E0" : `${heartbeatAge2}ms`}`,
21099
+ ` channel: ${channelHealthLabel(channel)}`
20875
21100
  ].join("\n");
20876
21101
  }
20877
21102
  async function heartbeatAge() {
@@ -20884,6 +21109,16 @@ async function heartbeatAge() {
20884
21109
  const heartbeat = await readHeartbeat(file);
20885
21110
  return heartbeat ? heartbeatAgeMs(heartbeat) : void 0;
20886
21111
  }
21112
+ async function heartbeatChannel() {
21113
+ const env = loadRuntimeEnv(process.env);
21114
+ const file = resolveAppPaths(env.home).profilePath(
21115
+ env.guardianBridgeProfile,
21116
+ "guardian",
21117
+ "heartbeat.json"
21118
+ );
21119
+ const heartbeat = await readHeartbeat(file);
21120
+ return heartbeat?.channel;
21121
+ }
20887
21122
  async function runServiceCommand(action, options = {}, deps = {}) {
20888
21123
  const manager = deps.manager ?? managerFor(options, deps.version ?? "0.0.0");
20889
21124
  const output = deps.output ?? ((text) => process.stdout.write(text));
@@ -20900,7 +21135,11 @@ ${logs.text || "\uFF08\u6682\u65E0\u65E5\u5FD7\uFF09"}
20900
21135
  return;
20901
21136
  }
20902
21137
  const status = action === "install" ? await manager.install() : action === "start" ? await manager.start() : action === "restart" ? await manager.restart() : action === "stop" ? await manager.stop() : action === "uninstall" ? await manager.uninstall() : await manager.status();
20903
- output(`${formatServiceStatus(status, await (deps.heartbeatAge ?? heartbeatAge)())}
21138
+ const [age, channel] = await Promise.all([
21139
+ (deps.heartbeatAge ?? heartbeatAge)(),
21140
+ (deps.heartbeatChannel ?? heartbeatChannel)()
21141
+ ]);
21142
+ output(`${formatServiceStatus(status, age, channel)}
20904
21143
  `);
20905
21144
  if ((action === "install" || action === "start" || action === "restart") && status.state !== "running") process.exitCode = 1;
20906
21145
  if (action === "status" && (!status.installed || status.state === "error")) {