dsh-lark-bot 0.19.10 → 0.19.12
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 +370 -71
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +69 -0
- package/dist/plugin.js +315 -29
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -296,6 +296,16 @@ var init_profile_package = __esm({
|
|
|
296
296
|
}
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
+
// src/adapters/model-choice.ts
|
|
300
|
+
function resolveModelChoice(preferenceModel, envModel) {
|
|
301
|
+
return preferenceModel?.trim() || envModel;
|
|
302
|
+
}
|
|
303
|
+
var init_model_choice = __esm({
|
|
304
|
+
"src/adapters/model-choice.ts"() {
|
|
305
|
+
"use strict";
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
299
309
|
// src/adapters/dsh/acp-runtime.ts
|
|
300
310
|
import { spawn as spawn4 } from "cross-spawn";
|
|
301
311
|
import { existsSync as existsSync4, realpathSync as realpathSync3 } from "fs";
|
|
@@ -666,7 +676,7 @@ async function buildAcpAgentAdapter(env, preferences) {
|
|
|
666
676
|
home: homedir2(),
|
|
667
677
|
env: process.env,
|
|
668
678
|
provider: env.provider,
|
|
669
|
-
model: preferences.model
|
|
679
|
+
model: resolveModelChoice(preferences.model, env.model),
|
|
670
680
|
...env.dshExplicit ? { command: env.dshCommand, args: env.dshArgs } : {}
|
|
671
681
|
};
|
|
672
682
|
const ensure = await ensureAcpProfile(runtimeOptions);
|
|
@@ -679,7 +689,7 @@ async function buildAcpAgentAdapter(env, preferences) {
|
|
|
679
689
|
return new AcpDshAdapter({
|
|
680
690
|
launch,
|
|
681
691
|
provider: env.provider,
|
|
682
|
-
model: preferences.model
|
|
692
|
+
model: resolveModelChoice(preferences.model, env.model)
|
|
683
693
|
});
|
|
684
694
|
}
|
|
685
695
|
var AcpDshAdapter;
|
|
@@ -688,6 +698,7 @@ var init_acp_adapter = __esm({
|
|
|
688
698
|
"use strict";
|
|
689
699
|
init_acp_runtime();
|
|
690
700
|
init_event_channel();
|
|
701
|
+
init_model_choice();
|
|
691
702
|
AcpDshAdapter = class {
|
|
692
703
|
id = "dsh-acp";
|
|
693
704
|
displayName = "DeepSeek Harness (ACP)";
|
|
@@ -972,12 +983,18 @@ async function listProcesses(platform = process.platform, run = captureOutput) {
|
|
|
972
983
|
return listProcessesPosix(run);
|
|
973
984
|
}
|
|
974
985
|
async function listProcessesPosix(run) {
|
|
975
|
-
const { stdout } = await run("ps", ["-axo", "pid=,args="], 1e4);
|
|
986
|
+
const { stdout } = await run("ps", ["-axo", "pid=,ppid=,args="], 1e4);
|
|
976
987
|
const result = [];
|
|
977
988
|
for (const line of stdout.split("\n")) {
|
|
978
|
-
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
979
|
-
if (match)
|
|
980
|
-
|
|
989
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
|
|
990
|
+
if (!match) continue;
|
|
991
|
+
const pid = Number(match[1]);
|
|
992
|
+
const ppid = Number(match[2]);
|
|
993
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
994
|
+
if (Number.isInteger(ppid) && ppid > 0) {
|
|
995
|
+
result.push({ pid, ppid, cmdline: match[3] ?? "" });
|
|
996
|
+
} else {
|
|
997
|
+
result.push({ pid, cmdline: match[3] ?? "" });
|
|
981
998
|
}
|
|
982
999
|
}
|
|
983
1000
|
return result;
|
|
@@ -989,7 +1006,7 @@ async function listProcessesWindows(run) {
|
|
|
989
1006
|
"-NoProfile",
|
|
990
1007
|
"-NonInteractive",
|
|
991
1008
|
"-Command",
|
|
992
|
-
"Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress"
|
|
1009
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId, CommandLine | ConvertTo-Json -Compress"
|
|
993
1010
|
],
|
|
994
1011
|
1e4
|
|
995
1012
|
);
|
|
@@ -1000,8 +1017,11 @@ async function listProcessesWindows(run) {
|
|
|
1000
1017
|
if (typeof row !== "object" || row === null) return [];
|
|
1001
1018
|
const record = row;
|
|
1002
1019
|
const pid = Number(record.ProcessId);
|
|
1020
|
+
const ppid = Number(record.ParentProcessId);
|
|
1003
1021
|
const cmdline = record.CommandLine;
|
|
1004
|
-
|
|
1022
|
+
if (!Number.isInteger(pid) || pid <= 0 || typeof cmdline !== "string") return [];
|
|
1023
|
+
if (Number.isInteger(ppid) && ppid > 0) return [{ pid, ppid, cmdline }];
|
|
1024
|
+
return [{ pid, cmdline }];
|
|
1005
1025
|
});
|
|
1006
1026
|
} catch {
|
|
1007
1027
|
return [];
|
|
@@ -1703,7 +1723,7 @@ var DshProviderManager = class {
|
|
|
1703
1723
|
try {
|
|
1704
1724
|
catalogProviders = await this.catalog.listProviders();
|
|
1705
1725
|
} catch (error) {
|
|
1706
|
-
log.
|
|
1726
|
+
log.info("model-catalog", "refresh-failed", {
|
|
1707
1727
|
error: error instanceof Error ? error.message : String(error)
|
|
1708
1728
|
});
|
|
1709
1729
|
}
|
|
@@ -3442,6 +3462,7 @@ var WebDshAdapter = class {
|
|
|
3442
3462
|
};
|
|
3443
3463
|
|
|
3444
3464
|
// src/adapters/index.ts
|
|
3465
|
+
init_model_choice();
|
|
3445
3466
|
async function resolveAdapterRoute(input, source) {
|
|
3446
3467
|
const provider = input.provider?.trim() || void 0;
|
|
3447
3468
|
const model = input.model?.trim() || void 0;
|
|
@@ -3461,7 +3482,7 @@ async function resolveAdapterRoute(input, source) {
|
|
|
3461
3482
|
return void 0;
|
|
3462
3483
|
}
|
|
3463
3484
|
async function buildAgentAdapter(env, preferences = { stopGraceMs: void 0, model: void 0 }) {
|
|
3464
|
-
const configuredModel = preferences.model
|
|
3485
|
+
const configuredModel = resolveModelChoice(preferences.model, env.model);
|
|
3465
3486
|
const managedRoute = env.adapterMode === "sdk" || env.adapterMode === "acp" ? await resolveAdapterRoute(
|
|
3466
3487
|
{ provider: env.provider, model: configuredModel },
|
|
3467
3488
|
new DshProviderManager({ env: process.env })
|
|
@@ -3493,7 +3514,7 @@ async function buildAgentAdapter(env, preferences = { stopGraceMs: void 0, model
|
|
|
3493
3514
|
return new WebDshAdapter({
|
|
3494
3515
|
baseUrl: env.webBaseUrl,
|
|
3495
3516
|
provider: env.provider,
|
|
3496
|
-
model: preferences.model
|
|
3517
|
+
model: resolveModelChoice(preferences.model, env.model)
|
|
3497
3518
|
});
|
|
3498
3519
|
}
|
|
3499
3520
|
case "sdk":
|
|
@@ -3700,6 +3721,10 @@ var DEFAULTS = {
|
|
|
3700
3721
|
guardianBridgeProfile: "default",
|
|
3701
3722
|
upgradeNotify: false,
|
|
3702
3723
|
upgradeCheckIntervalMs: 6 * 60 * 6e4,
|
|
3724
|
+
channelPingTimeoutSec: 30,
|
|
3725
|
+
channelKeepalive: true,
|
|
3726
|
+
channelKeepaliveMs: 15e3,
|
|
3727
|
+
channelHealthPollMs: 5e3,
|
|
3703
3728
|
sessionBackfillMessages: 20,
|
|
3704
3729
|
sessionBackfillBytes: 64 * 1024,
|
|
3705
3730
|
sessionStreamUpdateMs: 800
|
|
@@ -3930,6 +3955,22 @@ function loadRuntimeEnv(source = process.env) {
|
|
|
3930
3955
|
source.DSH_LARK_GUARDIAN_ENGINE_DEAD_MS,
|
|
3931
3956
|
DEFAULTS.guardianEngineDeadMs,
|
|
3932
3957
|
"DSH_LARK_GUARDIAN_ENGINE_DEAD_MS"
|
|
3958
|
+
),
|
|
3959
|
+
channelPingTimeoutSec: parsePositiveIntMin(
|
|
3960
|
+
source.DSH_LARK_CHANNEL_PING_TIMEOUT_SEC,
|
|
3961
|
+
DEFAULTS.channelPingTimeoutSec,
|
|
3962
|
+
"DSH_LARK_CHANNEL_PING_TIMEOUT_SEC"
|
|
3963
|
+
),
|
|
3964
|
+
channelKeepalive: parseBoolean(source.DSH_LARK_CHANNEL_KEEPALIVE, DEFAULTS.channelKeepalive),
|
|
3965
|
+
channelKeepaliveMs: parsePositiveIntMin(
|
|
3966
|
+
source.DSH_LARK_CHANNEL_KEEPALIVE_MS,
|
|
3967
|
+
DEFAULTS.channelKeepaliveMs,
|
|
3968
|
+
"DSH_LARK_CHANNEL_KEEPALIVE_MS"
|
|
3969
|
+
),
|
|
3970
|
+
channelHealthPollMs: parsePositiveIntMin(
|
|
3971
|
+
source.DSH_LARK_CHANNEL_HEALTH_POLL_MS,
|
|
3972
|
+
DEFAULTS.channelHealthPollMs,
|
|
3973
|
+
"DSH_LARK_CHANNEL_HEALTH_POLL_MS"
|
|
3933
3974
|
)
|
|
3934
3975
|
};
|
|
3935
3976
|
}
|
|
@@ -4169,17 +4210,36 @@ var ConfigStore = class {
|
|
|
4169
4210
|
};
|
|
4170
4211
|
|
|
4171
4212
|
// src/guardian/install.ts
|
|
4172
|
-
init_own_package();
|
|
4173
|
-
init_dsh_runtime();
|
|
4174
|
-
init_process();
|
|
4175
4213
|
import { mkdir as mkdir8, readFile as readFile8, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
4176
4214
|
import { existsSync as existsSync5 } from "fs";
|
|
4177
4215
|
import { homedir as homedir6 } from "os";
|
|
4178
|
-
import {
|
|
4216
|
+
import { dirname as dirname10, join as join11 } from "path";
|
|
4217
|
+
|
|
4218
|
+
// src/platform/path.ts
|
|
4219
|
+
import { delimiter, dirname as dirname8 } from "path";
|
|
4220
|
+
function sanitizeServicePath(nodeBin, inheritedPath = process.env.PATH) {
|
|
4221
|
+
const entries = [dirname8(nodeBin), ...inheritedPath?.split(delimiter) ?? []];
|
|
4222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4223
|
+
return entries.filter((entry) => {
|
|
4224
|
+
if (!entry) return false;
|
|
4225
|
+
const normalized = entry.replaceAll("\\", "/");
|
|
4226
|
+
if (normalized.includes("/node_modules/.bin") || /\/_npx(?:\/|$)/.test(normalized) || /\/guardian\/update-worker\/npm-cache(?:\/|$)/.test(normalized)) {
|
|
4227
|
+
return false;
|
|
4228
|
+
}
|
|
4229
|
+
if (seen.has(entry)) return false;
|
|
4230
|
+
seen.add(entry);
|
|
4231
|
+
return true;
|
|
4232
|
+
}).join(delimiter);
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
// src/guardian/install.ts
|
|
4236
|
+
init_own_package();
|
|
4237
|
+
init_dsh_runtime();
|
|
4238
|
+
init_process();
|
|
4179
4239
|
|
|
4180
4240
|
// src/guardian/state.ts
|
|
4181
4241
|
import { readFile as readFile7 } from "fs/promises";
|
|
4182
|
-
import { dirname as
|
|
4242
|
+
import { dirname as dirname9 } from "path";
|
|
4183
4243
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
4184
4244
|
var DEFAULT_DSH_PROFILE = "dsh-lark";
|
|
4185
4245
|
var DEFAULT_BRIDGE_PROFILE = "default";
|
|
@@ -4221,7 +4281,7 @@ async function loadGuardianState(file, fallback) {
|
|
|
4221
4281
|
}
|
|
4222
4282
|
async function saveGuardianState(file, state) {
|
|
4223
4283
|
const next = { ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4224
|
-
await mkdir7(
|
|
4284
|
+
await mkdir7(dirname9(file), { recursive: true });
|
|
4225
4285
|
await writeFileAtomic(file, `${JSON.stringify(next, null, 2)}
|
|
4226
4286
|
`, { mode: 384 });
|
|
4227
4287
|
}
|
|
@@ -4275,7 +4335,7 @@ function guardianServiceFilePath(platform, root) {
|
|
|
4275
4335
|
}
|
|
4276
4336
|
function systemdUnit(nodeBin, cliEntry, env = {}) {
|
|
4277
4337
|
const renderedEnv = {
|
|
4278
|
-
PATH: env.PATH ??
|
|
4338
|
+
PATH: env.PATH ?? dirname10(nodeBin),
|
|
4279
4339
|
...env
|
|
4280
4340
|
};
|
|
4281
4341
|
const envLines = Object.entries(renderedEnv).map(([key, value]) => `Environment=${key}=${value}`).join("\n");
|
|
@@ -4298,18 +4358,7 @@ function systemdUnit(nodeBin, cliEntry, env = {}) {
|
|
|
4298
4358
|
].join("\n");
|
|
4299
4359
|
}
|
|
4300
4360
|
function stableGuardianServicePath(nodeBin, inheritedPath = process.env.PATH) {
|
|
4301
|
-
|
|
4302
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4303
|
-
return entries.filter((entry) => {
|
|
4304
|
-
if (!entry) return false;
|
|
4305
|
-
const normalized = entry.replaceAll("\\", "/");
|
|
4306
|
-
if (normalized.includes("/node_modules/.bin") || /\/_npx(?:\/|$)/.test(normalized) || /\/guardian\/update-worker\/npm-cache(?:\/|$)/.test(normalized)) {
|
|
4307
|
-
return false;
|
|
4308
|
-
}
|
|
4309
|
-
if (seen.has(entry)) return false;
|
|
4310
|
-
seen.add(entry);
|
|
4311
|
-
return true;
|
|
4312
|
-
}).join(delimiter);
|
|
4361
|
+
return sanitizeServicePath(nodeBin, inheritedPath);
|
|
4313
4362
|
}
|
|
4314
4363
|
function launchdPlist(nodeBin, cliEntry, label2 = `io.dsh-lark.${GUARDIAN_LABEL}`, logPath = "/tmp/dsh-lark-guardian.log") {
|
|
4315
4364
|
return [
|
|
@@ -4477,15 +4526,6 @@ function guardianStatePath(root) {
|
|
|
4477
4526
|
return join11(root, ".dsh-lark", "guardian.json");
|
|
4478
4527
|
}
|
|
4479
4528
|
|
|
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
4529
|
// src/guardian/heartbeat.ts
|
|
4490
4530
|
import { readFile as readFile9 } from "fs/promises";
|
|
4491
4531
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
@@ -4496,15 +4536,25 @@ async function readHeartbeat(file) {
|
|
|
4496
4536
|
if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string" || typeof parsed.ts !== "number") {
|
|
4497
4537
|
return void 0;
|
|
4498
4538
|
}
|
|
4499
|
-
|
|
4539
|
+
const payload = {
|
|
4500
4540
|
pid: parsed.pid,
|
|
4501
4541
|
startedAt: parsed.startedAt,
|
|
4502
4542
|
ts: parsed.ts
|
|
4503
4543
|
};
|
|
4544
|
+
const channel = parsed.channel;
|
|
4545
|
+
if (isChannelSnapshot(channel)) {
|
|
4546
|
+
payload.channel = channel;
|
|
4547
|
+
}
|
|
4548
|
+
return payload;
|
|
4504
4549
|
} catch {
|
|
4505
4550
|
return void 0;
|
|
4506
4551
|
}
|
|
4507
4552
|
}
|
|
4553
|
+
function isChannelSnapshot(value) {
|
|
4554
|
+
if (!value || typeof value !== "object") return false;
|
|
4555
|
+
const snapshot = value;
|
|
4556
|
+
return typeof snapshot.state === "string" && typeof snapshot.ready === "boolean";
|
|
4557
|
+
}
|
|
4508
4558
|
function heartbeatAgeMs(payload, now = Date.now()) {
|
|
4509
4559
|
return Math.max(0, now - payload.ts);
|
|
4510
4560
|
}
|
|
@@ -4512,12 +4562,34 @@ function isHeartbeatFresh(payload, maxAgeMs, now = Date.now()) {
|
|
|
4512
4562
|
if (payload === void 0) return false;
|
|
4513
4563
|
return heartbeatAgeMs(payload, now) < maxAgeMs;
|
|
4514
4564
|
}
|
|
4515
|
-
function
|
|
4565
|
+
function channelHealthLabel(channel) {
|
|
4566
|
+
if (channel === void 0) return "\u672A\u4E0A\u62A5";
|
|
4567
|
+
if (channel.ready) {
|
|
4568
|
+
return `ready (generation ${channel.generation ?? "-"})`;
|
|
4569
|
+
}
|
|
4570
|
+
switch (channel.state) {
|
|
4571
|
+
case "connecting":
|
|
4572
|
+
return "connecting";
|
|
4573
|
+
case "reconnecting":
|
|
4574
|
+
return `reconnecting (attempt ${channel.reconnectAttempts ?? "-"})`;
|
|
4575
|
+
case "failed":
|
|
4576
|
+
return "failed";
|
|
4577
|
+
case "stopped":
|
|
4578
|
+
return "stopped";
|
|
4579
|
+
default:
|
|
4580
|
+
return channel.state;
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS, getChannelHealth) {
|
|
4516
4584
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4517
4585
|
let stopped = false;
|
|
4518
4586
|
const beat = async () => {
|
|
4519
4587
|
if (stopped) return;
|
|
4520
4588
|
const payload = { pid, startedAt, ts: Date.now() };
|
|
4589
|
+
const channel = getChannelHealth?.();
|
|
4590
|
+
if (channel !== void 0) {
|
|
4591
|
+
payload.channel = channel;
|
|
4592
|
+
}
|
|
4521
4593
|
try {
|
|
4522
4594
|
await writeFileAtomic(file, `${JSON.stringify(payload)}
|
|
4523
4595
|
`, {
|
|
@@ -4540,6 +4612,13 @@ function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS) {
|
|
|
4540
4612
|
}
|
|
4541
4613
|
|
|
4542
4614
|
// src/upgrade/detect.ts
|
|
4615
|
+
init_own_package();
|
|
4616
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
4617
|
+
import { join as join12 } from "path";
|
|
4618
|
+
import { homedir as homedir7 } from "os";
|
|
4619
|
+
init_dsh_runtime();
|
|
4620
|
+
init_process();
|
|
4621
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4543
4622
|
async function readInstalledPackage(dshHome, profile, packageName) {
|
|
4544
4623
|
const manifest = join12(
|
|
4545
4624
|
dshHome,
|
|
@@ -4616,7 +4695,7 @@ async function detectUpgradeState(options = {}) {
|
|
|
4616
4695
|
|
|
4617
4696
|
// src/upgrade/state.ts
|
|
4618
4697
|
import { mkdir as mkdir9, readFile as readFile11 } from "fs/promises";
|
|
4619
|
-
import { dirname as
|
|
4698
|
+
import { dirname as dirname11, join as join13 } from "path";
|
|
4620
4699
|
function upgradeStatePath(root) {
|
|
4621
4700
|
return join13(root, "upgrade-state.json");
|
|
4622
4701
|
}
|
|
@@ -4632,7 +4711,7 @@ async function loadUpgradeState(file) {
|
|
|
4632
4711
|
}
|
|
4633
4712
|
}
|
|
4634
4713
|
async function saveUpgradeState(file, state) {
|
|
4635
|
-
await mkdir9(
|
|
4714
|
+
await mkdir9(dirname11(file), { recursive: true });
|
|
4636
4715
|
await writeFileAtomic(file, JSON.stringify(state, null, 2));
|
|
4637
4716
|
}
|
|
4638
4717
|
|
|
@@ -4715,13 +4794,13 @@ function packageSpecFor(name, version) {
|
|
|
4715
4794
|
|
|
4716
4795
|
// src/service/manager.ts
|
|
4717
4796
|
import { mkdir as mkdir14, readFile as readFile14, rename as rename2, rm as rm7, stat as stat5 } from "fs/promises";
|
|
4718
|
-
import { dirname as
|
|
4797
|
+
import { dirname as dirname14, join as join19 } from "path";
|
|
4719
4798
|
import { homedir as homedir11 } from "os";
|
|
4720
4799
|
init_dsh_runtime();
|
|
4721
4800
|
|
|
4722
4801
|
// src/service/command.ts
|
|
4723
4802
|
import { execFile } from "child_process";
|
|
4724
|
-
import { basename as basename3, dirname as
|
|
4803
|
+
import { basename as basename3, dirname as dirname12, join as join14 } from "path";
|
|
4725
4804
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4726
4805
|
var runCommand = (command, args, options) => new Promise((resolve6) => {
|
|
4727
4806
|
execFile(
|
|
@@ -4747,7 +4826,7 @@ function resolveCliJsPath(metaUrl = import.meta.url) {
|
|
|
4747
4826
|
const current = fileURLToPath2(metaUrl);
|
|
4748
4827
|
const name = basename3(current);
|
|
4749
4828
|
if (name === "cli.js" || name === "cli.mjs" || name === "cli.cjs") return current;
|
|
4750
|
-
return join14(
|
|
4829
|
+
return join14(dirname12(current), "..", "dist", "cli.js");
|
|
4751
4830
|
}
|
|
4752
4831
|
function slugify(value) {
|
|
4753
4832
|
const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -4790,6 +4869,9 @@ function snapshotServiceEnv(source = process.env, extraKeys = [], inherited = {}
|
|
|
4790
4869
|
}
|
|
4791
4870
|
}
|
|
4792
4871
|
}
|
|
4872
|
+
if (typeof env.PATH === "string" && env.PATH.length > 0) {
|
|
4873
|
+
env.PATH = sanitizeServicePath(process.execPath, env.PATH);
|
|
4874
|
+
}
|
|
4793
4875
|
return env;
|
|
4794
4876
|
}
|
|
4795
4877
|
function formatEnvFile(env) {
|
|
@@ -5375,7 +5457,7 @@ var PortableServiceController = class {
|
|
|
5375
5457
|
|
|
5376
5458
|
// src/service/windows-task.ts
|
|
5377
5459
|
import { mkdir as mkdir13, writeFile as writeFile7 } from "fs/promises";
|
|
5378
|
-
import { dirname as
|
|
5460
|
+
import { dirname as dirname13, join as join18 } from "path";
|
|
5379
5461
|
function psQuote(value) {
|
|
5380
5462
|
return `'${value.replace(/'/g, "''")}'`;
|
|
5381
5463
|
}
|
|
@@ -5386,7 +5468,7 @@ function buildScheduledTaskScript(action, spec) {
|
|
|
5386
5468
|
`$arguments = ${psQuote(spec.commandArgs.map((arg) => `"${arg.replace(/"/g, '\\"')}"`).join(" "))}`,
|
|
5387
5469
|
`$log = ${psQuote(spec.logFile)}`,
|
|
5388
5470
|
`$profile = ${psQuote(spec.profile)}`,
|
|
5389
|
-
`$workDir = ${psQuote(
|
|
5471
|
+
`$workDir = ${psQuote(dirname13(spec.commandPath))}`
|
|
5390
5472
|
].join("\n");
|
|
5391
5473
|
const install = [
|
|
5392
5474
|
`$inner = '"' + $command + '" ' + $arguments + ' >> "' + $log + '" 2>&1'`,
|
|
@@ -5605,7 +5687,7 @@ var ServiceManager = class {
|
|
|
5605
5687
|
}
|
|
5606
5688
|
async withLifecycleLock(operation) {
|
|
5607
5689
|
const lockDir = this.paths.serviceLockDir(this.profile);
|
|
5608
|
-
await mkdir14(
|
|
5690
|
+
await mkdir14(dirname14(lockDir), { recursive: true });
|
|
5609
5691
|
try {
|
|
5610
5692
|
await mkdir14(lockDir);
|
|
5611
5693
|
} catch (error) {
|
|
@@ -5645,11 +5727,39 @@ var ServiceManager = class {
|
|
|
5645
5727
|
if (serviceStatus.state === "running") return;
|
|
5646
5728
|
const process2 = await this.findProcess(this.profile);
|
|
5647
5729
|
if (process2) {
|
|
5730
|
+
if (await this.isGuardianSpawned(process2)) {
|
|
5731
|
+
await this.stopGuardianSpawnedProcess(process2.pid);
|
|
5732
|
+
return;
|
|
5733
|
+
}
|
|
5648
5734
|
throw new Error(
|
|
5649
5735
|
`\u68C0\u6D4B\u5230\u672A\u53D7\u7BA1\u7684 dsh --profile ${this.profile}\uFF08pid ${process2.pid}\uFF09\u3002\u8BF7\u5148\u5728\u539F\u7EC8\u7AEF\u505C\u6B62\u5B83\uFF0C\u518D\u91CD\u8BD5\u3002`
|
|
5650
5736
|
);
|
|
5651
5737
|
}
|
|
5652
5738
|
}
|
|
5739
|
+
/** True when the profile process was spawned by the resident guardian. */
|
|
5740
|
+
async isGuardianSpawned(process2) {
|
|
5741
|
+
const ppid = process2.ppid;
|
|
5742
|
+
if (!ppid) return false;
|
|
5743
|
+
const parent = (await listProcesses()).find((entry) => entry.pid === ppid);
|
|
5744
|
+
return parent !== void 0 && matchGuardianProcess(parent.cmdline);
|
|
5745
|
+
}
|
|
5746
|
+
/** Best-effort stop of a guardian-spawned profile process (grace, then kill). */
|
|
5747
|
+
async stopGuardianSpawnedProcess(pid) {
|
|
5748
|
+
try {
|
|
5749
|
+
process.kill(pid, "SIGTERM");
|
|
5750
|
+
} catch {
|
|
5751
|
+
return;
|
|
5752
|
+
}
|
|
5753
|
+
const deadline = Date.now() + 5e3;
|
|
5754
|
+
while (Date.now() < deadline && isPidAlive2(pid)) {
|
|
5755
|
+
await new Promise((resolve6) => setTimeout(resolve6, 100));
|
|
5756
|
+
}
|
|
5757
|
+
if (!isPidAlive2(pid)) return;
|
|
5758
|
+
try {
|
|
5759
|
+
process.kill(pid, "SIGKILL");
|
|
5760
|
+
} catch {
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5653
5763
|
async buildSpec(requireProfile = false) {
|
|
5654
5764
|
const previous = await this.readMetadata();
|
|
5655
5765
|
const dshBin = this.dshBinOverride ?? discoverDshBin(this.home, this.sourceEnv) ?? previous?.dshBin;
|
|
@@ -5746,8 +5856,8 @@ var ServiceManager = class {
|
|
|
5746
5856
|
}
|
|
5747
5857
|
}
|
|
5748
5858
|
async prepareFiles(spec, platform) {
|
|
5749
|
-
await mkdir14(
|
|
5750
|
-
await mkdir14(
|
|
5859
|
+
await mkdir14(dirname14(spec.logFile), { recursive: true });
|
|
5860
|
+
await mkdir14(dirname14(spec.envFile), { recursive: true });
|
|
5751
5861
|
await writeServiceEnv(spec.envFile, spec.env);
|
|
5752
5862
|
if (platform === "win32-task") {
|
|
5753
5863
|
try {
|
|
@@ -5918,6 +6028,18 @@ async function runDoctorChecks(options) {
|
|
|
5918
6028
|
} catch (error) {
|
|
5919
6029
|
lines.push(`service: \u26A0\uFE0F \u72B6\u6001\u68C0\u67E5\u5931\u8D25\uFF08${error instanceof Error ? error.message : String(error)}\uFF09`);
|
|
5920
6030
|
}
|
|
6031
|
+
try {
|
|
6032
|
+
const heartbeat = await readHeartbeat(
|
|
6033
|
+
paths.profilePath(env.guardianBridgeProfile, "guardian", "heartbeat.json")
|
|
6034
|
+
);
|
|
6035
|
+
lines.push(`channel: ${channelHealthLabel(heartbeat?.channel)}`);
|
|
6036
|
+
if (heartbeat?.channel && !heartbeat.channel.ready) {
|
|
6037
|
+
lines.push(
|
|
6038
|
+
"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"
|
|
6039
|
+
);
|
|
6040
|
+
}
|
|
6041
|
+
} catch {
|
|
6042
|
+
}
|
|
5921
6043
|
try {
|
|
5922
6044
|
const state = await loadUpgradeState(upgradeStatePath(env.home));
|
|
5923
6045
|
if (state?.lastUpgrade.pendingRestart === true) {
|
|
@@ -7057,6 +7179,106 @@ var LanguagePolicyStore = class {
|
|
|
7057
7179
|
// src/bridge/channel.ts
|
|
7058
7180
|
import { createLarkChannel as createLarkChannel2 } from "@larksuite/channel";
|
|
7059
7181
|
|
|
7182
|
+
// src/bridge/channel-health.ts
|
|
7183
|
+
function mapState(state) {
|
|
7184
|
+
switch (state) {
|
|
7185
|
+
case "connected":
|
|
7186
|
+
return "ready";
|
|
7187
|
+
case "reconnecting":
|
|
7188
|
+
return "reconnecting";
|
|
7189
|
+
case "failed":
|
|
7190
|
+
return "failed";
|
|
7191
|
+
case "connecting":
|
|
7192
|
+
return "connecting";
|
|
7193
|
+
case "idle":
|
|
7194
|
+
default:
|
|
7195
|
+
return "connecting";
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
7198
|
+
var ChannelHealthMonitor = class {
|
|
7199
|
+
channel;
|
|
7200
|
+
pollMs;
|
|
7201
|
+
onUpdate;
|
|
7202
|
+
generation = 0;
|
|
7203
|
+
connectedAt;
|
|
7204
|
+
lastInboundAt;
|
|
7205
|
+
lastReconnectAt;
|
|
7206
|
+
lastError;
|
|
7207
|
+
observedState = "connecting";
|
|
7208
|
+
reconnectAttempts = 0;
|
|
7209
|
+
at = 0;
|
|
7210
|
+
pollTimer;
|
|
7211
|
+
constructor(channel, options = {}) {
|
|
7212
|
+
this.channel = channel;
|
|
7213
|
+
this.pollMs = options.pollMs ?? 5e3;
|
|
7214
|
+
this.onUpdate = options.onUpdate;
|
|
7215
|
+
}
|
|
7216
|
+
/** Latest snapshot (always a copy; safe to persist). */
|
|
7217
|
+
snapshot() {
|
|
7218
|
+
return {
|
|
7219
|
+
state: this.observedState,
|
|
7220
|
+
ready: this.observedState === "ready",
|
|
7221
|
+
generation: this.generation,
|
|
7222
|
+
...this.connectedAt !== void 0 ? { connectedAt: this.connectedAt } : {},
|
|
7223
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
7224
|
+
...this.lastInboundAt !== void 0 ? { lastInboundAt: this.lastInboundAt } : {},
|
|
7225
|
+
...this.lastReconnectAt !== void 0 ? { lastReconnectAt: this.lastReconnectAt } : {},
|
|
7226
|
+
...this.lastError !== void 0 ? { lastError: this.lastError } : {},
|
|
7227
|
+
at: this.at
|
|
7228
|
+
};
|
|
7229
|
+
}
|
|
7230
|
+
/** Bind transport hooks from the bridge event subscription. */
|
|
7231
|
+
observeMessage() {
|
|
7232
|
+
this.lastInboundAt = Date.now();
|
|
7233
|
+
this.markChanged();
|
|
7234
|
+
}
|
|
7235
|
+
observeReconnecting() {
|
|
7236
|
+
this.observedState = "reconnecting";
|
|
7237
|
+
this.markChanged();
|
|
7238
|
+
}
|
|
7239
|
+
observeReconnected() {
|
|
7240
|
+
this.generation += 1;
|
|
7241
|
+
const now = Date.now();
|
|
7242
|
+
this.connectedAt = now;
|
|
7243
|
+
this.lastReconnectAt = now;
|
|
7244
|
+
this.observedState = "ready";
|
|
7245
|
+
this.markChanged();
|
|
7246
|
+
}
|
|
7247
|
+
observeError(error) {
|
|
7248
|
+
this.lastError = error instanceof Error ? error.message : String(error);
|
|
7249
|
+
if (this.observedState !== "reconnecting" && this.observedState !== "failed") {
|
|
7250
|
+
this.observedState = "failed";
|
|
7251
|
+
}
|
|
7252
|
+
this.markChanged();
|
|
7253
|
+
}
|
|
7254
|
+
/** Poll the SDK's connection-status snapshot. */
|
|
7255
|
+
start() {
|
|
7256
|
+
if (this.pollTimer) return;
|
|
7257
|
+
this.refresh();
|
|
7258
|
+
this.pollTimer = setInterval(() => this.refresh(), this.pollMs);
|
|
7259
|
+
this.pollTimer.unref?.();
|
|
7260
|
+
}
|
|
7261
|
+
stop() {
|
|
7262
|
+
if (this.pollTimer) {
|
|
7263
|
+
clearInterval(this.pollTimer);
|
|
7264
|
+
this.pollTimer = void 0;
|
|
7265
|
+
}
|
|
7266
|
+
this.observedState = "stopped";
|
|
7267
|
+
this.markChanged();
|
|
7268
|
+
}
|
|
7269
|
+
refresh() {
|
|
7270
|
+
const status = this.channel.getConnectionStatus?.();
|
|
7271
|
+
this.observedState = mapState(status?.state);
|
|
7272
|
+
this.reconnectAttempts = status?.reconnectAttempts ?? this.reconnectAttempts;
|
|
7273
|
+
this.markChanged();
|
|
7274
|
+
}
|
|
7275
|
+
markChanged() {
|
|
7276
|
+
const now = Date.now();
|
|
7277
|
+
this.at = now;
|
|
7278
|
+
this.onUpdate?.(this.snapshot());
|
|
7279
|
+
}
|
|
7280
|
+
};
|
|
7281
|
+
|
|
7060
7282
|
// src/commands/index.ts
|
|
7061
7283
|
import { stat as stat8 } from "fs/promises";
|
|
7062
7284
|
import { homedir as homedir14 } from "os";
|
|
@@ -10299,7 +10521,7 @@ async function handleConfigHubAction(action, ctx, value = {}) {
|
|
|
10299
10521
|
}
|
|
10300
10522
|
|
|
10301
10523
|
// src/commands/archive.ts
|
|
10302
|
-
import { dirname as
|
|
10524
|
+
import { dirname as dirname15 } from "path";
|
|
10303
10525
|
|
|
10304
10526
|
// src/media/outbound-files.ts
|
|
10305
10527
|
import { constants } from "fs";
|
|
@@ -10495,7 +10717,7 @@ async function sendArchiveFiles(ctx, record, destination = {
|
|
|
10495
10717
|
[record.jsonlPath, `${record.archiveId}.jsonl`]
|
|
10496
10718
|
]) {
|
|
10497
10719
|
try {
|
|
10498
|
-
const prepared = await prepareOutboundFile({ path, baseDir:
|
|
10720
|
+
const prepared = await prepareOutboundFile({ path, baseDir: dirname15(path), allowedRoots: [dirname15(path)], fileName });
|
|
10499
10721
|
await ctx.channel.sendFile(destination.chatId, prepared.fileName, prepared.content, {
|
|
10500
10722
|
...destination.messageId ? { replyTo: destination.messageId } : {},
|
|
10501
10723
|
...destination.threadId ? { threadId: destination.threadId } : {}
|
|
@@ -13751,6 +13973,8 @@ The value is written only by the local bridge and is not sent to the agent.` },
|
|
|
13751
13973
|
}
|
|
13752
13974
|
|
|
13753
13975
|
// src/bridge/channel.ts
|
|
13976
|
+
var DEFAULT_CHANNEL_PING_TIMEOUT_SEC = 30;
|
|
13977
|
+
var DEFAULT_CHANNEL_KEEPALIVE_MS = 15e3;
|
|
13754
13978
|
async function startChannel(deps) {
|
|
13755
13979
|
const channel = (deps.createChannel ?? createLarkChannel2)({
|
|
13756
13980
|
appId: deps.appId,
|
|
@@ -13777,7 +14001,26 @@ async function startChannel(deps) {
|
|
|
13777
14001
|
resolveChatMode: true,
|
|
13778
14002
|
handshakeTimeoutMs: 8e3,
|
|
13779
14003
|
httpTimeoutMs: 3e4,
|
|
13780
|
-
respectProxyEnv: true
|
|
14004
|
+
respectProxyEnv: true,
|
|
14005
|
+
// Issue #108: detect a half-open WebSocket (TCP ESTABLISHED but Feishu no
|
|
14006
|
+
// longer delivering). `pingTimeout` force-reconnects when no inbound frame
|
|
14007
|
+
// arrives after the last ping; the app-level `keepalive` probes and
|
|
14008
|
+
// force-reconnects, and `onUnrecoverable` fires when even that fails so the
|
|
14009
|
+
// engine can exit and let the managed service / guardian restart it.
|
|
14010
|
+
wsConfig: { pingTimeout: deps.channelPingTimeoutSec ?? DEFAULT_CHANNEL_PING_TIMEOUT_SEC },
|
|
14011
|
+
keepalive: {
|
|
14012
|
+
enabled: deps.channelKeepalive ?? true,
|
|
14013
|
+
intervalMs: deps.channelKeepaliveMs ?? DEFAULT_CHANNEL_KEEPALIVE_MS,
|
|
14014
|
+
onUnrecoverable: (error) => {
|
|
14015
|
+
log.fail("channel", "unrecoverable", {
|
|
14016
|
+
error: error instanceof Error ? error.message : String(error)
|
|
14017
|
+
});
|
|
14018
|
+
deps.onChannelUnrecoverable?.(error);
|
|
14019
|
+
}
|
|
14020
|
+
}
|
|
14021
|
+
});
|
|
14022
|
+
const channelHealth = new ChannelHealthMonitor(channel, {
|
|
14023
|
+
...deps.channelHealthPollMs !== void 0 ? { pollMs: deps.channelHealthPollMs } : {}
|
|
13781
14024
|
});
|
|
13782
14025
|
const streaming = adaptLarkChannel(channel);
|
|
13783
14026
|
const commandChannel = streaming;
|
|
@@ -13818,6 +14061,7 @@ async function startChannel(deps) {
|
|
|
13818
14061
|
const isolationStore = deps.isolationStore ?? EMPTY_ISOLATION_STORE;
|
|
13819
14062
|
let groupPoller;
|
|
13820
14063
|
const processMessage = async (msg, alreadyClaimed = false) => {
|
|
14064
|
+
channelHealth.observeMessage();
|
|
13821
14065
|
if (groupPoller && !alreadyClaimed && !groupPoller.claim(msg.messageId)) return;
|
|
13822
14066
|
const chatMode = msg.chatMode ?? msg.chatType;
|
|
13823
14067
|
const botSender = msg.senderType === "bot";
|
|
@@ -14394,21 +14638,25 @@ ${msg.content}`,
|
|
|
14394
14638
|
},
|
|
14395
14639
|
reconnecting: () => {
|
|
14396
14640
|
log.warn("channel", "reconnecting", {});
|
|
14641
|
+
channelHealth.observeReconnecting();
|
|
14397
14642
|
void reconnectNotifier.reconnecting().catch((error) => {
|
|
14398
14643
|
log.fail("channel-reconnect-notice", error);
|
|
14399
14644
|
});
|
|
14400
14645
|
},
|
|
14401
14646
|
reconnected: () => {
|
|
14402
14647
|
log.info("channel", "reconnected", {});
|
|
14648
|
+
channelHealth.observeReconnected();
|
|
14403
14649
|
void reconnectNotifier.reconnected().catch((error) => {
|
|
14404
14650
|
log.fail("channel-reconnect-notice", error);
|
|
14405
14651
|
});
|
|
14406
14652
|
},
|
|
14407
14653
|
error: (error) => {
|
|
14408
14654
|
log.fail("channel", error);
|
|
14655
|
+
channelHealth.observeError(error);
|
|
14409
14656
|
}
|
|
14410
14657
|
});
|
|
14411
14658
|
await channel.connect();
|
|
14659
|
+
channelHealth.start();
|
|
14412
14660
|
if (sessionProjectionBridge) {
|
|
14413
14661
|
void sessionProjectionBridge.start().catch((error) => {
|
|
14414
14662
|
log.fail("session-projection", error, { step: "start" });
|
|
@@ -14425,8 +14673,10 @@ ${msg.content}`,
|
|
|
14425
14673
|
}
|
|
14426
14674
|
return {
|
|
14427
14675
|
channel,
|
|
14676
|
+
channelHealth: () => channelHealth.snapshot(),
|
|
14428
14677
|
disconnect: async () => {
|
|
14429
14678
|
await groupPoller?.stop();
|
|
14679
|
+
channelHealth.stop();
|
|
14430
14680
|
sessionProjection?.close();
|
|
14431
14681
|
await sessionProjectionBridge?.close();
|
|
14432
14682
|
await channel.disconnect();
|
|
@@ -16475,7 +16725,7 @@ async function safeRemove(path) {
|
|
|
16475
16725
|
import { execFile as execFile3 } from "child_process";
|
|
16476
16726
|
import { createHash as createHash2, randomBytes as randomBytes4 } from "crypto";
|
|
16477
16727
|
import { access as access2, copyFile, mkdir as mkdir18, realpath as realpath2 } from "fs/promises";
|
|
16478
|
-
import { dirname as
|
|
16728
|
+
import { dirname as dirname16, join as join25 } from "path";
|
|
16479
16729
|
import { promisify as promisify2 } from "util";
|
|
16480
16730
|
var execFileAsync2 = promisify2(execFile3);
|
|
16481
16731
|
async function defaultRunGit2(args, cwd) {
|
|
@@ -16494,7 +16744,7 @@ async function exists2(path) {
|
|
|
16494
16744
|
}
|
|
16495
16745
|
}
|
|
16496
16746
|
async function defaultCopyRulesFile(source, target) {
|
|
16497
|
-
await mkdir18(
|
|
16747
|
+
await mkdir18(dirname16(target), { recursive: true });
|
|
16498
16748
|
await copyFile(source, target);
|
|
16499
16749
|
}
|
|
16500
16750
|
function slugify2(scope) {
|
|
@@ -16712,7 +16962,7 @@ var UpdateNotifier = class {
|
|
|
16712
16962
|
|
|
16713
16963
|
// src/bot/fleet-store.ts
|
|
16714
16964
|
import { mkdir as mkdir19, readFile as readFile28 } from "fs/promises";
|
|
16715
|
-
import { dirname as
|
|
16965
|
+
import { dirname as dirname17 } from "path";
|
|
16716
16966
|
function validBotInstanceName(name) {
|
|
16717
16967
|
return /^[a-z][a-z0-9-]{0,31}$/u.test(name);
|
|
16718
16968
|
}
|
|
@@ -16833,7 +17083,7 @@ var BotFleetStore = class {
|
|
|
16833
17083
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
16834
17084
|
}
|
|
16835
17085
|
async persist() {
|
|
16836
|
-
await mkdir19(
|
|
17086
|
+
await mkdir19(dirname17(this.path), { recursive: true });
|
|
16837
17087
|
await writeFileAtomic(this.path, `${JSON.stringify(this.data, null, 2)}
|
|
16838
17088
|
`, { mode: 384 });
|
|
16839
17089
|
}
|
|
@@ -16853,7 +17103,7 @@ function isFleetEntry(value) {
|
|
|
16853
17103
|
|
|
16854
17104
|
// src/bot/handoff-guard.ts
|
|
16855
17105
|
import { mkdir as mkdir20, readFile as readFile29 } from "fs/promises";
|
|
16856
|
-
import { dirname as
|
|
17106
|
+
import { dirname as dirname18 } from "path";
|
|
16857
17107
|
var BotHandoffGuard = class {
|
|
16858
17108
|
constructor(path) {
|
|
16859
17109
|
this.path = path;
|
|
@@ -16901,7 +17151,7 @@ var BotHandoffGuard = class {
|
|
|
16901
17151
|
}
|
|
16902
17152
|
}
|
|
16903
17153
|
async write(data) {
|
|
16904
|
-
await mkdir20(
|
|
17154
|
+
await mkdir20(dirname18(this.path), { recursive: true });
|
|
16905
17155
|
await writeFileAtomic(this.path, `${JSON.stringify(data, null, 2)}
|
|
16906
17156
|
`, { mode: 384 });
|
|
16907
17157
|
}
|
|
@@ -17684,7 +17934,7 @@ init_own_package();
|
|
|
17684
17934
|
import { createHash as createHash3, randomUUID as randomUUID13 } from "crypto";
|
|
17685
17935
|
import { spawn as spawn7 } from "child_process";
|
|
17686
17936
|
import { chmod, mkdir as mkdir21, readFile as readFile31, rm as rm10 } from "fs/promises";
|
|
17687
|
-
import { dirname as
|
|
17937
|
+
import { dirname as dirname19, join as join26 } from "path";
|
|
17688
17938
|
init_own_package();
|
|
17689
17939
|
init_process();
|
|
17690
17940
|
function guardianUpdateFailureHint(code) {
|
|
@@ -17721,7 +17971,7 @@ async function loadState(file) {
|
|
|
17721
17971
|
}
|
|
17722
17972
|
}
|
|
17723
17973
|
async function saveState(file, state) {
|
|
17724
|
-
await mkdir21(
|
|
17974
|
+
await mkdir21(dirname19(file), { recursive: true });
|
|
17725
17975
|
await writeFileAtomic(file, `${JSON.stringify(state, null, 2)}
|
|
17726
17976
|
`, { mode: 384 });
|
|
17727
17977
|
}
|
|
@@ -17796,7 +18046,7 @@ async function runGuardianUpdateWorker(request, options = {}) {
|
|
|
17796
18046
|
if (delayMs > 0) await new Promise((resolve6) => setTimeout(resolve6, delayMs));
|
|
17797
18047
|
const spec = `${state.packageName}@${state.targetVersion}`;
|
|
17798
18048
|
const run = options.run ?? captureOutput;
|
|
17799
|
-
const workerRoot = join26(
|
|
18049
|
+
const workerRoot = join26(dirname19(request.stateFile), "update-worker");
|
|
17800
18050
|
const cacheRoot = join26(workerRoot, "npm-cache");
|
|
17801
18051
|
const cacheDir = join26(
|
|
17802
18052
|
cacheRoot,
|
|
@@ -18586,7 +18836,18 @@ async function startBridgeEngine(options) {
|
|
|
18586
18836
|
},
|
|
18587
18837
|
allowedUsers: activeProfile.access.allowedUsers,
|
|
18588
18838
|
allowedChats: activeProfile.access.allowedChats,
|
|
18589
|
-
...options.createChannel ? { createChannel: options.createChannel } : {}
|
|
18839
|
+
...options.createChannel ? { createChannel: options.createChannel } : {},
|
|
18840
|
+
channelPingTimeoutSec: env.channelPingTimeoutSec,
|
|
18841
|
+
channelKeepalive: env.channelKeepalive,
|
|
18842
|
+
channelKeepaliveMs: env.channelKeepaliveMs,
|
|
18843
|
+
channelHealthPollMs: env.channelHealthPollMs,
|
|
18844
|
+
onChannelUnrecoverable: (error) => {
|
|
18845
|
+
log.fail("engine", "channel-unrecoverable", {
|
|
18846
|
+
error: error instanceof Error ? error.message : String(error)
|
|
18847
|
+
});
|
|
18848
|
+
process.exitCode = 1;
|
|
18849
|
+
setTimeout(() => process.exit(1), 300);
|
|
18850
|
+
}
|
|
18590
18851
|
};
|
|
18591
18852
|
if (activeProfile.preferences.stopGraceMs !== void 0) {
|
|
18592
18853
|
channelInput.stopGraceMs = activeProfile.preferences.stopGraceMs;
|
|
@@ -18651,7 +18912,8 @@ async function startBridgeEngine(options) {
|
|
|
18651
18912
|
const heartbeat = startHeartbeat(
|
|
18652
18913
|
paths.profilePath(profileName, "guardian", "heartbeat.json"),
|
|
18653
18914
|
process.pid,
|
|
18654
|
-
env.heartbeatMs
|
|
18915
|
+
env.heartbeatMs,
|
|
18916
|
+
() => bridge.channelHealth?.()
|
|
18655
18917
|
);
|
|
18656
18918
|
await updateHandoff.reconcile(currentVersion());
|
|
18657
18919
|
void deliverUpdateResult().catch((error) => log.fail("upgrade", error, { step: "deliver-result" }));
|
|
@@ -19730,6 +19992,7 @@ var GuardianService = class {
|
|
|
19730
19992
|
dshUp: this.dshUp,
|
|
19731
19993
|
heartbeatAgeMs: this.lastHeartbeatAgeMs,
|
|
19732
19994
|
channelConnected: this.channel !== void 0,
|
|
19995
|
+
channelState: this.lastHeartbeatChannel?.state,
|
|
19733
19996
|
safeEngine: this.safeEngine?.kind,
|
|
19734
19997
|
safeRuns: this.safeRuns.size,
|
|
19735
19998
|
pid: process.pid,
|
|
@@ -19740,6 +20003,9 @@ var GuardianService = class {
|
|
|
19740
20003
|
}
|
|
19741
20004
|
dshUp = false;
|
|
19742
20005
|
lastHeartbeatAgeMs;
|
|
20006
|
+
lastHeartbeatChannel;
|
|
20007
|
+
/** When the engine reported a non-ready channel while its heartbeat was fresh. */
|
|
20008
|
+
channelUnhealthySinceMs;
|
|
19743
20009
|
log() {
|
|
19744
20010
|
return this.options.logger ?? log;
|
|
19745
20011
|
}
|
|
@@ -19774,7 +20040,24 @@ var GuardianService = class {
|
|
|
19774
20040
|
const now = (this.options.now ?? Date.now)();
|
|
19775
20041
|
const heartbeatFresh = isHeartbeatFresh(heartbeat, this.options.staleMs, now);
|
|
19776
20042
|
this.lastHeartbeatAgeMs = heartbeat ? heartbeatAgeMs(heartbeat, now) : void 0;
|
|
20043
|
+
this.lastHeartbeatChannel = heartbeat?.channel;
|
|
19777
20044
|
if (heartbeatFresh) this.lastHeartbeatFreshAt = now;
|
|
20045
|
+
const channelSnapshot = this.lastHeartbeatChannel;
|
|
20046
|
+
const channelUnhealthy = heartbeatFresh && channelSnapshot !== void 0 && channelSnapshot.ready === false;
|
|
20047
|
+
if (channelUnhealthy) {
|
|
20048
|
+
if (this.channelUnhealthySinceMs === void 0) {
|
|
20049
|
+
this.channelUnhealthySinceMs = now;
|
|
20050
|
+
this.log().warn("guardian", "channel-unhealthy", {
|
|
20051
|
+
dshProfile: this.state.dshProfile,
|
|
20052
|
+
state: channelSnapshot.state,
|
|
20053
|
+
generation: channelSnapshot.generation,
|
|
20054
|
+
reconnectAttempts: channelSnapshot.reconnectAttempts,
|
|
20055
|
+
lastError: channelSnapshot.lastError
|
|
20056
|
+
});
|
|
20057
|
+
}
|
|
20058
|
+
} else {
|
|
20059
|
+
this.channelUnhealthySinceMs = void 0;
|
|
20060
|
+
}
|
|
19778
20061
|
const processFound = await (this.options.findProcess ?? findProfileProcess)(
|
|
19779
20062
|
this.state.dshProfile
|
|
19780
20063
|
);
|
|
@@ -20822,6 +21105,7 @@ async function statusGuardianCommand(options = {}, deps = {}) {
|
|
|
20822
21105
|
`\u6865\u63A5 profile\uFF1A${state.bridgeProfile}`,
|
|
20823
21106
|
`dsh \u662F\u5426\u5728\u7EBF\uFF1A${up ? "\u662F" : "\u5426"}${processFound ? `\uFF08pid ${processFound.pid}\uFF09` : ""}`,
|
|
20824
21107
|
`\u5FC3\u8DF3\u9F84\uFF1A${heartbeat ? `${heartbeatAgeMs(heartbeat)}ms` : "\u65E0"}`,
|
|
21108
|
+
`\u98DE\u4E66\u901A\u9053\uFF1A${channelHealthLabel(heartbeat?.channel)}`,
|
|
20825
21109
|
`\u5DF2\u89C2\u5BDF\u8FC7 dsh \u8FD0\u884C\uFF1A${state.profileSeenUp ? "\u662F" : "\u5426"}`,
|
|
20826
21110
|
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
21111
|
`\u72B6\u6001\u6587\u4EF6\uFF1A${layout.stateFile}`,
|
|
@@ -20860,7 +21144,7 @@ function managerFor(options, version) {
|
|
|
20860
21144
|
version
|
|
20861
21145
|
});
|
|
20862
21146
|
}
|
|
20863
|
-
function formatServiceStatus(status, heartbeatAge2) {
|
|
21147
|
+
function formatServiceStatus(status, heartbeatAge2, channel) {
|
|
20864
21148
|
return [
|
|
20865
21149
|
"dsh-lark-bot \u6B63\u5E38\u5F15\u64CE\u670D\u52A1",
|
|
20866
21150
|
` name: ${status.name}`,
|
|
@@ -20871,7 +21155,8 @@ function formatServiceStatus(status, heartbeatAge2) {
|
|
|
20871
21155
|
` detail: ${status.detail}`,
|
|
20872
21156
|
` pid: ${status.pid ?? "-"}`,
|
|
20873
21157
|
` restarts: ${status.restarts ?? "-"}`,
|
|
20874
|
-
` heartbeat: ${heartbeatAge2 === void 0 ? "\u6682\u65E0" : `${heartbeatAge2}ms`}
|
|
21158
|
+
` heartbeat: ${heartbeatAge2 === void 0 ? "\u6682\u65E0" : `${heartbeatAge2}ms`}`,
|
|
21159
|
+
` channel: ${channelHealthLabel(channel)}`
|
|
20875
21160
|
].join("\n");
|
|
20876
21161
|
}
|
|
20877
21162
|
async function heartbeatAge() {
|
|
@@ -20884,6 +21169,16 @@ async function heartbeatAge() {
|
|
|
20884
21169
|
const heartbeat = await readHeartbeat(file);
|
|
20885
21170
|
return heartbeat ? heartbeatAgeMs(heartbeat) : void 0;
|
|
20886
21171
|
}
|
|
21172
|
+
async function heartbeatChannel() {
|
|
21173
|
+
const env = loadRuntimeEnv(process.env);
|
|
21174
|
+
const file = resolveAppPaths(env.home).profilePath(
|
|
21175
|
+
env.guardianBridgeProfile,
|
|
21176
|
+
"guardian",
|
|
21177
|
+
"heartbeat.json"
|
|
21178
|
+
);
|
|
21179
|
+
const heartbeat = await readHeartbeat(file);
|
|
21180
|
+
return heartbeat?.channel;
|
|
21181
|
+
}
|
|
20887
21182
|
async function runServiceCommand(action, options = {}, deps = {}) {
|
|
20888
21183
|
const manager = deps.manager ?? managerFor(options, deps.version ?? "0.0.0");
|
|
20889
21184
|
const output = deps.output ?? ((text) => process.stdout.write(text));
|
|
@@ -20900,7 +21195,11 @@ ${logs.text || "\uFF08\u6682\u65E0\u65E5\u5FD7\uFF09"}
|
|
|
20900
21195
|
return;
|
|
20901
21196
|
}
|
|
20902
21197
|
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
|
-
|
|
21198
|
+
const [age, channel] = await Promise.all([
|
|
21199
|
+
(deps.heartbeatAge ?? heartbeatAge)(),
|
|
21200
|
+
(deps.heartbeatChannel ?? heartbeatChannel)()
|
|
21201
|
+
]);
|
|
21202
|
+
output(`${formatServiceStatus(status, age, channel)}
|
|
20904
21203
|
`);
|
|
20905
21204
|
if ((action === "install" || action === "start" || action === "restart") && status.state !== "running") process.exitCode = 1;
|
|
20906
21205
|
if (action === "status" && (!status.installed || status.state === "error")) {
|
|
@@ -20927,7 +21226,7 @@ function followLogFile(path, lines) {
|
|
|
20927
21226
|
// src/cli/commands/supervise.ts
|
|
20928
21227
|
import { spawn as spawn10 } from "child_process";
|
|
20929
21228
|
import { closeSync, mkdirSync, openSync } from "fs";
|
|
20930
|
-
import { dirname as
|
|
21229
|
+
import { dirname as dirname20 } from "path";
|
|
20931
21230
|
import { homedir as homedir22 } from "os";
|
|
20932
21231
|
init_dsh_runtime();
|
|
20933
21232
|
var BASE_BACKOFF_MS = 5e3;
|
|
@@ -20961,8 +21260,8 @@ async function runSupervise(options, deps = {}) {
|
|
|
20961
21260
|
const logFile = paths.serviceLogFile(profile);
|
|
20962
21261
|
const dshBin = deps.dshBin ?? discoverDshBin(homedir22(), childEnv);
|
|
20963
21262
|
if (!dshBin) throw new Error("\u672A\u627E\u5230 dsh CLI\uFF0Cportable supervisor \u65E0\u6CD5\u542F\u52A8 profile\u3002");
|
|
20964
|
-
mkdirSync(
|
|
20965
|
-
mkdirSync(
|
|
21263
|
+
mkdirSync(dirname20(statusFile), { recursive: true });
|
|
21264
|
+
mkdirSync(dirname20(logFile), { recursive: true });
|
|
20966
21265
|
const logFd = openSync(logFile, "a");
|
|
20967
21266
|
const writeStatus = async (state, childPid, restarts2) => {
|
|
20968
21267
|
const processIdentity = await (deps.readProcessIdentity ?? readLinuxProcessIdentity)(process.pid);
|