dsh-lark-bot 0.7.0 → 0.8.0
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/README.md +206 -6
- package/SECURITY.md +15 -0
- package/dist/cli.js +1376 -17
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.js +77 -3
- package/dist/plugin.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -64,7 +64,7 @@ var init_dsh_compat = __esm({
|
|
|
64
64
|
sdkServer: "0.1.0-rc.6",
|
|
65
65
|
acp: "0.1.0-rc.6",
|
|
66
66
|
node: ">=22.19.0",
|
|
67
|
-
verifiedAt: "2026-08-
|
|
67
|
+
verifiedAt: "2026-08-15"
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
});
|
|
@@ -1498,7 +1498,13 @@ var DEFAULTS = {
|
|
|
1498
1498
|
scopeConcurrency: 2,
|
|
1499
1499
|
retentionMsgs: 40,
|
|
1500
1500
|
archiveMax: 50,
|
|
1501
|
-
archiveMaxAgeDays: 90
|
|
1501
|
+
archiveMaxAgeDays: 90,
|
|
1502
|
+
heartbeatMs: 5e3,
|
|
1503
|
+
guardianPollMs: 2e3,
|
|
1504
|
+
guardianStaleMs: 15e3,
|
|
1505
|
+
guardianEngineDeadMs: 12e4,
|
|
1506
|
+
guardianProfile: "dsh-lark",
|
|
1507
|
+
guardianBridgeProfile: "default"
|
|
1502
1508
|
};
|
|
1503
1509
|
function nonEmpty(value) {
|
|
1504
1510
|
const trimmed = value?.trim();
|
|
@@ -1584,6 +1590,15 @@ function parseMinOneInt(value, fallback, name) {
|
|
|
1584
1590
|
}
|
|
1585
1591
|
return parsed;
|
|
1586
1592
|
}
|
|
1593
|
+
function parsePositiveIntMin(value, fallback, name) {
|
|
1594
|
+
const raw = value?.trim();
|
|
1595
|
+
if (!raw) return fallback;
|
|
1596
|
+
const parsed = Number(raw);
|
|
1597
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
1598
|
+
throw new Error(`${name} must be a positive integer, got "${raw}"`);
|
|
1599
|
+
}
|
|
1600
|
+
return parsed;
|
|
1601
|
+
}
|
|
1587
1602
|
function loadRuntimeEnv(source = process.env) {
|
|
1588
1603
|
const homeOverride = nonEmpty(source.DSH_LARK_HOME);
|
|
1589
1604
|
const workspace = nonEmpty(source.DSH_LARK_WORKSPACE);
|
|
@@ -1634,7 +1649,30 @@ function loadRuntimeEnv(source = process.env) {
|
|
|
1634
1649
|
"DSH_LARK_ARCHIVE_MAX_AGE_DAYS"
|
|
1635
1650
|
),
|
|
1636
1651
|
accessDefaultDeny: parseBoolean(source.DSH_LARK_ACCESS_DEFAULT_DENY, false),
|
|
1637
|
-
eventFreshnessMs: parseFreshness(source.DSH_LARK_EVENT_FRESHNESS_MS)
|
|
1652
|
+
eventFreshnessMs: parseFreshness(source.DSH_LARK_EVENT_FRESHNESS_MS),
|
|
1653
|
+
heartbeatMs: parsePositiveIntMin(
|
|
1654
|
+
source.DSH_LARK_HEARTBEAT_MS,
|
|
1655
|
+
DEFAULTS.heartbeatMs,
|
|
1656
|
+
"DSH_LARK_HEARTBEAT_MS"
|
|
1657
|
+
),
|
|
1658
|
+
guardianDisabled: parseBoolean(source.DSH_LARK_GUARDIAN_DISABLED, false),
|
|
1659
|
+
guardianProfile: nonEmpty(source.DSH_LARK_GUARDIAN_PROFILE) ?? DEFAULTS.guardianProfile,
|
|
1660
|
+
guardianBridgeProfile: nonEmpty(source.DSH_LARK_GUARDIAN_BRIDGE_PROFILE) ?? DEFAULTS.guardianBridgeProfile,
|
|
1661
|
+
guardianPollMs: parsePositiveIntMin(
|
|
1662
|
+
source.DSH_LARK_GUARDIAN_POLL_MS,
|
|
1663
|
+
DEFAULTS.guardianPollMs,
|
|
1664
|
+
"DSH_LARK_GUARDIAN_POLL_MS"
|
|
1665
|
+
),
|
|
1666
|
+
guardianStaleMs: parsePositiveIntMin(
|
|
1667
|
+
source.DSH_LARK_GUARDIAN_STALE_MS,
|
|
1668
|
+
DEFAULTS.guardianStaleMs,
|
|
1669
|
+
"DSH_LARK_GUARDIAN_STALE_MS"
|
|
1670
|
+
),
|
|
1671
|
+
guardianEngineDeadMs: parsePositiveIntMin(
|
|
1672
|
+
source.DSH_LARK_GUARDIAN_ENGINE_DEAD_MS,
|
|
1673
|
+
DEFAULTS.guardianEngineDeadMs,
|
|
1674
|
+
"DSH_LARK_GUARDIAN_ENGINE_DEAD_MS"
|
|
1675
|
+
)
|
|
1638
1676
|
};
|
|
1639
1677
|
}
|
|
1640
1678
|
|
|
@@ -1660,9 +1698,9 @@ async function writeFileAtomic(target, data, options = {}) {
|
|
|
1660
1698
|
}
|
|
1661
1699
|
}
|
|
1662
1700
|
async function rmSilently(path) {
|
|
1663
|
-
const { rm:
|
|
1701
|
+
const { rm: rm3 } = await import("fs/promises");
|
|
1664
1702
|
try {
|
|
1665
|
-
await
|
|
1703
|
+
await rm3(path, { force: true });
|
|
1666
1704
|
} catch {
|
|
1667
1705
|
}
|
|
1668
1706
|
}
|
|
@@ -5391,6 +5429,59 @@ var WorkspaceStore = class {
|
|
|
5391
5429
|
}
|
|
5392
5430
|
};
|
|
5393
5431
|
|
|
5432
|
+
// src/guardian/heartbeat.ts
|
|
5433
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
5434
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
5435
|
+
async function readHeartbeat(file) {
|
|
5436
|
+
try {
|
|
5437
|
+
const raw = await readFile9(file, "utf8");
|
|
5438
|
+
const parsed = JSON.parse(raw);
|
|
5439
|
+
if (typeof parsed.pid !== "number" || typeof parsed.startedAt !== "string" || typeof parsed.ts !== "number") {
|
|
5440
|
+
return void 0;
|
|
5441
|
+
}
|
|
5442
|
+
return {
|
|
5443
|
+
pid: parsed.pid,
|
|
5444
|
+
startedAt: parsed.startedAt,
|
|
5445
|
+
ts: parsed.ts
|
|
5446
|
+
};
|
|
5447
|
+
} catch {
|
|
5448
|
+
return void 0;
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
function heartbeatAgeMs(payload, now = Date.now()) {
|
|
5452
|
+
return Math.max(0, now - payload.ts);
|
|
5453
|
+
}
|
|
5454
|
+
function isHeartbeatFresh(payload, maxAgeMs, now = Date.now()) {
|
|
5455
|
+
if (payload === void 0) return false;
|
|
5456
|
+
return heartbeatAgeMs(payload, now) < maxAgeMs;
|
|
5457
|
+
}
|
|
5458
|
+
function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS) {
|
|
5459
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5460
|
+
let stopped = false;
|
|
5461
|
+
const beat = async () => {
|
|
5462
|
+
if (stopped) return;
|
|
5463
|
+
const payload = { pid, startedAt, ts: Date.now() };
|
|
5464
|
+
try {
|
|
5465
|
+
await writeFileAtomic(file, `${JSON.stringify(payload)}
|
|
5466
|
+
`, {
|
|
5467
|
+
mode: 384
|
|
5468
|
+
});
|
|
5469
|
+
} catch {
|
|
5470
|
+
}
|
|
5471
|
+
};
|
|
5472
|
+
void beat();
|
|
5473
|
+
const timer = setInterval(() => {
|
|
5474
|
+
void beat();
|
|
5475
|
+
}, intervalMs);
|
|
5476
|
+
timer.unref?.();
|
|
5477
|
+
return {
|
|
5478
|
+
stop() {
|
|
5479
|
+
stopped = true;
|
|
5480
|
+
clearInterval(timer);
|
|
5481
|
+
}
|
|
5482
|
+
};
|
|
5483
|
+
}
|
|
5484
|
+
|
|
5394
5485
|
// src/cli/commands/run.ts
|
|
5395
5486
|
var DEBOUNCE_MS = 600;
|
|
5396
5487
|
async function ensureBotProfile(store, options) {
|
|
@@ -5605,6 +5696,11 @@ async function startBridgeEngine(options) {
|
|
|
5605
5696
|
larkChannel = bridge.channel;
|
|
5606
5697
|
await notifyServer.start();
|
|
5607
5698
|
process.env.DSH_LARK_NOTIFY_URL = notifyServer.url ?? "";
|
|
5699
|
+
const heartbeat = startHeartbeat(
|
|
5700
|
+
paths.profilePath(profileName, "guardian", "heartbeat.json"),
|
|
5701
|
+
process.pid,
|
|
5702
|
+
env.heartbeatMs
|
|
5703
|
+
);
|
|
5608
5704
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5609
5705
|
log.info("cli", "started", {
|
|
5610
5706
|
profile: profileName,
|
|
@@ -5629,6 +5725,7 @@ async function startBridgeEngine(options) {
|
|
|
5629
5725
|
stop: async () => {
|
|
5630
5726
|
if (stopped) return;
|
|
5631
5727
|
stopped = true;
|
|
5728
|
+
heartbeat.stop();
|
|
5632
5729
|
await notifyServer.stop();
|
|
5633
5730
|
await bridge.disconnect();
|
|
5634
5731
|
await adapter.dispose?.();
|
|
@@ -5672,29 +5769,396 @@ function waitForShutdown() {
|
|
|
5672
5769
|
// src/cli/commands/setup.ts
|
|
5673
5770
|
init_dsh_runtime();
|
|
5674
5771
|
init_own_package();
|
|
5675
|
-
import { spawn as
|
|
5676
|
-
import { mkdir as
|
|
5772
|
+
import { spawn as spawn6 } from "child_process";
|
|
5773
|
+
import { mkdir as mkdir12, readFile as readFile12, writeFile as writeFile7 } from "fs/promises";
|
|
5774
|
+
import { homedir as homedir7 } from "os";
|
|
5775
|
+
import { join as join13 } from "path";
|
|
5776
|
+
|
|
5777
|
+
// src/guardian/install.ts
|
|
5778
|
+
init_own_package();
|
|
5779
|
+
import { mkdir as mkdir11, readFile as readFile11, rm as rm2, writeFile as writeFile6 } from "fs/promises";
|
|
5780
|
+
import { existsSync as existsSync5 } from "fs";
|
|
5677
5781
|
import { homedir as homedir6 } from "os";
|
|
5678
5782
|
import { join as join12 } from "path";
|
|
5783
|
+
|
|
5784
|
+
// src/guardian/process.ts
|
|
5785
|
+
import { spawn as spawn5 } from "child_process";
|
|
5786
|
+
function hasProfileFlag(cmdline, dshProfile) {
|
|
5787
|
+
const pattern = new RegExp(
|
|
5788
|
+
`(?:^|\\s)--profile(?:\\s+|=)${escapeRegExp(dshProfile)}(?:\\s|$)`
|
|
5789
|
+
);
|
|
5790
|
+
return pattern.test(cmdline);
|
|
5791
|
+
}
|
|
5792
|
+
function escapeRegExp(value) {
|
|
5793
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5794
|
+
}
|
|
5795
|
+
function looksLikeDshProcess(cmdline) {
|
|
5796
|
+
return cmdline.includes("@deepseek-ai/dsh") || /(?:^|\s)dsh(?:\.exe)?(?:\s|$)/.test(cmdline);
|
|
5797
|
+
}
|
|
5798
|
+
function matchProfileProcess(cmdline, dshProfile) {
|
|
5799
|
+
return hasProfileFlag(cmdline, dshProfile) && looksLikeDshProcess(cmdline);
|
|
5800
|
+
}
|
|
5801
|
+
async function listProcesses() {
|
|
5802
|
+
if (process.platform === "win32") {
|
|
5803
|
+
return listProcessesWindows();
|
|
5804
|
+
}
|
|
5805
|
+
return listProcessesPosix();
|
|
5806
|
+
}
|
|
5807
|
+
async function listProcessesPosix() {
|
|
5808
|
+
const { stdout } = await captureOutput("ps", ["-axo", "pid=,args="], 1e4);
|
|
5809
|
+
const result = [];
|
|
5810
|
+
for (const line of stdout.split("\n")) {
|
|
5811
|
+
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
5812
|
+
if (match) {
|
|
5813
|
+
result.push({ pid: Number(match[1]), cmdline: match[2] ?? "" });
|
|
5814
|
+
}
|
|
5815
|
+
}
|
|
5816
|
+
return result;
|
|
5817
|
+
}
|
|
5818
|
+
async function listProcessesWindows() {
|
|
5819
|
+
const { stdout } = await captureOutput(
|
|
5820
|
+
"powershell.exe",
|
|
5821
|
+
[
|
|
5822
|
+
"-NoProfile",
|
|
5823
|
+
"-NonInteractive",
|
|
5824
|
+
"-Command",
|
|
5825
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine | ConvertTo-Csv -NoTypeInformation"
|
|
5826
|
+
],
|
|
5827
|
+
1e4
|
|
5828
|
+
);
|
|
5829
|
+
const result = [];
|
|
5830
|
+
for (const line of stdout.split("\n").slice(1)) {
|
|
5831
|
+
const [pid, ...rest] = line.trim().split(",");
|
|
5832
|
+
const numeric = Number(pid?.replace(/"/g, ""));
|
|
5833
|
+
if (Number.isInteger(numeric) && rest.length > 0) {
|
|
5834
|
+
result.push({ pid: numeric, cmdline: rest.join(",").replace(/^"|"$/g, "") });
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
return result;
|
|
5838
|
+
}
|
|
5839
|
+
async function captureOutput(command, args, timeoutMs = 3e4) {
|
|
5840
|
+
return new Promise((resolve5) => {
|
|
5841
|
+
const child = spawn5(command, [...args], {
|
|
5842
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5843
|
+
windowsHide: true
|
|
5844
|
+
});
|
|
5845
|
+
let stdout = "";
|
|
5846
|
+
let stderr = "";
|
|
5847
|
+
child.stdout?.on("data", (chunk) => {
|
|
5848
|
+
stdout += chunk.toString("utf8");
|
|
5849
|
+
});
|
|
5850
|
+
child.stderr?.on("data", (chunk) => {
|
|
5851
|
+
stderr += chunk.toString("utf8");
|
|
5852
|
+
});
|
|
5853
|
+
child.on("error", () => {
|
|
5854
|
+
resolve5({ code: 1, stdout: "", stderr });
|
|
5855
|
+
});
|
|
5856
|
+
child.on("close", (code) => {
|
|
5857
|
+
resolve5({ code: code ?? 1, stdout, stderr });
|
|
5858
|
+
});
|
|
5859
|
+
const timer = setTimeout(() => {
|
|
5860
|
+
child.kill("SIGKILL");
|
|
5861
|
+
resolve5({ code: 1, stdout, stderr });
|
|
5862
|
+
}, timeoutMs);
|
|
5863
|
+
child.once("close", () => clearTimeout(timer));
|
|
5864
|
+
});
|
|
5865
|
+
}
|
|
5866
|
+
async function findProfileProcess(dshProfile) {
|
|
5867
|
+
const processes = await listProcesses();
|
|
5868
|
+
return processes.find((entry) => matchProfileProcess(entry.cmdline, dshProfile));
|
|
5869
|
+
}
|
|
5870
|
+
function spawnDetached(command, args, env = process.env) {
|
|
5871
|
+
const child = spawn5(command, [...args], {
|
|
5872
|
+
detached: process.platform !== "win32",
|
|
5873
|
+
stdio: "ignore",
|
|
5874
|
+
windowsHide: true,
|
|
5875
|
+
env: { ...env }
|
|
5876
|
+
});
|
|
5877
|
+
if (child.pid !== void 0 && process.platform !== "win32") {
|
|
5878
|
+
child.unref();
|
|
5879
|
+
}
|
|
5880
|
+
return { pid: child.pid };
|
|
5881
|
+
}
|
|
5882
|
+
|
|
5883
|
+
// src/guardian/state.ts
|
|
5884
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
5885
|
+
import { dirname as dirname6 } from "path";
|
|
5886
|
+
import { mkdir as mkdir10 } from "fs/promises";
|
|
5887
|
+
var DEFAULT_DSH_PROFILE = "dsh-lark";
|
|
5888
|
+
var DEFAULT_BRIDGE_PROFILE = "default";
|
|
5889
|
+
function defaultSafeProfile(dshProfile) {
|
|
5890
|
+
return `${dshProfile}-safe`;
|
|
5891
|
+
}
|
|
5892
|
+
function newGuardianState(input) {
|
|
5893
|
+
const dshProfile = input.dshProfile ?? DEFAULT_DSH_PROFILE;
|
|
5894
|
+
return {
|
|
5895
|
+
schemaVersion: 1,
|
|
5896
|
+
dshProfile,
|
|
5897
|
+
bridgeProfile: input.bridgeProfile ?? DEFAULT_BRIDGE_PROFILE,
|
|
5898
|
+
safeProfile: defaultSafeProfile(dshProfile),
|
|
5899
|
+
profileSeenUp: false,
|
|
5900
|
+
mode: "standby",
|
|
5901
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5902
|
+
};
|
|
5903
|
+
}
|
|
5904
|
+
async function loadGuardianState(file, fallback) {
|
|
5905
|
+
try {
|
|
5906
|
+
const raw = await readFile10(file, "utf8");
|
|
5907
|
+
const parsed = JSON.parse(raw);
|
|
5908
|
+
if (parsed.schemaVersion !== 1) return fallback;
|
|
5909
|
+
const dshProfile = typeof parsed.dshProfile === "string" ? parsed.dshProfile : fallback.dshProfile;
|
|
5910
|
+
const bridgeProfile = typeof parsed.bridgeProfile === "string" ? parsed.bridgeProfile : fallback.bridgeProfile;
|
|
5911
|
+
return {
|
|
5912
|
+
schemaVersion: 1,
|
|
5913
|
+
dshProfile,
|
|
5914
|
+
bridgeProfile,
|
|
5915
|
+
safeProfile: typeof parsed.safeProfile === "string" ? parsed.safeProfile : defaultSafeProfile(dshProfile),
|
|
5916
|
+
profileSeenUp: parsed.profileSeenUp === true || fallback.profileSeenUp === true,
|
|
5917
|
+
mode: parsed.mode === "takeover" || parsed.mode === "safe" ? parsed.mode : "standby",
|
|
5918
|
+
relaunchedPid: typeof parsed.relaunchedPid === "number" ? parsed.relaunchedPid : void 0,
|
|
5919
|
+
updatedAt: parsed.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5920
|
+
};
|
|
5921
|
+
} catch {
|
|
5922
|
+
return fallback;
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
async function saveGuardianState(file, state) {
|
|
5926
|
+
const next = { ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5927
|
+
await mkdir10(dirname6(file), { recursive: true });
|
|
5928
|
+
await writeFileAtomic(file, `${JSON.stringify(next, null, 2)}
|
|
5929
|
+
`, { mode: 384 });
|
|
5930
|
+
}
|
|
5931
|
+
|
|
5932
|
+
// src/guardian/install.ts
|
|
5933
|
+
var GUARDIAN_LABEL = "dsh-lark-guardian";
|
|
5934
|
+
function guardianCliEntry() {
|
|
5935
|
+
return join12(ownPackageInfo().root, "dist", "cli.js");
|
|
5936
|
+
}
|
|
5937
|
+
function serviceDirectory(platform, root) {
|
|
5938
|
+
switch (platform) {
|
|
5939
|
+
case "linux":
|
|
5940
|
+
return join12(root, ".config", "systemd", "user");
|
|
5941
|
+
case "darwin":
|
|
5942
|
+
return join12(root, "Library", "LaunchAgents");
|
|
5943
|
+
case "win32":
|
|
5944
|
+
return join12(
|
|
5945
|
+
process.env.APPDATA ?? join12(root, "AppData", "Roaming"),
|
|
5946
|
+
"Microsoft",
|
|
5947
|
+
"Windows",
|
|
5948
|
+
"Start Menu",
|
|
5949
|
+
"Programs",
|
|
5950
|
+
"Startup"
|
|
5951
|
+
);
|
|
5952
|
+
default:
|
|
5953
|
+
return join12(root, ".dsh-lark", "guardian");
|
|
5954
|
+
}
|
|
5955
|
+
}
|
|
5956
|
+
function systemdUnit(nodeBin, cliEntry, env = {}) {
|
|
5957
|
+
const envLines = Object.entries(env).map(([key, value]) => `Environment=${key}=${value}`).join("\n");
|
|
5958
|
+
return [
|
|
5959
|
+
"[Unit]",
|
|
5960
|
+
"Description=dsh-lark-bot safety-net guardian (Feishu rescue when dsh is down)",
|
|
5961
|
+
"After=network-online.target",
|
|
5962
|
+
"Wants=network-online.target",
|
|
5963
|
+
"",
|
|
5964
|
+
"[Service]",
|
|
5965
|
+
"Type=simple",
|
|
5966
|
+
`ExecStart=${nodeBin} ${cliEntry} guardian run`,
|
|
5967
|
+
"Restart=on-failure",
|
|
5968
|
+
"RestartSec=5",
|
|
5969
|
+
...envLines ? [envLines] : [],
|
|
5970
|
+
"",
|
|
5971
|
+
"[Install]",
|
|
5972
|
+
"WantedBy=default.target",
|
|
5973
|
+
""
|
|
5974
|
+
].join("\n");
|
|
5975
|
+
}
|
|
5976
|
+
function launchdPlist(nodeBin, cliEntry, label = `io.dsh-lark.${GUARDIAN_LABEL}`, logPath = "/tmp/dsh-lark-guardian.log") {
|
|
5977
|
+
return [
|
|
5978
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
5979
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
5980
|
+
'<plist version="1.0">',
|
|
5981
|
+
"<dict>",
|
|
5982
|
+
` <key>Label</key><string>${label}</string>`,
|
|
5983
|
+
" <key>ProgramArguments</key>",
|
|
5984
|
+
" <array>",
|
|
5985
|
+
` <string>${nodeBin}</string>`,
|
|
5986
|
+
` <string>${cliEntry}</string>`,
|
|
5987
|
+
" <string>guardian</string>",
|
|
5988
|
+
" <string>run</string>",
|
|
5989
|
+
" </array>",
|
|
5990
|
+
" <key>RunAtLoad</key><true/>",
|
|
5991
|
+
" <key>KeepAlive</key><true/>",
|
|
5992
|
+
` <key>StandardOutPath</key><string>${logPath}</string>`,
|
|
5993
|
+
` <key>StandardErrorPath</key><string>${logPath}</string>`,
|
|
5994
|
+
"</dict>",
|
|
5995
|
+
"</plist>",
|
|
5996
|
+
""
|
|
5997
|
+
].join("\n");
|
|
5998
|
+
}
|
|
5999
|
+
function windowsStartupCmd(nodeBin, cliEntry) {
|
|
6000
|
+
return [
|
|
6001
|
+
"@echo off",
|
|
6002
|
+
"REM dsh-lark-bot safety-net guardian - keep the Feishu rescue entrance alive.",
|
|
6003
|
+
`start "" /b "${nodeBin}" "${cliEntry}" guardian run`,
|
|
6004
|
+
""
|
|
6005
|
+
].join("\r\n");
|
|
6006
|
+
}
|
|
6007
|
+
function defaultRun(command, args) {
|
|
6008
|
+
return captureOutput(command, args, 3e4);
|
|
6009
|
+
}
|
|
6010
|
+
async function installGuardian(options) {
|
|
6011
|
+
const messages = [];
|
|
6012
|
+
const root = options.rootOverride ?? homedir6();
|
|
6013
|
+
const dshProfile = options.dshProfile ?? options.env.guardianProfile;
|
|
6014
|
+
const bridgeProfile = options.bridgeProfile ?? options.env.guardianBridgeProfile;
|
|
6015
|
+
const stateFile = join12(root, ".dsh-lark", "guardian.json");
|
|
6016
|
+
const fallback = newGuardianState({ dshProfile, bridgeProfile });
|
|
6017
|
+
const existing = await loadGuardianState(stateFile, fallback);
|
|
6018
|
+
const state = {
|
|
6019
|
+
...existing,
|
|
6020
|
+
dshProfile: existing.dshProfile === dshProfile ? existing.dshProfile : dshProfile,
|
|
6021
|
+
bridgeProfile: existing.bridgeProfile === bridgeProfile ? existing.bridgeProfile : bridgeProfile,
|
|
6022
|
+
safeProfile: existing.safeProfile || `${dshProfile}-safe`
|
|
6023
|
+
};
|
|
6024
|
+
await saveGuardianState(stateFile, state);
|
|
6025
|
+
messages.push(`\u5B88\u62A4\u72B6\u6001\u5DF2\u5199\u5165 ${stateFile}\uFF08dsh profile=${state.dshProfile}\uFF09`);
|
|
6026
|
+
if (options.dryRun) {
|
|
6027
|
+
return { ok: true, messages };
|
|
6028
|
+
}
|
|
6029
|
+
const nodeBin = process.execPath;
|
|
6030
|
+
const cliEntry = guardianCliEntry();
|
|
6031
|
+
const directory = serviceDirectory(process.platform, root);
|
|
6032
|
+
await mkdir11(directory, { recursive: true });
|
|
6033
|
+
if (process.platform === "linux") {
|
|
6034
|
+
const unitPath = join12(directory, `${GUARDIAN_LABEL}.service`);
|
|
6035
|
+
await writeFile6(
|
|
6036
|
+
unitPath,
|
|
6037
|
+
systemdUnit(nodeBin, cliEntry, { DSH_LARK_GUARDIAN_DISABLED: "0" }),
|
|
6038
|
+
"utf8"
|
|
6039
|
+
);
|
|
6040
|
+
messages.push(`systemd user unit \u5DF2\u5199\u5165 ${unitPath}`);
|
|
6041
|
+
const run = options.run ?? defaultRun;
|
|
6042
|
+
const reload = await run("systemctl", ["--user", "daemon-reload"]);
|
|
6043
|
+
const enable = await run("systemctl", ["--user", "enable", "--now", `${GUARDIAN_LABEL}.service`]);
|
|
6044
|
+
if (reload.code === 0 && enable.code === 0) {
|
|
6045
|
+
messages.push("\u5B88\u62A4\u670D\u52A1\u5DF2\u542F\u7528\u5E76\u542F\u52A8\uFF08systemctl --user\uFF09\u3002");
|
|
6046
|
+
} else {
|
|
6047
|
+
messages.push(
|
|
6048
|
+
"\u672A\u80FD\u81EA\u52A8\u542F\u7528\u670D\u52A1\uFF08\u53EF\u80FD\u9700\u8981\u56FE\u5F62\u4F1A\u8BDD / systemd user \u5B9E\u4F8B\uFF09\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF1A"
|
|
6049
|
+
);
|
|
6050
|
+
messages.push(` systemctl --user daemon-reload && systemctl --user enable --now ${GUARDIAN_LABEL}.service`);
|
|
6051
|
+
return { ok: false, messages };
|
|
6052
|
+
}
|
|
6053
|
+
} else if (process.platform === "darwin") {
|
|
6054
|
+
const label = `io.dsh-lark.${GUARDIAN_LABEL}`;
|
|
6055
|
+
const plistPath = join12(directory, `${label}.plist`);
|
|
6056
|
+
await writeFile6(plistPath, launchdPlist(nodeBin, cliEntry), "utf8");
|
|
6057
|
+
messages.push(`launchd LaunchAgent \u5DF2\u5199\u5165 ${plistPath}`);
|
|
6058
|
+
const run = options.run ?? defaultRun;
|
|
6059
|
+
const uid = typeof options.userOverride === "string" ? options.userOverride : String(process.getuid?.() ?? 501);
|
|
6060
|
+
const bootstrap = await run("launchctl", ["bootstrap", `gui/${uid}`, plistPath]);
|
|
6061
|
+
if (bootstrap.code === 0) {
|
|
6062
|
+
messages.push("\u5B88\u62A4\u670D\u52A1\u5DF2\u52A0\u8F7D\uFF08launchctl bootstrap\uFF09\u3002");
|
|
6063
|
+
} else {
|
|
6064
|
+
messages.push(
|
|
6065
|
+
"\u672A\u80FD\u81EA\u52A8\u52A0\u8F7D LaunchAgent\u3002\u8BF7\u624B\u52A8\u6267\u884C\uFF08\u6216\u6539\u7528\u65E7\u7248 launchctl load -w\uFF09\uFF1A"
|
|
6066
|
+
);
|
|
6067
|
+
messages.push(` launchctl bootstrap gui/${uid} ${plistPath}`);
|
|
6068
|
+
return { ok: false, messages };
|
|
6069
|
+
}
|
|
6070
|
+
} else if (process.platform === "win32") {
|
|
6071
|
+
const cmdPath = join12(directory, `${GUARDIAN_LABEL}.cmd`);
|
|
6072
|
+
await writeFile6(cmdPath, windowsStartupCmd(nodeBin, cliEntry), "utf8");
|
|
6073
|
+
messages.push(`Windows \u542F\u52A8\u9879\u5DF2\u5199\u5165 ${cmdPath}\uFF08\u767B\u5F55\u540E\u81EA\u52A8\u5E38\u9A7B\uFF09\u3002`);
|
|
6074
|
+
} else {
|
|
6075
|
+
const fallbackPath = join12(directory, `${GUARDIAN_LABEL}.sh`);
|
|
6076
|
+
await writeFile6(
|
|
6077
|
+
fallbackPath,
|
|
6078
|
+
`#!/bin/sh
|
|
6079
|
+
exec "${nodeBin}" "${cliEntry}" guardian run
|
|
6080
|
+
`,
|
|
6081
|
+
"utf8"
|
|
6082
|
+
);
|
|
6083
|
+
messages.push(
|
|
6084
|
+
`\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u52A8\u6CE8\u518C\u670D\u52A1\uFF1B\u5DF2\u5199\u5165\u542F\u52A8\u811A\u672C ${fallbackPath}\uFF0C\u8BF7\u6309\u9700\u914D\u7F6E\u5E38\u9A7B\u3002`
|
|
6085
|
+
);
|
|
6086
|
+
}
|
|
6087
|
+
return { ok: true, messages };
|
|
6088
|
+
}
|
|
6089
|
+
async function uninstallGuardian(options) {
|
|
6090
|
+
const messages = [];
|
|
6091
|
+
const root = options.rootOverride ?? homedir6();
|
|
6092
|
+
const run = options.run ?? defaultRun;
|
|
6093
|
+
if (process.platform === "linux") {
|
|
6094
|
+
await run("systemctl", ["--user", "disable", "--now", `${GUARDIAN_LABEL}.service`]);
|
|
6095
|
+
const unitPath = join12(root, ".config", "systemd", "user", `${GUARDIAN_LABEL}.service`);
|
|
6096
|
+
await rm2(unitPath, { force: true });
|
|
6097
|
+
await run("systemctl", ["--user", "daemon-reload"]);
|
|
6098
|
+
messages.push("systemd user unit \u5DF2\u79FB\u9664\u5E76\u505C\u6B62\u3002");
|
|
6099
|
+
} else if (process.platform === "darwin") {
|
|
6100
|
+
const label = `io.dsh-lark.${GUARDIAN_LABEL}`;
|
|
6101
|
+
const plistPath = join12(root, "Library", "LaunchAgents", `${label}.plist`);
|
|
6102
|
+
const uid = typeof options.userOverride === "string" ? options.userOverride : String(process.getuid?.() ?? 501);
|
|
6103
|
+
await run("launchctl", ["bootout", `gui/${uid}`, plistPath]);
|
|
6104
|
+
await rm2(plistPath, { force: true });
|
|
6105
|
+
messages.push("launchd LaunchAgent \u5DF2\u79FB\u9664\u3002");
|
|
6106
|
+
} else if (process.platform === "win32") {
|
|
6107
|
+
const startupDir = join12(
|
|
6108
|
+
process.env.APPDATA ?? join12(root, "AppData", "Roaming"),
|
|
6109
|
+
"Microsoft",
|
|
6110
|
+
"Windows",
|
|
6111
|
+
"Start Menu",
|
|
6112
|
+
"Programs",
|
|
6113
|
+
"Startup"
|
|
6114
|
+
);
|
|
6115
|
+
await rm2(join12(startupDir, `${GUARDIAN_LABEL}.cmd`), { force: true });
|
|
6116
|
+
messages.push("Windows \u542F\u52A8\u9879\u5DF2\u79FB\u9664\u3002");
|
|
6117
|
+
} else {
|
|
6118
|
+
const fallbackPath = join12(root, ".dsh-lark", "guardian", `${GUARDIAN_LABEL}.sh`);
|
|
6119
|
+
await rm2(fallbackPath, { force: true });
|
|
6120
|
+
messages.push("\u542F\u52A8\u811A\u672C\u5DF2\u79FB\u9664\u3002");
|
|
6121
|
+
}
|
|
6122
|
+
messages.push("\u5B88\u62A4\u72B6\u6001\u6587\u4EF6\uFF08~/.dsh-lark/guardian.json\uFF09\u5DF2\u4FDD\u7559\uFF0C\u53EF\u968F\u65F6\u91CD\u65B0\u5B89\u88C5\u3002");
|
|
6123
|
+
return { ok: true, messages };
|
|
6124
|
+
}
|
|
6125
|
+
|
|
6126
|
+
// src/cli/commands/setup.ts
|
|
5679
6127
|
async function runSetup(options = {}) {
|
|
5680
6128
|
const profile = options.profile ?? "dsh-lark";
|
|
5681
6129
|
const own = ownPackageInfo();
|
|
5682
6130
|
const packageSpec = options.packageSpec ?? process.env.DSH_LARK_SETUP_PACKAGE ?? own.name;
|
|
5683
|
-
const dshHome = options.dshHome ?? resolveDshHome(
|
|
5684
|
-
const bin = options.bin ?? discoverDshBin(
|
|
6131
|
+
const dshHome = options.dshHome ?? resolveDshHome(homedir7(), process.env);
|
|
6132
|
+
const bin = options.bin ?? discoverDshBin(homedir7(), process.env);
|
|
5685
6133
|
if (!bin) {
|
|
5686
6134
|
throw new Error(
|
|
5687
6135
|
"\u672A\u627E\u5230\u672C\u673A dsh \u5B89\u88C5\uFF08`@deepseek-ai/dsh`\uFF09\u3002\u8BF7\u5148\u5B89\u88C5 DeepSeek Harness\uFF0C\u518D\u8FD0\u884C\u672C\u547D\u4EE4\u3002"
|
|
5688
6136
|
);
|
|
5689
6137
|
}
|
|
5690
|
-
const profileDir =
|
|
5691
|
-
await
|
|
6138
|
+
const profileDir = join13(dshHome, "profiles", profile);
|
|
6139
|
+
await mkdir12(profileDir, { recursive: true });
|
|
5692
6140
|
await approveBuilds(profileDir);
|
|
5693
6141
|
process.stdout.write(
|
|
5694
6142
|
`\u6B63\u5728\u628A ${packageSpec} \u5B89\u88C5\u5230 dsh profile \`${profile}\`\uFF08$DSH_HOME=${dshHome}\uFF09...
|
|
5695
6143
|
`
|
|
5696
6144
|
);
|
|
5697
6145
|
await runDshPlugin(bin, profile, packageSpec);
|
|
6146
|
+
if (options.guardian) {
|
|
6147
|
+
const install = options.installGuardianFn ?? installGuardian;
|
|
6148
|
+
const guardianResult = await install({
|
|
6149
|
+
env: loadRuntimeEnv(process.env),
|
|
6150
|
+
dshProfile: profile
|
|
6151
|
+
});
|
|
6152
|
+
for (const message of guardianResult.messages) {
|
|
6153
|
+
process.stdout.write(`${message}
|
|
6154
|
+
`);
|
|
6155
|
+
}
|
|
6156
|
+
if (!guardianResult.ok) {
|
|
6157
|
+
process.stdout.write(
|
|
6158
|
+
"\n\u6CE8\u610F\uFF1Aguardian \u670D\u52A1\u672A\u5B8C\u5168\u542F\u7528\uFF0C\u8BF7\u6309\u4E0A\u65B9\u63D0\u793A\u624B\u52A8\u6267\u884C\uFF1B\u4E5F\u53EF\u7A0D\u540E\u91CD\u8DD1 `dsh-lark-bot guardian install`\u3002\n"
|
|
6159
|
+
);
|
|
6160
|
+
}
|
|
6161
|
+
}
|
|
5698
6162
|
process.stdout.write(
|
|
5699
6163
|
[
|
|
5700
6164
|
"",
|
|
@@ -5704,13 +6168,21 @@ async function runSetup(options = {}) {
|
|
|
5704
6168
|
"",
|
|
5705
6169
|
"\u9996\u6B21\u542F\u52A8\u4F1A\u5728\u7EC8\u7AEF\u6253\u5370\u4E8C\u7EF4\u7801\uFF0C\u7528\u98DE\u4E66 / Lark \u626B\u7801\u5B8C\u6210\u4E00\u6B21\u6027\u7ED1\u5B9A\uFF1B",
|
|
5706
6170
|
"\u7ED1\u5B9A\u540E dsh \u5373\u4EE5\u6807\u51C6\u63D2\u4EF6\u65B9\u5F0F\u52A0\u8F7D dsh-lark-bot \u7684\u6865\u63A5\u5F15\u64CE\u3002",
|
|
6171
|
+
...options.guardian ? [
|
|
6172
|
+
"",
|
|
6173
|
+
"\u5B89\u5168\u7F51\u5B88\u62A4\u5DF2\u5B89\u88C5\uFF1Adsh \u4E0B\u7EBF\u540E\u4ECD\u53EF\u901A\u8FC7\u98DE\u4E66\u53D1\u9001 /safemode \u8FDB\u5165\u4EC5\u6838\u5FC3\u5B89\u5168\u6A21\u5F0F\u81EA\u6551\u3002",
|
|
6174
|
+
"\u67E5\u770B\u72B6\u6001\uFF1Adsh-lark-bot guardian status"
|
|
6175
|
+
] : [
|
|
6176
|
+
"",
|
|
6177
|
+
"\u63D0\u793A\uFF1A\u53EF\u52A0 --guardian \u540C\u65F6\u5B89\u88C5\u300C\u5B89\u5168\u7F51\u5B88\u62A4\u300D\uFF08dsh \u5168\u90E8\u4E0B\u7EBF\u540E\u7684\u98DE\u4E66\u6551\u63F4\u901A\u9053\uFF09\u3002"
|
|
6178
|
+
],
|
|
5707
6179
|
""
|
|
5708
6180
|
].join("\n")
|
|
5709
6181
|
);
|
|
5710
6182
|
}
|
|
5711
6183
|
function runDshPlugin(bin, profile, packageSpec) {
|
|
5712
6184
|
return new Promise((resolve5, reject) => {
|
|
5713
|
-
const child =
|
|
6185
|
+
const child = spawn6(
|
|
5714
6186
|
"node",
|
|
5715
6187
|
[bin, "plugin", "--profile", profile, "add", packageSpec],
|
|
5716
6188
|
{ stdio: "inherit" }
|
|
@@ -5723,10 +6195,10 @@ function runDshPlugin(bin, profile, packageSpec) {
|
|
|
5723
6195
|
});
|
|
5724
6196
|
}
|
|
5725
6197
|
async function approveBuilds(profileDir) {
|
|
5726
|
-
const workspaceFile =
|
|
6198
|
+
const workspaceFile = join13(profileDir, "pnpm-workspace.yaml");
|
|
5727
6199
|
let existing = "";
|
|
5728
6200
|
try {
|
|
5729
|
-
existing = await
|
|
6201
|
+
existing = await readFile12(workspaceFile, "utf8");
|
|
5730
6202
|
} catch {
|
|
5731
6203
|
}
|
|
5732
6204
|
const allowBuilds = [
|
|
@@ -5745,7 +6217,876 @@ allowBuilds:
|
|
|
5745
6217
|
existing = existing.replace(/(allowBuilds:\s*\n)/, `$1 ${allowBuilds}
|
|
5746
6218
|
`);
|
|
5747
6219
|
}
|
|
5748
|
-
await
|
|
6220
|
+
await writeFile7(workspaceFile, existing, "utf8");
|
|
6221
|
+
}
|
|
6222
|
+
|
|
6223
|
+
// src/cli/commands/guardian.ts
|
|
6224
|
+
import { join as join16 } from "path";
|
|
6225
|
+
|
|
6226
|
+
// src/guardian/service.ts
|
|
6227
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
6228
|
+
import { homedir as homedir8 } from "os";
|
|
6229
|
+
import { join as join15 } from "path";
|
|
6230
|
+
import { createLarkChannel as createLarkChannel2 } from "@larksuite/channel";
|
|
6231
|
+
init_dsh_runtime();
|
|
6232
|
+
|
|
6233
|
+
// src/guardian/control.ts
|
|
6234
|
+
var COMMAND_RE = /^\/(safemode)(?:\s+(.+))?$/i;
|
|
6235
|
+
function parseGuardianCommand(text) {
|
|
6236
|
+
const trimmed = text.trim();
|
|
6237
|
+
const match = COMMAND_RE.exec(trimmed);
|
|
6238
|
+
if (!match) return void 0;
|
|
6239
|
+
const argument = match[2]?.trim().toLowerCase() ?? "";
|
|
6240
|
+
switch (argument) {
|
|
6241
|
+
case "":
|
|
6242
|
+
case "start":
|
|
6243
|
+
case "enter":
|
|
6244
|
+
return { kind: "safemode", argument };
|
|
6245
|
+
case "status":
|
|
6246
|
+
return { kind: "safemode-status", argument };
|
|
6247
|
+
case "plugins":
|
|
6248
|
+
case "list":
|
|
6249
|
+
return { kind: "safemode-plugins", argument };
|
|
6250
|
+
case "exit":
|
|
6251
|
+
case "quit":
|
|
6252
|
+
case "leave":
|
|
6253
|
+
case "restore":
|
|
6254
|
+
return { kind: "safemode-exit", argument };
|
|
6255
|
+
case "help":
|
|
6256
|
+
case "?":
|
|
6257
|
+
return { kind: "safemode-help", argument };
|
|
6258
|
+
default:
|
|
6259
|
+
return { kind: "safemode-help", argument };
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
var SAFEMODE_HELP = [
|
|
6263
|
+
"/safemode \u2014 \u8FDB\u5165\u4EC5\u6838\u5FC3\u5B89\u5168\u6A21\u5F0F\uFF08dsh \u4E3B\u6838\u5FC3 + \u5B98\u65B9 headless\uFF0C\u4E0D\u52A0\u8F7D\u4EFB\u4F55\u7B2C\u4E09\u65B9\u63D2\u4EF6\uFF09",
|
|
6264
|
+
"/safemode status \u2014 \u67E5\u770B\u5B88\u62A4\u72B6\u6001",
|
|
6265
|
+
"/safemode plugins \u2014 \u5217\u51FA\u6545\u969C profile \u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6",
|
|
6266
|
+
"/safemode exit \u2014 \u9000\u51FA\u5B89\u5168\u6A21\u5F0F\uFF0C\u91CD\u542F\u5B8C\u6574 profile \u5E76\u4EA4\u8FD8\u98DE\u4E66\u901A\u9053",
|
|
6267
|
+
"",
|
|
6268
|
+
"\u5B89\u5168\u6A21\u5F0F\u4E0B\u53D1\u9001\u666E\u901A\u6D88\u606F\u5373\u53EF\u4E0E dsh \u6838\u5FC3\u5BF9\u8BDD\uFF0C\u8FDB\u884C\u5B9A\u4F4D / \u4FEE\u590D / \u7981\u7528\u635F\u574F\u63D2\u4EF6\u7684\u81EA\u6108\u64CD\u4F5C\u3002"
|
|
6269
|
+
].join("\n");
|
|
6270
|
+
var SAFEMODE_HELP_EN = [
|
|
6271
|
+
"/safemode \u2014 enter core-only safe mode (dsh core + official headless, no third-party plugins)",
|
|
6272
|
+
"/safemode status \u2014 show guardian state",
|
|
6273
|
+
"/safemode plugins \u2014 list plugins installed into the broken profile",
|
|
6274
|
+
"/safemode exit \u2014 leave safe mode, relaunch the full profile and hand the channel back",
|
|
6275
|
+
"",
|
|
6276
|
+
"In safe mode, send normal messages to converse with the dsh core for self-healing."
|
|
6277
|
+
].join("\n");
|
|
6278
|
+
function safemodeHelpText() {
|
|
6279
|
+
return `${SAFEMODE_HELP}
|
|
6280
|
+
|
|
6281
|
+
${SAFEMODE_HELP_EN}`;
|
|
6282
|
+
}
|
|
6283
|
+
|
|
6284
|
+
// src/guardian/safe-profile.ts
|
|
6285
|
+
init_dsh_runtime();
|
|
6286
|
+
import { mkdir as mkdir13, writeFile as writeFile8 } from "fs/promises";
|
|
6287
|
+
import { existsSync as existsSync6 } from "fs";
|
|
6288
|
+
import { join as join14 } from "path";
|
|
6289
|
+
var SAFE_CORE_BUNDLES = [
|
|
6290
|
+
"@deepseek-ai/dsh-base",
|
|
6291
|
+
"@deepseek-ai/dsh-headless"
|
|
6292
|
+
];
|
|
6293
|
+
function safeProfileName(dshProfile) {
|
|
6294
|
+
return `${dshProfile}-safe`;
|
|
6295
|
+
}
|
|
6296
|
+
function safeProfileRoot(home, dshProfile, env) {
|
|
6297
|
+
return join14(resolveDshHome(home, env), "profiles", safeProfileName(dshProfile));
|
|
6298
|
+
}
|
|
6299
|
+
async function ensureSafeProfile(options) {
|
|
6300
|
+
const root = safeProfileRoot(options.home, options.dshProfile, options.env);
|
|
6301
|
+
const name = safeProfileName(options.dshProfile);
|
|
6302
|
+
await mkdir13(root, { recursive: true });
|
|
6303
|
+
let created = false;
|
|
6304
|
+
const manifest = join14(root, "package.json");
|
|
6305
|
+
if (!existsSync6(manifest)) {
|
|
6306
|
+
await writeFile8(
|
|
6307
|
+
manifest,
|
|
6308
|
+
`${JSON.stringify(
|
|
6309
|
+
{
|
|
6310
|
+
name: `dsh-profile-${name}`,
|
|
6311
|
+
private: true,
|
|
6312
|
+
dependencies: {},
|
|
6313
|
+
dsh: {
|
|
6314
|
+
profile: {
|
|
6315
|
+
bundles: [...SAFE_CORE_BUNDLES]
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
},
|
|
6319
|
+
null,
|
|
6320
|
+
2
|
|
6321
|
+
)}
|
|
6322
|
+
`,
|
|
6323
|
+
"utf8"
|
|
6324
|
+
);
|
|
6325
|
+
created = true;
|
|
6326
|
+
}
|
|
6327
|
+
const rootConfig = join14(root, "cordis.yml");
|
|
6328
|
+
if (!existsSync6(rootConfig)) {
|
|
6329
|
+
await writeFile8(
|
|
6330
|
+
rootConfig,
|
|
6331
|
+
"# dsh profile root - an empty entry list; the tree is composed as patches.\n[]\n",
|
|
6332
|
+
"utf8"
|
|
6333
|
+
);
|
|
6334
|
+
created = true;
|
|
6335
|
+
}
|
|
6336
|
+
const userPatch = join14(root, "cordis.patch.yml");
|
|
6337
|
+
if (!existsSync6(userPatch)) {
|
|
6338
|
+
await writeFile8(userPatch, "[]\n", "utf8");
|
|
6339
|
+
created = true;
|
|
6340
|
+
}
|
|
6341
|
+
const workspace = join14(root, "pnpm-workspace.yaml");
|
|
6342
|
+
if (!existsSync6(workspace)) {
|
|
6343
|
+
await writeFile8(
|
|
6344
|
+
workspace,
|
|
6345
|
+
[
|
|
6346
|
+
"packages:",
|
|
6347
|
+
" - .",
|
|
6348
|
+
"",
|
|
6349
|
+
"",
|
|
6350
|
+
"nodeLinker: hoisted",
|
|
6351
|
+
"autoInstallPeers: false",
|
|
6352
|
+
""
|
|
6353
|
+
].join("\n"),
|
|
6354
|
+
"utf8"
|
|
6355
|
+
);
|
|
6356
|
+
created = true;
|
|
6357
|
+
}
|
|
6358
|
+
return { root, created };
|
|
6359
|
+
}
|
|
6360
|
+
async function defaultRun2(command, args) {
|
|
6361
|
+
const { spawn: spawn7 } = await import("child_process");
|
|
6362
|
+
return new Promise((resolve5) => {
|
|
6363
|
+
const child = spawn7(command, [...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
6364
|
+
let stdout = "";
|
|
6365
|
+
let stderr = "";
|
|
6366
|
+
child.stdout?.on("data", (chunk) => {
|
|
6367
|
+
stdout += chunk.toString("utf8");
|
|
6368
|
+
});
|
|
6369
|
+
child.stderr?.on("data", (chunk) => {
|
|
6370
|
+
stderr += chunk.toString("utf8");
|
|
6371
|
+
});
|
|
6372
|
+
child.on("error", (error) => {
|
|
6373
|
+
resolve5({ code: 1, stdout, stderr: `${error.message}
|
|
6374
|
+
` });
|
|
6375
|
+
});
|
|
6376
|
+
child.on("close", (code) => {
|
|
6377
|
+
resolve5({ code: code ?? 1, stdout, stderr });
|
|
6378
|
+
});
|
|
6379
|
+
});
|
|
6380
|
+
}
|
|
6381
|
+
async function probeSafeProfile(options) {
|
|
6382
|
+
const run = options.run ?? defaultRun2;
|
|
6383
|
+
const profile = safeProfileName(options.dshProfile);
|
|
6384
|
+
const result = await run(options.bin, ["--profile", profile, "--dump-config"]);
|
|
6385
|
+
if (result.code === 0) {
|
|
6386
|
+
return { ok: true, stdout: result.stdout, stderr: result.stderr };
|
|
6387
|
+
}
|
|
6388
|
+
const tail = result.stderr.trim().split("\n").slice(-5).join("\n");
|
|
6389
|
+
return {
|
|
6390
|
+
ok: false,
|
|
6391
|
+
stdout: result.stdout,
|
|
6392
|
+
stderr: result.stderr,
|
|
6393
|
+
error: tail || `dsh --profile ${profile} --dump-config exited with code ${result.code}`
|
|
6394
|
+
};
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6397
|
+
// src/guardian/service.ts
|
|
6398
|
+
var MAX_TRANSCRIPT_ENTRIES = 30;
|
|
6399
|
+
var SAFE_RUN_TIMEOUT_MS = 10 * 6e4;
|
|
6400
|
+
var GuardianService = class {
|
|
6401
|
+
options;
|
|
6402
|
+
state;
|
|
6403
|
+
channel;
|
|
6404
|
+
safeAdapter;
|
|
6405
|
+
transcripts = /* @__PURE__ */ new Map();
|
|
6406
|
+
downStreak = 0;
|
|
6407
|
+
lastHeartbeatFreshAt;
|
|
6408
|
+
timer;
|
|
6409
|
+
ticking = false;
|
|
6410
|
+
stopped = false;
|
|
6411
|
+
dshBin;
|
|
6412
|
+
config;
|
|
6413
|
+
eventFreshnessMs;
|
|
6414
|
+
activeMessages = /* @__PURE__ */ new Set();
|
|
6415
|
+
constructor(options) {
|
|
6416
|
+
this.options = {
|
|
6417
|
+
pollMs: options.pollMs ?? 2e3,
|
|
6418
|
+
staleMs: options.staleMs ?? 15e3,
|
|
6419
|
+
engineDeadMs: options.engineDeadMs ?? 12e4,
|
|
6420
|
+
takeoverGracePolls: options.takeoverGracePolls ?? 2,
|
|
6421
|
+
sendDelayMs: options.sendDelayMs ?? 600,
|
|
6422
|
+
stateFile: options.stateFile,
|
|
6423
|
+
configFile: options.configFile,
|
|
6424
|
+
heartbeatFile: options.heartbeatFile,
|
|
6425
|
+
home: options.home,
|
|
6426
|
+
dshProfile: options.dshProfile,
|
|
6427
|
+
bridgeProfile: options.bridgeProfile,
|
|
6428
|
+
safeProfile: options.safeProfile,
|
|
6429
|
+
...defined({
|
|
6430
|
+
env: options.env,
|
|
6431
|
+
createChannel: options.createChannel,
|
|
6432
|
+
adapter: options.adapter,
|
|
6433
|
+
findProcess: options.findProcess,
|
|
6434
|
+
spawnDetachedFn: options.spawnDetachedFn,
|
|
6435
|
+
probeSafeProfileFn: options.probeSafeProfileFn,
|
|
6436
|
+
runPluginList: options.runPluginList,
|
|
6437
|
+
dshBin: options.dshBin,
|
|
6438
|
+
saveState: options.saveState,
|
|
6439
|
+
logger: options.logger,
|
|
6440
|
+
now: options.now
|
|
6441
|
+
})
|
|
6442
|
+
};
|
|
6443
|
+
this.state = newGuardianState({
|
|
6444
|
+
dshProfile: options.dshProfile,
|
|
6445
|
+
bridgeProfile: options.bridgeProfile
|
|
6446
|
+
});
|
|
6447
|
+
this.eventFreshnessMs = parseFreshness2(this.options.env?.DSH_LARK_EVENT_FRESHNESS_MS);
|
|
6448
|
+
}
|
|
6449
|
+
get mode() {
|
|
6450
|
+
return this.state.mode;
|
|
6451
|
+
}
|
|
6452
|
+
async start() {
|
|
6453
|
+
const loaded = await loadGuardianState(this.options.stateFile, this.state);
|
|
6454
|
+
if (loaded.dshProfile !== this.options.dshProfile) {
|
|
6455
|
+
this.state = loaded;
|
|
6456
|
+
} else {
|
|
6457
|
+
this.state = loaded;
|
|
6458
|
+
}
|
|
6459
|
+
await this.loadContext();
|
|
6460
|
+
await this.save();
|
|
6461
|
+
await this.tick();
|
|
6462
|
+
this.timer = setInterval(() => {
|
|
6463
|
+
void this.tick();
|
|
6464
|
+
}, this.options.pollMs);
|
|
6465
|
+
this.timer.unref?.();
|
|
6466
|
+
this.log().info("guardian", "started", {
|
|
6467
|
+
dshProfile: this.state.dshProfile,
|
|
6468
|
+
bridgeProfile: this.state.bridgeProfile,
|
|
6469
|
+
mode: this.state.mode,
|
|
6470
|
+
pollMs: this.options.pollMs
|
|
6471
|
+
});
|
|
6472
|
+
}
|
|
6473
|
+
async stop() {
|
|
6474
|
+
if (this.stopped) return;
|
|
6475
|
+
this.stopped = true;
|
|
6476
|
+
if (this.timer) clearInterval(this.timer);
|
|
6477
|
+
this.timer = void 0;
|
|
6478
|
+
await Promise.allSettled([...this.activeMessages]);
|
|
6479
|
+
await this.disconnectChannel();
|
|
6480
|
+
await this.safeAdapter?.dispose?.();
|
|
6481
|
+
this.log().info("guardian", "stopped", {});
|
|
6482
|
+
}
|
|
6483
|
+
snapshot() {
|
|
6484
|
+
return {
|
|
6485
|
+
mode: this.state.mode,
|
|
6486
|
+
dshProfile: this.state.dshProfile,
|
|
6487
|
+
bridgeProfile: this.state.bridgeProfile,
|
|
6488
|
+
safeProfile: this.state.safeProfile,
|
|
6489
|
+
profileSeenUp: this.state.profileSeenUp,
|
|
6490
|
+
dshUp: this.dshUp,
|
|
6491
|
+
heartbeatAgeMs: this.lastHeartbeatAgeMs,
|
|
6492
|
+
channelConnected: this.channel !== void 0,
|
|
6493
|
+
pid: process.pid,
|
|
6494
|
+
dshBin: this.dshBin,
|
|
6495
|
+
relaunchedPid: this.state.relaunchedPid,
|
|
6496
|
+
updatedAt: this.state.updatedAt
|
|
6497
|
+
};
|
|
6498
|
+
}
|
|
6499
|
+
dshUp = false;
|
|
6500
|
+
lastHeartbeatAgeMs;
|
|
6501
|
+
log() {
|
|
6502
|
+
return this.options.logger ?? log;
|
|
6503
|
+
}
|
|
6504
|
+
async loadContext() {
|
|
6505
|
+
const store = new ConfigStore(this.options.configFile);
|
|
6506
|
+
await store.load();
|
|
6507
|
+
this.config = store.getProfile(this.state.bridgeProfile);
|
|
6508
|
+
this.dshBin = this.options.dshBin ?? discoverDshBin(this.options.home, this.options.env ?? process.env);
|
|
6509
|
+
}
|
|
6510
|
+
async save() {
|
|
6511
|
+
if (this.options.saveState) {
|
|
6512
|
+
await this.options.saveState(this.state);
|
|
6513
|
+
return;
|
|
6514
|
+
}
|
|
6515
|
+
await saveGuardianState(this.options.stateFile, this.state);
|
|
6516
|
+
}
|
|
6517
|
+
async setMode(mode) {
|
|
6518
|
+
if (this.state.mode === mode) return;
|
|
6519
|
+
this.state.mode = mode;
|
|
6520
|
+
await this.save();
|
|
6521
|
+
}
|
|
6522
|
+
async setSeenUp() {
|
|
6523
|
+
if (this.state.profileSeenUp) return;
|
|
6524
|
+
this.state.profileSeenUp = true;
|
|
6525
|
+
await this.save();
|
|
6526
|
+
}
|
|
6527
|
+
async tick() {
|
|
6528
|
+
if (this.stopped || this.ticking) return;
|
|
6529
|
+
this.ticking = true;
|
|
6530
|
+
try {
|
|
6531
|
+
const heartbeat = await readHeartbeat(this.options.heartbeatFile);
|
|
6532
|
+
const now = (this.options.now ?? Date.now)();
|
|
6533
|
+
const heartbeatFresh = isHeartbeatFresh(heartbeat, this.options.staleMs, now);
|
|
6534
|
+
this.lastHeartbeatAgeMs = heartbeat ? heartbeatAgeMs(heartbeat, now) : void 0;
|
|
6535
|
+
if (heartbeatFresh) this.lastHeartbeatFreshAt = now;
|
|
6536
|
+
const processFound = await (this.options.findProcess ?? findProfileProcess)(
|
|
6537
|
+
this.state.dshProfile
|
|
6538
|
+
);
|
|
6539
|
+
const processAlive = processFound !== void 0;
|
|
6540
|
+
const engineDead = this.lastHeartbeatFreshAt !== void 0 && now - this.lastHeartbeatFreshAt > this.options.engineDeadMs;
|
|
6541
|
+
const up = heartbeatFresh || processAlive && !engineDead;
|
|
6542
|
+
this.dshUp = up;
|
|
6543
|
+
if (up) {
|
|
6544
|
+
this.downStreak = 0;
|
|
6545
|
+
await this.setSeenUp();
|
|
6546
|
+
if (this.state.mode !== "standby" || this.channel !== void 0) {
|
|
6547
|
+
await this.disconnectChannel();
|
|
6548
|
+
this.transcripts.clear();
|
|
6549
|
+
await this.setMode("standby");
|
|
6550
|
+
}
|
|
6551
|
+
return;
|
|
6552
|
+
}
|
|
6553
|
+
if (!this.state.profileSeenUp) return;
|
|
6554
|
+
this.downStreak += 1;
|
|
6555
|
+
if (this.downStreak < this.options.takeoverGracePolls) return;
|
|
6556
|
+
await this.ensureChannel();
|
|
6557
|
+
} catch (error) {
|
|
6558
|
+
this.log().fail("guardian", error);
|
|
6559
|
+
} finally {
|
|
6560
|
+
this.ticking = false;
|
|
6561
|
+
}
|
|
6562
|
+
}
|
|
6563
|
+
async ensureChannel() {
|
|
6564
|
+
if (this.channel !== void 0) return;
|
|
6565
|
+
const config = this.config ?? await this.reloadConfig();
|
|
6566
|
+
if (!config) {
|
|
6567
|
+
this.log().warn("guardian", "no-bridge-profile", {
|
|
6568
|
+
bridgeProfile: this.state.bridgeProfile
|
|
6569
|
+
});
|
|
6570
|
+
return;
|
|
6571
|
+
}
|
|
6572
|
+
const create = this.options.createChannel ?? createLarkChannel2;
|
|
6573
|
+
const channel = create({
|
|
6574
|
+
appId: config.accounts.appId,
|
|
6575
|
+
appSecret: config.accounts.appSecret,
|
|
6576
|
+
domain: config.tenant === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn",
|
|
6577
|
+
source: "dsh-lark-bot-guardian",
|
|
6578
|
+
policy: {
|
|
6579
|
+
dmMode: config.access.allowedUsers.length ? "allowlist" : "open",
|
|
6580
|
+
requireMention: true,
|
|
6581
|
+
respondToMentionAll: false,
|
|
6582
|
+
...config.access.allowedUsers.length ? { dmAllowlist: config.access.allowedUsers } : {},
|
|
6583
|
+
...config.access.allowedChats.length ? { groupAllowlist: config.access.allowedChats } : {}
|
|
6584
|
+
},
|
|
6585
|
+
safety: {
|
|
6586
|
+
chatQueue: { enabled: false }
|
|
6587
|
+
},
|
|
6588
|
+
outbound: {
|
|
6589
|
+
streamThrottleMs: 400
|
|
6590
|
+
},
|
|
6591
|
+
includeRawEvent: true,
|
|
6592
|
+
resolveChatMode: true,
|
|
6593
|
+
handshakeTimeoutMs: 8e3,
|
|
6594
|
+
httpTimeoutMs: 3e4,
|
|
6595
|
+
respectProxyEnv: true
|
|
6596
|
+
});
|
|
6597
|
+
channel.on({
|
|
6598
|
+
message: (msg) => this.track(this.handleMessage(msg)),
|
|
6599
|
+
error: (error) => {
|
|
6600
|
+
this.log().fail("guardian-channel", error);
|
|
6601
|
+
},
|
|
6602
|
+
reconnecting: () => {
|
|
6603
|
+
this.log().warn("guardian-channel", "reconnecting", {});
|
|
6604
|
+
},
|
|
6605
|
+
reconnected: () => {
|
|
6606
|
+
this.log().info("guardian-channel", "reconnected", {});
|
|
6607
|
+
}
|
|
6608
|
+
});
|
|
6609
|
+
try {
|
|
6610
|
+
await channel.connect();
|
|
6611
|
+
this.channel = channel;
|
|
6612
|
+
if (this.state.mode !== "safe") await this.setMode("takeover");
|
|
6613
|
+
this.log().info("guardian", "channel-taken-over", {
|
|
6614
|
+
dshProfile: this.state.dshProfile
|
|
6615
|
+
});
|
|
6616
|
+
} catch (error) {
|
|
6617
|
+
this.log().fail("guardian-channel", error);
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
async reloadConfig() {
|
|
6621
|
+
await this.loadContext();
|
|
6622
|
+
return this.config;
|
|
6623
|
+
}
|
|
6624
|
+
async disconnectChannel() {
|
|
6625
|
+
const channel = this.channel;
|
|
6626
|
+
this.channel = void 0;
|
|
6627
|
+
if (!channel) return;
|
|
6628
|
+
try {
|
|
6629
|
+
await channel.disconnect();
|
|
6630
|
+
} catch (error) {
|
|
6631
|
+
this.log().fail("guardian-channel", error);
|
|
6632
|
+
}
|
|
6633
|
+
if (this.state.mode !== "standby") {
|
|
6634
|
+
await this.setMode("standby");
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
scopeFor(msg) {
|
|
6638
|
+
if (msg.chatMode === "topic" && msg.threadId) return `${msg.chatId}:${msg.threadId}`;
|
|
6639
|
+
return msg.chatId;
|
|
6640
|
+
}
|
|
6641
|
+
authorized(msg) {
|
|
6642
|
+
const access3 = this.config?.access;
|
|
6643
|
+
if (!access3) return false;
|
|
6644
|
+
const controllers = access3.admins.length > 0 ? access3.admins : access3.allowedUsers;
|
|
6645
|
+
return controllers.includes(msg.senderId);
|
|
6646
|
+
}
|
|
6647
|
+
async sendMarkdown(chatId, markdown2, replyTo) {
|
|
6648
|
+
if (!this.channel) return;
|
|
6649
|
+
try {
|
|
6650
|
+
await this.channel.send(
|
|
6651
|
+
chatId,
|
|
6652
|
+
{ markdown: markdown2 },
|
|
6653
|
+
replyTo ? { replyTo } : void 0
|
|
6654
|
+
);
|
|
6655
|
+
} catch (error) {
|
|
6656
|
+
this.log().fail("guardian-send", error);
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
async handleMessage(msg) {
|
|
6660
|
+
if (!this.authorized(msg)) {
|
|
6661
|
+
this.log().warn("guardian", "unauthorized-message-dropped", {
|
|
6662
|
+
senderId: msg.senderId,
|
|
6663
|
+
scope: this.scopeFor(msg)
|
|
6664
|
+
});
|
|
6665
|
+
return;
|
|
6666
|
+
}
|
|
6667
|
+
if (this.eventFreshnessMs > 0 && !isEventFresh(msg.createTime, this.eventFreshnessMs)) {
|
|
6668
|
+
this.log().warn("guardian", "stale-message-dropped", {
|
|
6669
|
+
ageMs: Date.now() - msg.createTime
|
|
6670
|
+
});
|
|
6671
|
+
return;
|
|
6672
|
+
}
|
|
6673
|
+
const scope = this.scopeFor(msg);
|
|
6674
|
+
const control = parseGuardianCommand(msg.content);
|
|
6675
|
+
if (control) {
|
|
6676
|
+
await this.handleControl(control.kind, msg);
|
|
6677
|
+
return;
|
|
6678
|
+
}
|
|
6679
|
+
if (this.state.mode === "safe") {
|
|
6680
|
+
await this.runSafeTask(scope, msg);
|
|
6681
|
+
return;
|
|
6682
|
+
}
|
|
6683
|
+
await this.sendMarkdown(
|
|
6684
|
+
msg.chatId,
|
|
6685
|
+
[
|
|
6686
|
+
"dsh \u672A\u5728\u8FD0\u884C\uFF0C\u5B88\u62A4\u8FDB\u7A0B\u5DF2\u63A5\u7BA1\u98DE\u4E66\u901A\u9053\u3002",
|
|
6687
|
+
"",
|
|
6688
|
+
"\u53D1\u9001 `/safemode` \u8FDB\u5165\u4EC5\u6838\u5FC3\u5B89\u5168\u6A21\u5F0F\uFF08dsh \u4E3B\u6838\u5FC3 + \u5B98\u65B9 headless\uFF0C\u4E0D\u52A0\u8F7D\u4EFB\u4F55\u7B2C\u4E09\u65B9\u63D2\u4EF6\uFF09\uFF0C",
|
|
6689
|
+
"\u6216\u53D1\u9001 `/safemode status` \u67E5\u770B\u72B6\u6001\u3002"
|
|
6690
|
+
].join("\n"),
|
|
6691
|
+
msg.messageId
|
|
6692
|
+
);
|
|
6693
|
+
}
|
|
6694
|
+
track(promise) {
|
|
6695
|
+
this.activeMessages.add(promise);
|
|
6696
|
+
void promise.finally(() => {
|
|
6697
|
+
this.activeMessages.delete(promise);
|
|
6698
|
+
});
|
|
6699
|
+
return promise;
|
|
6700
|
+
}
|
|
6701
|
+
async handleControl(kind, msg) {
|
|
6702
|
+
switch (kind) {
|
|
6703
|
+
case "safemode":
|
|
6704
|
+
await this.enterSafeMode(msg);
|
|
6705
|
+
return;
|
|
6706
|
+
case "safemode-status":
|
|
6707
|
+
await this.sendStatus(msg);
|
|
6708
|
+
return;
|
|
6709
|
+
case "safemode-plugins":
|
|
6710
|
+
await this.sendPluginList(msg);
|
|
6711
|
+
return;
|
|
6712
|
+
case "safemode-exit":
|
|
6713
|
+
await this.exitSafeMode(msg);
|
|
6714
|
+
return;
|
|
6715
|
+
case "safemode-help":
|
|
6716
|
+
await this.sendMarkdown(msg.chatId, safemodeHelpText(), msg.messageId);
|
|
6717
|
+
return;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
async enterSafeMode(msg) {
|
|
6721
|
+
if (this.state.mode === "safe") {
|
|
6722
|
+
await this.sendMarkdown(
|
|
6723
|
+
msg.chatId,
|
|
6724
|
+
"\u5DF2\u5728\u5B89\u5168\u6A21\u5F0F\u4E2D\u3002\u53D1\u9001\u666E\u901A\u6D88\u606F\u4E0E dsh \u6838\u5FC3\u5BF9\u8BDD\uFF1B`/safemode exit` \u9000\u51FA\u3002",
|
|
6725
|
+
msg.messageId
|
|
6726
|
+
);
|
|
6727
|
+
return;
|
|
6728
|
+
}
|
|
6729
|
+
await this.sendMarkdown(
|
|
6730
|
+
msg.chatId,
|
|
6731
|
+
"\u6B63\u5728\u8FDB\u5165\u5B89\u5168\u6A21\u5F0F\uFF1A\u4EC5\u6302\u8F7D dsh \u4E3B\u6838\u5FC3\uFF08`dsh-base` + `dsh-headless`\uFF09\uFF0C\u4E0D\u52A0\u8F7D\u4EFB\u4F55\u7B2C\u4E09\u65B9\u63D2\u4EF6\u2026",
|
|
6732
|
+
msg.messageId
|
|
6733
|
+
);
|
|
6734
|
+
try {
|
|
6735
|
+
await ensureSafeProfile({
|
|
6736
|
+
home: this.options.home,
|
|
6737
|
+
dshProfile: this.state.dshProfile,
|
|
6738
|
+
env: this.options.env ?? process.env
|
|
6739
|
+
});
|
|
6740
|
+
const bin = this.dshBin;
|
|
6741
|
+
if (!bin) {
|
|
6742
|
+
await this.sendMarkdown(
|
|
6743
|
+
msg.chatId,
|
|
6744
|
+
"\u672A\u627E\u5230\u672C\u673A dsh \u5B89\u88C5\uFF0C\u65E0\u6CD5\u8FDB\u5165\u5B89\u5168\u6A21\u5F0F\u3002\u8BF7\u5148\u786E\u8BA4 `dsh` \u53EF\u7528\u3002",
|
|
6745
|
+
msg.messageId
|
|
6746
|
+
);
|
|
6747
|
+
return;
|
|
6748
|
+
}
|
|
6749
|
+
const probe = await (this.options.probeSafeProfileFn ?? probeSafeProfile)({
|
|
6750
|
+
bin,
|
|
6751
|
+
dshProfile: this.state.dshProfile,
|
|
6752
|
+
home: this.options.home,
|
|
6753
|
+
env: this.options.env ?? process.env
|
|
6754
|
+
});
|
|
6755
|
+
if (!probe.ok) {
|
|
6756
|
+
await this.sendMarkdown(
|
|
6757
|
+
msg.chatId,
|
|
6758
|
+
[
|
|
6759
|
+
"\u5B89\u5168\u6A21\u5F0F\u5C31\u7EEA\u68C0\u67E5\u5931\u8D25\uFF08dsh \u6838\u5FC3\u65E0\u6CD5\u89E3\u6790\uFF09\uFF1A",
|
|
6760
|
+
"",
|
|
6761
|
+
"```",
|
|
6762
|
+
(probe.error ?? "unknown error").slice(0, 1500),
|
|
6763
|
+
"```"
|
|
6764
|
+
].join("\n"),
|
|
6765
|
+
msg.messageId
|
|
6766
|
+
);
|
|
6767
|
+
return;
|
|
6768
|
+
}
|
|
6769
|
+
this.safeAdapter ??= this.options.adapter ?? new DshAdapter({
|
|
6770
|
+
command: "node",
|
|
6771
|
+
args: [bin, "--profile", this.state.safeProfile],
|
|
6772
|
+
stopGraceMs: 5e3
|
|
6773
|
+
});
|
|
6774
|
+
await this.setMode("safe");
|
|
6775
|
+
await this.sendMarkdown(
|
|
6776
|
+
msg.chatId,
|
|
6777
|
+
[
|
|
6778
|
+
"\u5B89\u5168\u6A21\u5F0F\u5DF2\u5C31\u7EEA\uFF1Adsh \u4E3B\u6838\u5FC3\u8FD0\u884C\u4E2D\uFF0C\u7B2C\u4E09\u65B9\u63D2\u4EF6\u672A\u52A0\u8F7D\u3002",
|
|
6779
|
+
"",
|
|
6780
|
+
"\u73B0\u5728\u53EF\u4EE5\u76F4\u63A5\u5BF9\u8BDD\u8FDB\u884C\u81EA\u6108\uFF08\u5B9A\u4F4D / \u4FEE\u590D / \u7981\u7528\u635F\u574F\u63D2\u4EF6\uFF09\uFF0C\u4F8B\u5982\uFF1A",
|
|
6781
|
+
"- \u201C\u5217\u51FA\u5F53\u524D profile \u5B89\u88C5\u7684\u63D2\u4EF6\u5E76\u68C0\u67E5\u54EA\u4E2A\u6700\u8FD1\u53D8\u574F\u201D",
|
|
6782
|
+
"- \u201C\u7528 `/safemode plugins` \u67E5\u770B\u63D2\u4EF6\u6E05\u5355\u201D",
|
|
6783
|
+
"- \u4FEE\u590D\u5B8C\u6210\u540E\u53D1\u9001 `/safemode exit` \u91CD\u542F\u5B8C\u6574 profile\u3002"
|
|
6784
|
+
].join("\n"),
|
|
6785
|
+
msg.messageId
|
|
6786
|
+
);
|
|
6787
|
+
} catch (error) {
|
|
6788
|
+
await this.sendMarkdown(
|
|
6789
|
+
msg.chatId,
|
|
6790
|
+
`\u8FDB\u5165\u5B89\u5168\u6A21\u5F0F\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`,
|
|
6791
|
+
msg.messageId
|
|
6792
|
+
);
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
async sendStatus(msg) {
|
|
6796
|
+
const snapshot = this.snapshot();
|
|
6797
|
+
await this.sendMarkdown(
|
|
6798
|
+
msg.chatId,
|
|
6799
|
+
[
|
|
6800
|
+
`\u6A21\u5F0F\uFF1A${snapshot.mode}`,
|
|
6801
|
+
`dsh profile\uFF1A${snapshot.dshProfile}`,
|
|
6802
|
+
`\u5B89\u5168 profile\uFF1A${snapshot.safeProfile}`,
|
|
6803
|
+
`dsh \u662F\u5426\u5728\u7EBF\uFF1A${snapshot.dshUp ? "\u662F" : "\u5426"}`,
|
|
6804
|
+
`\u5FC3\u8DF3\u9F84\uFF1A${snapshot.heartbeatAgeMs === void 0 ? "\u65E0" : `${snapshot.heartbeatAgeMs}ms`}`,
|
|
6805
|
+
`\u98DE\u4E66\u901A\u9053\uFF1A${snapshot.channelConnected ? "\u5B88\u62A4\u5DF2\u63A5\u7BA1" : "\u672A\u8FDE\u63A5"}`,
|
|
6806
|
+
`dsh bin\uFF1A${snapshot.dshBin ?? "\u672A\u53D1\u73B0"}`,
|
|
6807
|
+
`\u5B88\u62A4 pid\uFF1A${snapshot.pid}`,
|
|
6808
|
+
`\u5DF2\u89C2\u5BDF\u8FC7 dsh \u8FD0\u884C\uFF1A${snapshot.profileSeenUp ? "\u662F" : "\u5426"}`
|
|
6809
|
+
].join("\n"),
|
|
6810
|
+
msg.messageId
|
|
6811
|
+
);
|
|
6812
|
+
}
|
|
6813
|
+
async sendPluginList(msg) {
|
|
6814
|
+
const bin = this.dshBin;
|
|
6815
|
+
if (!bin) {
|
|
6816
|
+
await this.sendMarkdown(
|
|
6817
|
+
msg.chatId,
|
|
6818
|
+
"\u672A\u627E\u5230\u672C\u673A dsh \u5B89\u88C5\uFF0C\u65E0\u6CD5\u5217\u51FA\u63D2\u4EF6\u3002",
|
|
6819
|
+
msg.messageId
|
|
6820
|
+
);
|
|
6821
|
+
return;
|
|
6822
|
+
}
|
|
6823
|
+
const run = this.options.runPluginList ?? defaultRunPluginList;
|
|
6824
|
+
const result = await run(bin, this.state.dshProfile);
|
|
6825
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
|
|
6826
|
+
await this.sendMarkdown(
|
|
6827
|
+
msg.chatId,
|
|
6828
|
+
[
|
|
6829
|
+
`profile \`${this.state.dshProfile}\` \u5DF2\u5B89\u88C5\u7684\u63D2\u4EF6\uFF08\u4F9D\u8D56\u6E05\u5355\uFF09\uFF1A`,
|
|
6830
|
+
"",
|
|
6831
|
+
"```",
|
|
6832
|
+
(output || "\uFF08\u7A7A\uFF09").slice(0, 3e3),
|
|
6833
|
+
"```"
|
|
6834
|
+
].join("\n"),
|
|
6835
|
+
msg.messageId
|
|
6836
|
+
);
|
|
6837
|
+
}
|
|
6838
|
+
async exitSafeMode(msg) {
|
|
6839
|
+
if (this.state.mode !== "safe") {
|
|
6840
|
+
await this.sendMarkdown(
|
|
6841
|
+
msg.chatId,
|
|
6842
|
+
"\u5F53\u524D\u4E0D\u5728\u5B89\u5168\u6A21\u5F0F\uFF0C\u65E0\u9700\u9000\u51FA\u3002",
|
|
6843
|
+
msg.messageId
|
|
6844
|
+
);
|
|
6845
|
+
return;
|
|
6846
|
+
}
|
|
6847
|
+
const bin = this.dshBin;
|
|
6848
|
+
if (!bin) {
|
|
6849
|
+
await this.sendMarkdown(
|
|
6850
|
+
msg.chatId,
|
|
6851
|
+
"\u672A\u627E\u5230\u672C\u673A dsh \u5B89\u88C5\uFF0C\u65E0\u6CD5\u91CD\u542F\u5B8C\u6574 profile\u3002",
|
|
6852
|
+
msg.messageId
|
|
6853
|
+
);
|
|
6854
|
+
return;
|
|
6855
|
+
}
|
|
6856
|
+
const spawn7 = this.options.spawnDetachedFn ?? spawnDetached;
|
|
6857
|
+
const spawned = spawn7("node", [bin, "--profile", this.state.dshProfile]);
|
|
6858
|
+
if (spawned.pid !== void 0) this.state.relaunchedPid = spawned.pid;
|
|
6859
|
+
await this.save();
|
|
6860
|
+
await this.sendMarkdown(
|
|
6861
|
+
msg.chatId,
|
|
6862
|
+
[
|
|
6863
|
+
`\u6B63\u5728\u9000\u51FA\u5B89\u5168\u6A21\u5F0F\u5E76\u91CD\u542F\u5B8C\u6574 profile\uFF08\`${this.state.dshProfile}\`\uFF09\u2026`,
|
|
6864
|
+
"\u5B88\u62A4\u8FDB\u7A0B\u5C06\u65AD\u5F00\u98DE\u4E66\u8FDE\u63A5\u5E76\u628A\u901A\u9053\u4EA4\u8FD8\u7ED9 dsh \u6865\u63A5\u5F15\u64CE\u3002"
|
|
6865
|
+
].join("\n"),
|
|
6866
|
+
msg.messageId
|
|
6867
|
+
);
|
|
6868
|
+
await delay(this.options.sendDelayMs);
|
|
6869
|
+
this.transcripts.clear();
|
|
6870
|
+
await this.disconnectChannel();
|
|
6871
|
+
}
|
|
6872
|
+
async runSafeTask(scope, msg) {
|
|
6873
|
+
const adapter = this.safeAdapter;
|
|
6874
|
+
if (!adapter) {
|
|
6875
|
+
await this.sendMarkdown(
|
|
6876
|
+
msg.chatId,
|
|
6877
|
+
"\u5B89\u5168\u6A21\u5F0F\u672A\u5C31\u7EEA\uFF0C\u8BF7\u5148\u53D1\u9001 `/safemode`\u3002",
|
|
6878
|
+
msg.messageId
|
|
6879
|
+
);
|
|
6880
|
+
return;
|
|
6881
|
+
}
|
|
6882
|
+
const transcript = this.transcripts.get(scope) ?? [];
|
|
6883
|
+
const prompt = buildSafePrompt(transcript, msg.content);
|
|
6884
|
+
await this.sendMarkdown(
|
|
6885
|
+
msg.chatId,
|
|
6886
|
+
"\uFF08\u5B89\u5168\u6A21\u5F0F \xB7 dsh \u6838\u5FC3\u5904\u7406\u4E2D\u2026\uFF09",
|
|
6887
|
+
msg.messageId
|
|
6888
|
+
);
|
|
6889
|
+
const run = adapter.run({
|
|
6890
|
+
runId: randomUUID5(),
|
|
6891
|
+
prompt,
|
|
6892
|
+
cwd: this.config?.workspaces.default,
|
|
6893
|
+
sessionId: void 0,
|
|
6894
|
+
model: void 0,
|
|
6895
|
+
images: void 0,
|
|
6896
|
+
stopGraceMs: 5e3
|
|
6897
|
+
});
|
|
6898
|
+
const textParts = [];
|
|
6899
|
+
let finalText;
|
|
6900
|
+
let errorText;
|
|
6901
|
+
let terminal = false;
|
|
6902
|
+
try {
|
|
6903
|
+
for await (const event of run.events) {
|
|
6904
|
+
if (event.type === "text" && event.delta) textParts.push(event.delta);
|
|
6905
|
+
if (event.type === "final_text") finalText = event.content;
|
|
6906
|
+
if (event.type === "error") {
|
|
6907
|
+
errorText = event.message;
|
|
6908
|
+
terminal = true;
|
|
6909
|
+
}
|
|
6910
|
+
if (event.type === "done") terminal = true;
|
|
6911
|
+
}
|
|
6912
|
+
} catch (error) {
|
|
6913
|
+
errorText = error instanceof Error ? error.message : String(error);
|
|
6914
|
+
terminal = true;
|
|
6915
|
+
}
|
|
6916
|
+
if (!terminal) {
|
|
6917
|
+
await run.waitForExit(SAFE_RUN_TIMEOUT_MS);
|
|
6918
|
+
}
|
|
6919
|
+
if (errorText) {
|
|
6920
|
+
await this.sendMarkdown(
|
|
6921
|
+
msg.chatId,
|
|
6922
|
+
`\uFF08\u5B89\u5168\u6A21\u5F0F \xB7 dsh \u6838\u5FC3\u9519\u8BEF\uFF09
|
|
6923
|
+
${errorText.slice(0, 2e3)}`,
|
|
6924
|
+
msg.messageId
|
|
6925
|
+
);
|
|
6926
|
+
return;
|
|
6927
|
+
}
|
|
6928
|
+
const answer = finalText ?? textParts.join("").trim();
|
|
6929
|
+
if (!answer) {
|
|
6930
|
+
await this.sendMarkdown(
|
|
6931
|
+
msg.chatId,
|
|
6932
|
+
"\uFF08\u5B89\u5168\u6A21\u5F0F \xB7 dsh \u6838\u5FC3\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09",
|
|
6933
|
+
msg.messageId
|
|
6934
|
+
);
|
|
6935
|
+
return;
|
|
6936
|
+
}
|
|
6937
|
+
await this.sendMarkdown(msg.chatId, answer, msg.messageId);
|
|
6938
|
+
this.transcripts.set(scope, pushTranscript(transcript, [
|
|
6939
|
+
{ role: "user", content: msg.content },
|
|
6940
|
+
{ role: "assistant", content: answer }
|
|
6941
|
+
]));
|
|
6942
|
+
}
|
|
6943
|
+
};
|
|
6944
|
+
function buildSafePrompt(transcript, current) {
|
|
6945
|
+
if (transcript.length === 0) return current;
|
|
6946
|
+
const history = transcript.map((entry) => `${entry.role === "user" ? "\u7528\u6237" : "\u52A9\u624B"}: ${entry.content}`).join("\n");
|
|
6947
|
+
return [
|
|
6948
|
+
"\u4EE5\u4E0B\u662F\u672C\u6B21\u5B89\u5168\u6A21\u5F0F\u5BF9\u8BDD\u7684\u4E0A\u4E0B\u6587\uFF08\u7528\u4E8E\u8FDE\u7EED\u6027\uFF0C\u4E0D\u662F\u65B0\u4EFB\u52A1\u6307\u4EE4\uFF09\uFF1A",
|
|
6949
|
+
"",
|
|
6950
|
+
history,
|
|
6951
|
+
"",
|
|
6952
|
+
`\u7528\u6237: ${current}`
|
|
6953
|
+
].join("\n");
|
|
6954
|
+
}
|
|
6955
|
+
function pushTranscript(existing, entries) {
|
|
6956
|
+
return [...existing, ...entries].slice(-MAX_TRANSCRIPT_ENTRIES);
|
|
6957
|
+
}
|
|
6958
|
+
async function defaultRunPluginList(bin, dshProfile) {
|
|
6959
|
+
return captureOutput("node", [bin, "plugin", "--profile", dshProfile, "list"], 6e4);
|
|
6960
|
+
}
|
|
6961
|
+
function delay(ms) {
|
|
6962
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
6963
|
+
}
|
|
6964
|
+
function parseFreshness2(value) {
|
|
6965
|
+
const raw = value?.trim();
|
|
6966
|
+
if (!raw) return 6e5;
|
|
6967
|
+
const parsed = Number(raw);
|
|
6968
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : 6e5;
|
|
6969
|
+
}
|
|
6970
|
+
function defined(input) {
|
|
6971
|
+
const output = {};
|
|
6972
|
+
for (const [key, value] of Object.entries(input)) {
|
|
6973
|
+
if (value !== void 0) output[key] = value;
|
|
6974
|
+
}
|
|
6975
|
+
return output;
|
|
6976
|
+
}
|
|
6977
|
+
function guardianLayoutFor(env, bridgeProfile) {
|
|
6978
|
+
const paths = resolveAppPaths(env.home);
|
|
6979
|
+
return {
|
|
6980
|
+
paths,
|
|
6981
|
+
stateFile: join15(paths.root, "guardian.json"),
|
|
6982
|
+
heartbeatFile: paths.profilePath(bridgeProfile, "guardian", "heartbeat.json"),
|
|
6983
|
+
configFile: paths.configFile
|
|
6984
|
+
};
|
|
6985
|
+
}
|
|
6986
|
+
async function buildGuardianService(env, overrides = {}) {
|
|
6987
|
+
const paths = resolveAppPaths(env.home);
|
|
6988
|
+
const stateFile = join15(paths.root, "guardian.json");
|
|
6989
|
+
const fallback = newGuardianState({
|
|
6990
|
+
dshProfile: env.guardianProfile,
|
|
6991
|
+
bridgeProfile: env.guardianBridgeProfile
|
|
6992
|
+
});
|
|
6993
|
+
const state = await loadGuardianState(stateFile, fallback);
|
|
6994
|
+
return new GuardianService({
|
|
6995
|
+
stateFile,
|
|
6996
|
+
configFile: paths.configFile,
|
|
6997
|
+
heartbeatFile: paths.profilePath(state.bridgeProfile, "guardian", "heartbeat.json"),
|
|
6998
|
+
home: homedir8(),
|
|
6999
|
+
env: process.env,
|
|
7000
|
+
dshProfile: state.dshProfile,
|
|
7001
|
+
bridgeProfile: state.bridgeProfile,
|
|
7002
|
+
safeProfile: state.safeProfile,
|
|
7003
|
+
pollMs: env.guardianPollMs,
|
|
7004
|
+
staleMs: env.guardianStaleMs,
|
|
7005
|
+
engineDeadMs: env.guardianEngineDeadMs,
|
|
7006
|
+
...overrides
|
|
7007
|
+
});
|
|
7008
|
+
}
|
|
7009
|
+
|
|
7010
|
+
// src/cli/commands/guardian.ts
|
|
7011
|
+
function envWithOverrides(options) {
|
|
7012
|
+
const env = loadRuntimeEnv(process.env);
|
|
7013
|
+
if (options.dshProfile) env.guardianProfile = options.dshProfile;
|
|
7014
|
+
if (options.bridgeProfile) env.guardianBridgeProfile = options.bridgeProfile;
|
|
7015
|
+
return env;
|
|
7016
|
+
}
|
|
7017
|
+
function printLines(lines) {
|
|
7018
|
+
process.stdout.write(`${lines.join("\n")}
|
|
7019
|
+
`);
|
|
7020
|
+
}
|
|
7021
|
+
async function runGuardian(options = {}) {
|
|
7022
|
+
const env = envWithOverrides(options);
|
|
7023
|
+
if (env.guardianDisabled) {
|
|
7024
|
+
printLines(["\u5B89\u5168\u7F51\u5B88\u62A4\u5DF2\u7981\u7528\uFF08DSH_LARK_GUARDIAN_DISABLED=1\uFF09\uFF0C\u8FDB\u7A0B\u9000\u51FA\u3002"]);
|
|
7025
|
+
return;
|
|
7026
|
+
}
|
|
7027
|
+
const service = await buildGuardianService(env);
|
|
7028
|
+
await service.start();
|
|
7029
|
+
printLines([
|
|
7030
|
+
`\u5B89\u5168\u7F51\u5B88\u62A4\u8FD0\u884C\u4E2D\uFF08pid=${process.pid}\uFF0Cdsh profile=${service.snapshot().dshProfile}\uFF09\u3002`,
|
|
7031
|
+
"dsh \u6B63\u5E38\u8FD0\u884C\u65F6\u4FDD\u6301\u9759\u9ED8\uFF1Bdsh \u4E0B\u7EBF\u540E\u81EA\u52A8\u63A5\u7BA1\u98DE\u4E66\u901A\u9053\uFF0C\u7B49\u5F85 /safemode \u63A7\u5236\u4FE1\u53F7\u3002"
|
|
7032
|
+
]);
|
|
7033
|
+
await waitForShutdown2();
|
|
7034
|
+
await service.stop();
|
|
7035
|
+
}
|
|
7036
|
+
async function installGuardianCommand(options = {}) {
|
|
7037
|
+
const env = envWithOverrides(options);
|
|
7038
|
+
const result = await installGuardian({ env, ...options.dshProfile ? { dshProfile: options.dshProfile } : {} });
|
|
7039
|
+
printResult(result);
|
|
7040
|
+
if (!result.ok) process.exitCode = 1;
|
|
7041
|
+
}
|
|
7042
|
+
async function uninstallGuardianCommand(options = {}) {
|
|
7043
|
+
const env = envWithOverrides(options);
|
|
7044
|
+
const result = await uninstallGuardian({ env });
|
|
7045
|
+
printResult(result);
|
|
7046
|
+
}
|
|
7047
|
+
async function statusGuardianCommand(options = {}) {
|
|
7048
|
+
const env = envWithOverrides(options);
|
|
7049
|
+
const paths = resolveAppPaths(env.home);
|
|
7050
|
+
const fallback = newGuardianState({
|
|
7051
|
+
dshProfile: env.guardianProfile,
|
|
7052
|
+
bridgeProfile: env.guardianBridgeProfile
|
|
7053
|
+
});
|
|
7054
|
+
const state = await loadGuardianState(join16(paths.root, "guardian.json"), fallback);
|
|
7055
|
+
const layout = guardianLayoutFor(env, state.bridgeProfile);
|
|
7056
|
+
const heartbeat = await readHeartbeat(layout.heartbeatFile);
|
|
7057
|
+
const processFound = await findProfileProcess(state.dshProfile);
|
|
7058
|
+
const up = isHeartbeatFresh(heartbeat, env.guardianStaleMs) || processFound !== void 0;
|
|
7059
|
+
printLines([
|
|
7060
|
+
"\u5B89\u5168\u7F51\u5B88\u62A4\u72B6\u6001",
|
|
7061
|
+
"---------------",
|
|
7062
|
+
`\u6A21\u5F0F\uFF1A${state.mode}`,
|
|
7063
|
+
`dsh profile\uFF1A${state.dshProfile}`,
|
|
7064
|
+
`\u5B89\u5168 profile\uFF1A${state.safeProfile}`,
|
|
7065
|
+
`\u6865\u63A5 profile\uFF1A${state.bridgeProfile}`,
|
|
7066
|
+
`dsh \u662F\u5426\u5728\u7EBF\uFF1A${up ? "\u662F" : "\u5426"}${processFound ? `\uFF08pid ${processFound.pid}\uFF09` : ""}`,
|
|
7067
|
+
`\u5FC3\u8DF3\u9F84\uFF1A${heartbeat ? `${heartbeatAgeMs(heartbeat)}ms` : "\u65E0"}`,
|
|
7068
|
+
`\u5DF2\u89C2\u5BDF\u8FC7 dsh \u8FD0\u884C\uFF1A${state.profileSeenUp ? "\u662F" : "\u5426"}`,
|
|
7069
|
+
`\u5B88\u62A4\u8FDB\u7A0B pid\uFF1A${process.pid}`,
|
|
7070
|
+
`\u72B6\u6001\u6587\u4EF6\uFF1A${layout.stateFile}`,
|
|
7071
|
+
`\u5FC3\u8DF3\u6587\u4EF6\uFF1A${layout.heartbeatFile}`
|
|
7072
|
+
]);
|
|
7073
|
+
}
|
|
7074
|
+
function printResult(result) {
|
|
7075
|
+
for (const message of result.messages) {
|
|
7076
|
+
process.stdout.write(`${message}
|
|
7077
|
+
`);
|
|
7078
|
+
}
|
|
7079
|
+
}
|
|
7080
|
+
function waitForShutdown2() {
|
|
7081
|
+
return new Promise((resolve5) => {
|
|
7082
|
+
const shutdown = () => {
|
|
7083
|
+
process.off("SIGINT", shutdown);
|
|
7084
|
+
process.off("SIGTERM", shutdown);
|
|
7085
|
+
resolve5();
|
|
7086
|
+
};
|
|
7087
|
+
process.once("SIGINT", shutdown);
|
|
7088
|
+
process.once("SIGTERM", shutdown);
|
|
7089
|
+
});
|
|
5749
7090
|
}
|
|
5750
7091
|
|
|
5751
7092
|
// src/cli.ts
|
|
@@ -5762,8 +7103,11 @@ function buildProgram() {
|
|
|
5762
7103
|
program.name("dsh-lark-bot").description("Bridge DeepSeek Harness into Feishu / Lark").version(packageVersion(), "-v, --version");
|
|
5763
7104
|
program.command("setup").description(
|
|
5764
7105
|
"Install this package as a standard dsh profile bundle (single install path)"
|
|
5765
|
-
).option("--profile <name>", "dsh profile to install into (default: dsh-lark)").action(async (opts) => {
|
|
5766
|
-
await runSetup({
|
|
7106
|
+
).option("--profile <name>", "dsh profile to install into (default: dsh-lark)").option("--guardian", "also install the safety-net guardian service").action(async (opts) => {
|
|
7107
|
+
await runSetup({
|
|
7108
|
+
...opts.profile ? { profile: opts.profile } : {},
|
|
7109
|
+
...opts.guardian ? { guardian: true } : {}
|
|
7110
|
+
});
|
|
5767
7111
|
});
|
|
5768
7112
|
program.command("doctor").description("Run local diagnostics").option("--profile <name>", "profile name").option("--workspace <path>", "initial working directory").option("--app-id <id>", "existing Lark/Feishu app id").option("--app-secret <secret>", "existing Lark/Feishu app secret").option("--tenant <tenant>", "feishu or lark").action(async (opts) => {
|
|
5769
7113
|
await runDoctor({ ...opts, version: packageVersion() });
|
|
@@ -5773,6 +7117,21 @@ function buildProgram() {
|
|
|
5773
7117
|
).action(async (opts) => {
|
|
5774
7118
|
await runBot(opts);
|
|
5775
7119
|
});
|
|
7120
|
+
const guardian = program.command("guardian").description(
|
|
7121
|
+
"Safety-net guardian: a minimal process independent of dsh that keeps the Feishu rescue entrance alive"
|
|
7122
|
+
);
|
|
7123
|
+
guardian.command("run").description("Run the guardian in the foreground (system service entry point)").option("--dsh-profile <name>", "dsh profile to watch / relaunch (default from state)").option("--bridge-profile <name>", "bridge state profile with Feishu credentials").action(async (opts) => {
|
|
7124
|
+
await runGuardian(opts);
|
|
7125
|
+
});
|
|
7126
|
+
guardian.command("install").description("Install the guardian as a system-level resident service").option("--dsh-profile <name>", "dsh profile to watch / relaunch (default: dsh-lark)").option("--bridge-profile <name>", "bridge state profile (default: default)").action(async (opts) => {
|
|
7127
|
+
await installGuardianCommand(opts);
|
|
7128
|
+
});
|
|
7129
|
+
guardian.command("uninstall").description("Remove the system service entry (state file is kept)").action(async () => {
|
|
7130
|
+
await uninstallGuardianCommand();
|
|
7131
|
+
});
|
|
7132
|
+
guardian.command("status").description("Show guardian / dsh / safe-mode state").option("--dsh-profile <name>", "dsh profile to inspect").option("--bridge-profile <name>", "bridge state profile to inspect").action(async (opts) => {
|
|
7133
|
+
await statusGuardianCommand(opts);
|
|
7134
|
+
});
|
|
5776
7135
|
return program;
|
|
5777
7136
|
}
|
|
5778
7137
|
async function main(argv = process.argv) {
|