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 +256 -17
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +69 -0
- package/dist/plugin.js +168 -4
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
-
|
|
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")) {
|