@modelzen/feishu-codex-bridge 0.6.5 → 0.6.7-test.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 +32 -8
- package/dist/cli.js +372 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
|
|
5
5
|
[](https://www.npmjs.com/package/@modelzen/feishu-codex-bridge)
|
|
6
6
|
[](https://github.com/modelzen/feishu-codex-bridge/blob/main/LICENSE)
|
|
7
|
+
[](https://bridge.vonvon.cc)
|
|
8
|
+
|
|
9
|
+
🌐 **官网:<https://bridge.vonvon.cc>**
|
|
7
10
|
|
|
8
11
|
> 把飞书 / Lark 桥接到你本机的 [Codex](https://github.com/openai/codex) 或 [Claude Code](https://www.anthropic.com/claude-code),在群里 @ 机器人就能让它在指定项目目录里干活,结果以流式 Markdown 卡片实时回到群里。
|
|
9
12
|
>
|
|
@@ -23,22 +26,42 @@
|
|
|
23
26
|
|
|
24
27
|
## ⚡ 安装
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
两步打开本机网页控制台。装好 Bridge、打开控制台,再在页面里启动后台服务、添加第一个 Bridge 机器人。
|
|
27
30
|
|
|
28
31
|
```bash
|
|
29
|
-
|
|
30
|
-
feishu-codex-bridge
|
|
32
|
+
# 1. 全局安装 Bridge
|
|
33
|
+
npm i -g @modelzen/feishu-codex-bridge
|
|
34
|
+
|
|
35
|
+
# 2. 打开本机网页控制台
|
|
36
|
+
# 启动服务、添加好机器人后,这条前台命令即可 Ctrl+C 关闭
|
|
37
|
+
feishu-codex-bridge web
|
|
31
38
|
```
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
不想自己敲?把下面这段发给 **Codex / Claude** 等 AI agent,让它替你装好并跑起来:
|
|
34
41
|
|
|
35
42
|
```text
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
帮我在这台电脑上安装并跑起来 feishu-codex-bridge:
|
|
44
|
+
|
|
45
|
+
1) 先 node -v 确认有 Node.js(≥18),没有就先装好;
|
|
46
|
+
2) 再 codex --version 确认已经安装了 codex CLI,没有就先装好;
|
|
47
|
+
3) 全局安装:
|
|
48
|
+
npm i -g @modelzen/feishu-codex-bridge
|
|
49
|
+
4) 前台运行,打开本机 Web 控制台:
|
|
50
|
+
feishu-codex-bridge web
|
|
51
|
+
它一启动会打印一个 http://127.0.0.1:xxxx/?token=... 的链接。
|
|
52
|
+
5) 把那个链接发给我;
|
|
53
|
+
6) 然后用文字告诉我接下来怎么做:
|
|
54
|
+
- 我在浏览器打开这个链接;
|
|
55
|
+
- 点页面上的「启动」按钮,把后台服务跑起来;
|
|
56
|
+
- 启动后页面会自动进入可写控制台,在里面扫码添加我的第一个飞书机器人;
|
|
57
|
+
- 成功创建机器人后,前台那条 feishu-codex-bridge web 就可以 Ctrl+C 关掉,不影响后台服务。
|
|
39
58
|
```
|
|
40
59
|
|
|
41
|
-
|
|
60
|
+
### 三分钟,看它入职你的飞书
|
|
61
|
+
|
|
62
|
+
从全局装包、打开本机网页控制台,到扫码建机器人、私聊报到,一整段带解说录屏。点击下面封面去 B 站观看高清版。
|
|
63
|
+
|
|
64
|
+
[](https://www.bilibili.com/video/BV1xP7V6fESb)
|
|
42
65
|
|
|
43
66
|
---
|
|
44
67
|
|
|
@@ -140,6 +163,7 @@ npm test # vitest
|
|
|
140
163
|
|
|
141
164
|
## 💬 文档 & 交流
|
|
142
165
|
|
|
166
|
+
- 🌐 **官网**:<https://bridge.vonvon.cc>
|
|
143
167
|
- 🎀 **图文介绍**:<https://my.feishu.cn/docx/AFKNdf4QaooL5OxSR8bc5H7vn7b> —— 配大量截图,讲清它在飞书里长什么样、怎么用。
|
|
144
168
|
- 📖 **命令手册**:<https://my.feishu.cn/wiki/PZ23wGr7JiKK5RkIG4rcZXzGn5g> —— 各场景可用命令速查。
|
|
145
169
|
- 🐛 **反馈 / 贡献**:<https://github.com/modelzen/feishu-codex-bridge/issues>
|
package/dist/cli.js
CHANGED
|
@@ -5538,7 +5538,11 @@ function buildResumeCard(state) {
|
|
|
5538
5538
|
if (state.threads.length === 0) {
|
|
5539
5539
|
elements.push(md("_\u8BE5\u76EE\u5F55\u4E0B\u8FD8\u6CA1\u6709\u5386\u53F2\u4F1A\u8BDD\u3002\u76F4\u63A5 @\u6211 \u5373\u53EF\u65B0\u5EFA\u3002_"));
|
|
5540
5540
|
} else {
|
|
5541
|
-
elements.push(
|
|
5541
|
+
elements.push(
|
|
5542
|
+
note(
|
|
5543
|
+
state.flat ? "\u70B9\u4E00\u6761\u5373\u5207\u56DE \u2014\u2014 \u5C31\u5730\u7EE7\u7EED\uFF0C\u4E0D\u53E6\u8D77\u8BDD\u9898\u3002" : "\u70B9\u4E00\u6761\u5373\u6062\u590D \u2014\u2014 \u5728\u65B0\u8BDD\u9898\u91CC\u6253\u5F00\u5386\u53F2\u3001\u53EF\u76F4\u63A5\u7EE7\u7EED\u3002"
|
|
5544
|
+
)
|
|
5545
|
+
);
|
|
5542
5546
|
for (const t of state.threads) {
|
|
5543
5547
|
const title = (t.name?.trim() || t.preview.trim() || "(\u65E0\u6458\u8981)").replace(/\s+/g, " ");
|
|
5544
5548
|
const label = `\u21A9\uFE0F ${pickerTime(t.updatedAt || t.createdAt)} \xB7 ${truncate(title, RESUME_TITLE_MAX)}`;
|
|
@@ -5551,7 +5555,19 @@ function buildResumeLaunchingCard(state) {
|
|
|
5551
5555
|
return card([md("\u23F3 \u6B63\u5728\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u2026"), note(metaNote(state))], { summary: "\u6062\u590D\u4E2D" });
|
|
5552
5556
|
}
|
|
5553
5557
|
function buildResumeDoneCard(state) {
|
|
5554
|
-
|
|
5558
|
+
const line = state.flat ? "\u21A9\uFE0F \u5DF2\u5207\u56DE \u2014\u2014 \u5C31\u5730\u7EE7\u7EED\uFF0C\u4E0A\u9762\u7684\u5386\u53F2\u6D88\u606F\u4FDD\u7559\u3002" : "\u2705 \u5DF2\u6062\u590D \u2014\u2014 \u5DF2\u5728\u4E0A\u65B9\u65B0\u8BDD\u9898\u6253\u5F00\uFF0C\u53EF\u76F4\u63A5\u7EE7\u7EED\u3002";
|
|
5559
|
+
return card([md(line), note(metaNote(state))], { summary: "\u5DF2\u6062\u590D" });
|
|
5560
|
+
}
|
|
5561
|
+
function buildClearedCard() {
|
|
5562
|
+
return card(
|
|
5563
|
+
[
|
|
5564
|
+
md("\u{1F9F9} **\u5DF2\u5F00\u542F\u5168\u65B0\u4F1A\u8BDD**"),
|
|
5565
|
+
md("\u2705 \u4E0A\u4E0B\u6587\u5DF2\u6E05\u7A7A \u2014\u2014 \u6211\u4ECE\u8FD9\u91CC\u91CD\u65B0\u5F00\u59CB\uFF0C\u4E0D\u518D\u53C2\u8003\u4E0A\u9762\u7684\u5BF9\u8BDD\u3002"),
|
|
5566
|
+
md("\u{1F4AC} \u98DE\u4E66\u804A\u5929\u8BB0\u5F55\u4E0D\u53D7\u5F71\u54CD \u2014\u2014 \u4E0A\u9762\u7684\u6D88\u606F\u90FD\u8FD8\u5728\uFF0C\u53EA\u662F\u6211\u4E0D\u518D\u8BFB\u53D6\u5B83\u4EEC\u3002"),
|
|
5567
|
+
md("\u{1F5C2}\uFE0F \u521A\u624D\u90A3\u6BB5\u5DF2\u5F52\u6863 \u2014\u2014 \u53D1 `/resume` \u968F\u65F6\u5207\u56DE\u7EE7\u7EED\u3002")
|
|
5568
|
+
],
|
|
5569
|
+
{ summary: "\u5DF2\u6E05\u7A7A\u4E0A\u4E0B\u6587" }
|
|
5570
|
+
);
|
|
5555
5571
|
}
|
|
5556
5572
|
function buildResumeErrorCard(state, message) {
|
|
5557
5573
|
return card([md(`\u274C \u6062\u590D\u5931\u8D25\uFF1A${truncate(message, 200)}`), note(metaNote(state))], { summary: "\u6062\u590D\u5931\u8D25" });
|
|
@@ -5608,6 +5624,8 @@ function buildHelpCard(scope, noMention = true, isAdmin2 = false, caps) {
|
|
|
5608
5624
|
if (showGoal) lines.push(goalLine);
|
|
5609
5625
|
lines.push("\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6", "\xB7 `/context` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4");
|
|
5610
5626
|
if (showCompact) lines.push(compactLine);
|
|
5627
|
+
if (isAdmin2) lines.push("\xB7 `/clear` \u2192 \u6E05\u7A7A\u4E0A\u4E0B\u6587\uFF0C\u5F00\u4E00\u6BB5\u5168\u65B0\u4F1A\u8BDD\uFF08\u98DE\u4E66\u6D88\u606F\u4FDD\u7559\uFF09");
|
|
5628
|
+
if (isAdmin2 && showResume) lines.push("\xB7 `/resume` \u2192 \u5207\u56DE\u5386\u53F2\u4F1A\u8BDD");
|
|
5611
5629
|
if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
5612
5630
|
lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
5613
5631
|
elements.push(md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"), hr(), md(lines.join("\n")));
|
|
@@ -5651,6 +5669,8 @@ function buildWelcomeCard(kind, docUrl, noMention = true, caps, agentName = "Cod
|
|
|
5651
5669
|
talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"),
|
|
5652
5670
|
...showGoal ? [goalLine] : [],
|
|
5653
5671
|
"\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6",
|
|
5672
|
+
"\xB7 `/clear` \u2192 \u6E05\u7A7A\u4E0A\u4E0B\u6587\uFF0C\u5F00\u65B0\u4F1A\u8BDD\uFF08\u7BA1\u7406\u5458\uFF0C\u98DE\u4E66\u6D88\u606F\u4FDD\u7559\uFF09",
|
|
5673
|
+
...showResume ? ["\xB7 `/resume` \u2192 \u5207\u56DE\u5386\u53F2\u4F1A\u8BDD\uFF08\u7BA1\u7406\u5458\uFF09"] : [],
|
|
5654
5674
|
"\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09",
|
|
5655
5675
|
"\xB7 `/help` \u2192 \u547D\u4EE4\u901F\u67E5\u5361"
|
|
5656
5676
|
].join("\n")
|
|
@@ -8489,7 +8509,7 @@ function withCodexHooksFeature(text) {
|
|
|
8489
8509
|
}
|
|
8490
8510
|
|
|
8491
8511
|
// src/service/update.ts
|
|
8492
|
-
import { existsSync as
|
|
8512
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
|
|
8493
8513
|
import { dirname as dirname9, join as join15, resolve as resolve6 } from "path";
|
|
8494
8514
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
8495
8515
|
|
|
@@ -8503,7 +8523,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
8503
8523
|
|
|
8504
8524
|
// src/service/common.ts
|
|
8505
8525
|
init_paths();
|
|
8506
|
-
import { createReadStream, statSync as statSync2 } from "fs";
|
|
8526
|
+
import { appendFileSync, createReadStream, statSync as statSync2 } from "fs";
|
|
8507
8527
|
import { appendFile, mkdir as mkdir7, readFile as readFile9 } from "fs/promises";
|
|
8508
8528
|
import { dirname as dirname6, join as join11, resolve as resolve4 } from "path";
|
|
8509
8529
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -8518,6 +8538,13 @@ async function ensureLogFiles() {
|
|
|
8518
8538
|
await appendFile(serviceStdoutPath(), "");
|
|
8519
8539
|
await appendFile(serviceStderrPath(), "");
|
|
8520
8540
|
}
|
|
8541
|
+
function appendServiceErr(tag, line) {
|
|
8542
|
+
try {
|
|
8543
|
+
appendFileSync(serviceStderrPath(), `[${(/* @__PURE__ */ new Date()).toISOString()}] [${tag}] ${line}
|
|
8544
|
+
`);
|
|
8545
|
+
} catch {
|
|
8546
|
+
}
|
|
8547
|
+
}
|
|
8521
8548
|
function resolveCliBinPath() {
|
|
8522
8549
|
const distDir = dirname6(fileURLToPath2(import.meta.url));
|
|
8523
8550
|
return resolve4(distDir, "..", "bin", "feishu-codex-bridge.mjs");
|
|
@@ -8727,9 +8754,10 @@ function launchctlError(command, result) {
|
|
|
8727
8754
|
// src/service/win-startup.ts
|
|
8728
8755
|
init_paths();
|
|
8729
8756
|
import { spawn as spawn3, spawnSync as spawnSync2 } from "child_process";
|
|
8730
|
-
import { openSync, readFileSync as readFileSync3, rmSync, writeFileSync } from "fs";
|
|
8757
|
+
import { existsSync as existsSync7, openSync, readFileSync as readFileSync3, renameSync, rmSync, writeFileSync } from "fs";
|
|
8731
8758
|
import { mkdir as mkdir9, writeFile as writeFile8 } from "fs/promises";
|
|
8732
8759
|
import { join as join13 } from "path";
|
|
8760
|
+
var RELAUNCH_TASK_NAME = "feishu-codex-bridge-relaunch";
|
|
8733
8761
|
var RUN_KEY_PATH = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
8734
8762
|
var RUN_KEY_NAME = "feishu-codex-bridge";
|
|
8735
8763
|
var SERVICE_ENV_FLAG = "FEISHU_CODEX_BRIDGE_SERVICE";
|
|
@@ -8759,14 +8787,16 @@ function buildLauncherVbs() {
|
|
|
8759
8787
|
""
|
|
8760
8788
|
].join("\r\n");
|
|
8761
8789
|
}
|
|
8762
|
-
function startNow() {
|
|
8790
|
+
function startNow(envPath) {
|
|
8763
8791
|
const out = openSync(serviceStdoutPath(), "a");
|
|
8764
8792
|
const err = openSync(serviceStderrPath(), "a");
|
|
8793
|
+
const overrides = { [SERVICE_ENV_FLAG]: "1" };
|
|
8794
|
+
if (envPath) overrides.PATH = envPath;
|
|
8765
8795
|
const child = spawn3(process.execPath, [resolveCliBinPath(), "run"], {
|
|
8766
8796
|
detached: true,
|
|
8767
8797
|
windowsHide: true,
|
|
8768
8798
|
stdio: ["ignore", out, err],
|
|
8769
|
-
env: mergeProcessEnv(process.env,
|
|
8799
|
+
env: mergeProcessEnv(process.env, overrides)
|
|
8770
8800
|
});
|
|
8771
8801
|
if (child.pid) writeFileSync(servicePidFile(), String(child.pid), "utf8");
|
|
8772
8802
|
child.unref();
|
|
@@ -8797,10 +8827,150 @@ async function restartWinStartup() {
|
|
|
8797
8827
|
throw new Error("\u767B\u5F55\u81EA\u542F\u672A\u5B89\u88C5\uFF08\u5148\u8FD0\u884C `feishu-codex-bridge start`\uFF09\u3002");
|
|
8798
8828
|
}
|
|
8799
8829
|
await ensureLogFiles();
|
|
8800
|
-
|
|
8801
|
-
|
|
8830
|
+
const oldPid = readServicePid();
|
|
8831
|
+
if (oldPid === null || !pidAlive(oldPid)) {
|
|
8832
|
+
startNow();
|
|
8833
|
+
return statusWinStartup();
|
|
8834
|
+
}
|
|
8835
|
+
writeRelaunchRequest({ oldPid, envPath: process.env.PATH ?? "", nonce: `${process.pid}-${Date.now()}` });
|
|
8836
|
+
if (!spawnTreeFreeRelauncher()) {
|
|
8837
|
+
clearRelaunchRequest();
|
|
8838
|
+
appendServiceErr("relaunch", `no tree-free backend (powershell/schtasks); kept old pid=${oldPid} running, did NOT restart`);
|
|
8839
|
+
throw new Error("\u65E0\u6CD5\u62C9\u8D77\u300C\u6811\u5916\u300D\u91CD\u542F\u8FDB\u7A0B\uFF08powershell \u4E0E schtasks \u5747\u4E0D\u53EF\u7528\uFF09\uFF1B\u5DF2\u4FDD\u7559\u65E7\u670D\u52A1\u7EE7\u7EED\u8FD0\u884C\uFF0C\u672A\u91CD\u542F\u3002");
|
|
8840
|
+
}
|
|
8841
|
+
appendServiceErr("relaunch", `submitted async relaunch for oldPid=${oldPid}`);
|
|
8802
8842
|
return statusWinStartup();
|
|
8803
8843
|
}
|
|
8844
|
+
function relaunchRequestFile() {
|
|
8845
|
+
return join13(paths.appDir, "relaunch.json");
|
|
8846
|
+
}
|
|
8847
|
+
function writeRelaunchRequest(req) {
|
|
8848
|
+
writeFileSync(relaunchRequestFile(), JSON.stringify(req), "utf8");
|
|
8849
|
+
}
|
|
8850
|
+
function clearRelaunchRequest() {
|
|
8851
|
+
rmSync(relaunchRequestFile(), { force: true });
|
|
8852
|
+
}
|
|
8853
|
+
function relaunchClaimFile() {
|
|
8854
|
+
return `${relaunchRequestFile()}.claim.${process.pid}`;
|
|
8855
|
+
}
|
|
8856
|
+
function claimRelaunchRequest() {
|
|
8857
|
+
const claim = relaunchClaimFile();
|
|
8858
|
+
try {
|
|
8859
|
+
renameSync(relaunchRequestFile(), claim);
|
|
8860
|
+
} catch {
|
|
8861
|
+
return null;
|
|
8862
|
+
}
|
|
8863
|
+
try {
|
|
8864
|
+
const r = JSON.parse(readFileSync3(claim, "utf8"));
|
|
8865
|
+
if (typeof r.oldPid === "number" && Number.isFinite(r.oldPid)) {
|
|
8866
|
+
return {
|
|
8867
|
+
oldPid: r.oldPid,
|
|
8868
|
+
envPath: typeof r.envPath === "string" ? r.envPath : "",
|
|
8869
|
+
nonce: String(r.nonce ?? "")
|
|
8870
|
+
};
|
|
8871
|
+
}
|
|
8872
|
+
} catch {
|
|
8873
|
+
}
|
|
8874
|
+
return null;
|
|
8875
|
+
}
|
|
8876
|
+
function clearRelaunchClaim() {
|
|
8877
|
+
rmSync(relaunchClaimFile(), { force: true });
|
|
8878
|
+
}
|
|
8879
|
+
function buildRelauncherPowershell(binPath = resolveCliBinPath(), nodePath = process.execPath) {
|
|
8880
|
+
const inner = `"${nodePath}" "${binPath}" __win-relaunch`;
|
|
8881
|
+
const lit = `'${inner.replace(/'/g, "''")}'`;
|
|
8882
|
+
return `$c=${lit}; try { Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine=$c } -ErrorAction Stop | Out-Null } catch { schtasks /create /tn ${RELAUNCH_TASK_NAME} /tr $c /sc ONCE /st 00:00 /f | Out-Null; schtasks /run /tn ${RELAUNCH_TASK_NAME} | Out-Null }`;
|
|
8883
|
+
}
|
|
8884
|
+
function powershellPath() {
|
|
8885
|
+
const root = process.env.SystemRoot || process.env.windir || "C:\\Windows";
|
|
8886
|
+
const p = join13(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
8887
|
+
return existsSync7(p) ? p : null;
|
|
8888
|
+
}
|
|
8889
|
+
function spawnTreeFreeRelauncher() {
|
|
8890
|
+
const ps = powershellPath();
|
|
8891
|
+
if (ps) {
|
|
8892
|
+
const errFd = openSync(serviceStderrPath(), "a");
|
|
8893
|
+
const child = spawn3(
|
|
8894
|
+
ps,
|
|
8895
|
+
["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", buildRelauncherPowershell()],
|
|
8896
|
+
{ detached: true, windowsHide: true, stdio: ["ignore", errFd, errFd] }
|
|
8897
|
+
);
|
|
8898
|
+
child.on("error", (e) => appendServiceErr("relaunch", `powershell spawn error: ${e.message}`));
|
|
8899
|
+
child.unref();
|
|
8900
|
+
return true;
|
|
8901
|
+
}
|
|
8902
|
+
appendServiceErr("relaunch", "powershell.exe not found; trying schtasks fallback");
|
|
8903
|
+
return spawnSchtasksRelauncher();
|
|
8904
|
+
}
|
|
8905
|
+
function spawnSchtasksRelauncher() {
|
|
8906
|
+
const tr = `"${process.execPath}" "${resolveCliBinPath()}" __win-relaunch`;
|
|
8907
|
+
const create = spawnSync2(
|
|
8908
|
+
"schtasks",
|
|
8909
|
+
["/create", "/tn", RELAUNCH_TASK_NAME, "/tr", tr, "/sc", "ONCE", "/st", "00:00", "/f"],
|
|
8910
|
+
{ encoding: "utf8", windowsHide: true }
|
|
8911
|
+
);
|
|
8912
|
+
if (create.status !== 0) {
|
|
8913
|
+
appendServiceErr("relaunch", `schtasks /create failed exit=${create.status ?? "?"} ${(create.stderr || "").trim()}`);
|
|
8914
|
+
return false;
|
|
8915
|
+
}
|
|
8916
|
+
const run = spawnSync2("schtasks", ["/run", "/tn", RELAUNCH_TASK_NAME], { encoding: "utf8", windowsHide: true });
|
|
8917
|
+
if (run.status !== 0) {
|
|
8918
|
+
appendServiceErr("relaunch", `schtasks /run failed exit=${run.status ?? "?"} ${(run.stderr || "").trim()}`);
|
|
8919
|
+
return false;
|
|
8920
|
+
}
|
|
8921
|
+
return true;
|
|
8922
|
+
}
|
|
8923
|
+
function defaultTaskkill(pid) {
|
|
8924
|
+
const r = spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
8925
|
+
return { status: r.status, stderr: (r.stderr || "").toString() };
|
|
8926
|
+
}
|
|
8927
|
+
async function runWinRelaunch(deps = {}) {
|
|
8928
|
+
const claim = deps.claimRequest ?? claimRelaunchRequest;
|
|
8929
|
+
const clearClaim = deps.clearRequest ?? clearRelaunchClaim;
|
|
8930
|
+
const alive = deps.pidAlive ?? pidAlive;
|
|
8931
|
+
const kill = deps.taskkill ?? defaultTaskkill;
|
|
8932
|
+
const start = deps.start ?? startNow;
|
|
8933
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
8934
|
+
const graceMs = deps.graceMs ?? 15e3;
|
|
8935
|
+
const pollMs = deps.pollMs ?? 200;
|
|
8936
|
+
const now = deps.now ?? (() => Date.now());
|
|
8937
|
+
const ensureLogs = deps.ensureLogs ?? ensureLogFiles;
|
|
8938
|
+
const logLine = deps.logLine ?? ((line) => appendServiceErr("relaunch", line));
|
|
8939
|
+
await ensureLogs();
|
|
8940
|
+
const req = claim();
|
|
8941
|
+
if (!req) {
|
|
8942
|
+
logLine("no relaunch request to claim (already handled by another relauncher, or none)");
|
|
8943
|
+
return;
|
|
8944
|
+
}
|
|
8945
|
+
try {
|
|
8946
|
+
logLine(`start oldPid=${req.oldPid} self=${process.pid}`);
|
|
8947
|
+
if (alive(req.oldPid)) {
|
|
8948
|
+
const r = kill(req.oldPid);
|
|
8949
|
+
logLine(`taskkill oldPid=${req.oldPid} exit=${r.status ?? "?"} ${r.stderr.trim()}`.trim());
|
|
8950
|
+
}
|
|
8951
|
+
const deadline = now() + graceMs;
|
|
8952
|
+
while (now() < deadline && alive(req.oldPid)) {
|
|
8953
|
+
await sleep(pollMs);
|
|
8954
|
+
}
|
|
8955
|
+
if (alive(req.oldPid)) {
|
|
8956
|
+
logLine(
|
|
8957
|
+
`ABORT: oldPid=${req.oldPid} still alive after ${graceMs}ms (elevated / access denied?); not starting new to avoid double instance`
|
|
8958
|
+
);
|
|
8959
|
+
return;
|
|
8960
|
+
}
|
|
8961
|
+
await sleep(300);
|
|
8962
|
+
start(req.envPath);
|
|
8963
|
+
logLine(`relaunched; new service.pid=${readServicePid() ?? "?"}`);
|
|
8964
|
+
} finally {
|
|
8965
|
+
clearClaim();
|
|
8966
|
+
if (process.platform === "win32") {
|
|
8967
|
+
try {
|
|
8968
|
+
spawnSync2("schtasks", ["/delete", "/tn", RELAUNCH_TASK_NAME, "/f"], { stdio: "ignore", windowsHide: true });
|
|
8969
|
+
} catch {
|
|
8970
|
+
}
|
|
8971
|
+
}
|
|
8972
|
+
}
|
|
8973
|
+
}
|
|
8804
8974
|
function statusWinStartup() {
|
|
8805
8975
|
const installed = isInstalled();
|
|
8806
8976
|
const pid = readServicePid();
|
|
@@ -8860,12 +9030,15 @@ function pidAlive(pid) {
|
|
|
8860
9030
|
function killService() {
|
|
8861
9031
|
const pid = readServicePid();
|
|
8862
9032
|
if (pid === null || !pidAlive(pid)) return;
|
|
8863
|
-
spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
9033
|
+
const r = spawnSync2("taskkill", ["/pid", String(pid), "/T", "/F"], { encoding: "utf8", windowsHide: true });
|
|
9034
|
+
if (r.status !== 0) {
|
|
9035
|
+
appendServiceErr("kill", `taskkill pid=${pid} exit=${r.status ?? "?"} ${(r.stderr || "").toString().trim()}`);
|
|
9036
|
+
}
|
|
8864
9037
|
}
|
|
8865
9038
|
|
|
8866
9039
|
// src/service/systemd.ts
|
|
8867
9040
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
8868
|
-
import { existsSync as
|
|
9041
|
+
import { existsSync as existsSync8 } from "fs";
|
|
8869
9042
|
import { mkdir as mkdir10, rm as rm4, writeFile as writeFile9 } from "fs/promises";
|
|
8870
9043
|
import { homedir as homedir5 } from "os";
|
|
8871
9044
|
import { dirname as dirname8, join as join14 } from "path";
|
|
@@ -8969,7 +9142,7 @@ function statusSystemd() {
|
|
|
8969
9142
|
};
|
|
8970
9143
|
}
|
|
8971
9144
|
function unitExists() {
|
|
8972
|
-
return
|
|
9145
|
+
return existsSync8(systemdUnitPath());
|
|
8973
9146
|
}
|
|
8974
9147
|
function systemdActive() {
|
|
8975
9148
|
const r = spawnSync3("systemctl", ["--user", "is-active", SYSTEMD_UNIT_NAME], {
|
|
@@ -9044,7 +9217,7 @@ function packageName() {
|
|
|
9044
9217
|
return pkgJson().name ?? "@modelzen/feishu-codex-bridge";
|
|
9045
9218
|
}
|
|
9046
9219
|
function isDevSource() {
|
|
9047
|
-
return
|
|
9220
|
+
return existsSync9(join15(pkgRoot(), ".git"));
|
|
9048
9221
|
}
|
|
9049
9222
|
function isNewer(a, b) {
|
|
9050
9223
|
const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -9759,7 +9932,7 @@ init_paths();
|
|
|
9759
9932
|
init_paths();
|
|
9760
9933
|
init_logger();
|
|
9761
9934
|
import { mkdir as mkdir11 } from "fs/promises";
|
|
9762
|
-
import { existsSync as
|
|
9935
|
+
import { existsSync as existsSync10 } from "fs";
|
|
9763
9936
|
import { isAbsolute as isAbsolute2, join as join17, resolve as resolve7 } from "path";
|
|
9764
9937
|
|
|
9765
9938
|
// src/project/announcement.ts
|
|
@@ -9952,7 +10125,7 @@ function assertBackendUsable(backend, mode) {
|
|
|
9952
10125
|
async function resolveCwd(name, existingPath) {
|
|
9953
10126
|
if (existingPath) {
|
|
9954
10127
|
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve7(existingPath);
|
|
9955
|
-
if (!
|
|
10128
|
+
if (!existsSync10(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
9956
10129
|
return { cwd: cwd2, blank: false };
|
|
9957
10130
|
}
|
|
9958
10131
|
const cwd = join17(paths.projectsRootDir, name);
|
|
@@ -11369,6 +11542,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11369
11542
|
return;
|
|
11370
11543
|
}
|
|
11371
11544
|
const ts = turnSession(msg.chatId, project, msg.senderId);
|
|
11545
|
+
if (cmd === "resume") {
|
|
11546
|
+
postResumeCard(msg, ts.sessionKey);
|
|
11547
|
+
return;
|
|
11548
|
+
}
|
|
11549
|
+
if (cmd === "clear") {
|
|
11550
|
+
runClear(msg, ts.sessionKey, ts);
|
|
11551
|
+
return;
|
|
11552
|
+
}
|
|
11372
11553
|
if (cmd === "model") {
|
|
11373
11554
|
postModelCard(msg, ts.sessionKey, false);
|
|
11374
11555
|
return;
|
|
@@ -11402,6 +11583,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11402
11583
|
await channel.send(msg.chatId, { markdown: `\`/${cmd}\` \u8BF7\u5230\u4E3B\u7FA4\u533A\u4F7F\u7528\uFF08\u8BDD\u9898\u5916\u53D1\uFF09\u3002` }, { replyTo: msg.messageId, replyInThread: true }).catch(() => void 0);
|
|
11403
11584
|
return;
|
|
11404
11585
|
}
|
|
11586
|
+
if (cmd === "clear") {
|
|
11587
|
+
await channel.send(
|
|
11588
|
+
msg.chatId,
|
|
11589
|
+
{ markdown: "`/clear` \u4EC5\u5355\u4F1A\u8BDD\u7FA4\u53EF\u7528\u3002\u591A\u8BDD\u9898\u7FA4\u91CC\uFF0C\u56DE\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9 \u5373\u5F00\u65B0\u8BDD\u9898\uFF1B`/resume` \u53EF\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u3002" },
|
|
11590
|
+
{ replyTo: msg.messageId, replyInThread: true }
|
|
11591
|
+
).catch(() => void 0);
|
|
11592
|
+
return;
|
|
11593
|
+
}
|
|
11405
11594
|
const ts = turnSession(msg.threadId, project, msg.senderId);
|
|
11406
11595
|
if (cmd === "model") {
|
|
11407
11596
|
postModelCard(msg, ts.sessionKey, true);
|
|
@@ -11439,6 +11628,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11439
11628
|
await postGroupSettings(msg, project);
|
|
11440
11629
|
return;
|
|
11441
11630
|
}
|
|
11631
|
+
if (cmd === "clear") {
|
|
11632
|
+
await channel.send(
|
|
11633
|
+
msg.chatId,
|
|
11634
|
+
{ markdown: "`/clear` \u4EC5\u5355\u4F1A\u8BDD\u7FA4\u53EF\u7528\u3002\u591A\u8BDD\u9898\u7FA4\u91CC\uFF0C@\u6211 + \u5185\u5BB9 \u5373\u5F00\u65B0\u8BDD\u9898\uFF1B`/resume` \u53EF\u6062\u590D\u5386\u53F2\u4F1A\u8BDD\u3002" },
|
|
11635
|
+
{ replyTo: msg.messageId }
|
|
11636
|
+
).catch(() => void 0);
|
|
11637
|
+
return;
|
|
11638
|
+
}
|
|
11442
11639
|
if (cmd === "model" || cmd === "compact" || cmd === "context") {
|
|
11443
11640
|
await channel.send(msg.chatId, { markdown: `\`/${cmd}\` \u9700\u8981\u5728\u8BDD\u9898\u91CC\u4F7F\u7528\uFF08\u5148 @\u6211 \u5F00\u4E2A\u8BDD\u9898\uFF09\u3002` }, { replyTo: msg.messageId }).catch(() => void 0);
|
|
11444
11641
|
return;
|
|
@@ -11467,7 +11664,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11467
11664
|
function parseCommand(text) {
|
|
11468
11665
|
const m = /^\/(\w+)/.exec(text);
|
|
11469
11666
|
const name = m?.[1]?.toLowerCase();
|
|
11470
|
-
return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" ? name : null;
|
|
11667
|
+
return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" || name === "clear" ? name : null;
|
|
11471
11668
|
}
|
|
11472
11669
|
function shouldRespondWithoutMention(project, msg) {
|
|
11473
11670
|
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
@@ -11783,7 +11980,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11783
11980
|
);
|
|
11784
11981
|
}).catch((err) => log.fail("intake", err));
|
|
11785
11982
|
}
|
|
11786
|
-
function postResumeCard(msg) {
|
|
11983
|
+
function postResumeCard(msg, singleSessionKey) {
|
|
11787
11984
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
11788
11985
|
if (!isAdmin(cfg, msg.senderId)) {
|
|
11789
11986
|
await denyAdminCommand(msg, "resume");
|
|
@@ -11802,7 +11999,9 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11802
11999
|
projectName: project?.name,
|
|
11803
12000
|
backend: be.id,
|
|
11804
12001
|
threads,
|
|
11805
|
-
createdAt: Date.now()
|
|
12002
|
+
createdAt: Date.now(),
|
|
12003
|
+
// Single-session group: pick rebinds this key flat (no new topic).
|
|
12004
|
+
...singleSessionKey ? { flat: true, sessionKey: singleSessionKey } : {}
|
|
11806
12005
|
};
|
|
11807
12006
|
const res = await sendManagedCard(channel, msg.chatId, buildResumeCard(state), msg.messageId);
|
|
11808
12007
|
pruneResumePending();
|
|
@@ -11926,6 +12125,68 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11926
12125
|
}
|
|
11927
12126
|
});
|
|
11928
12127
|
}
|
|
12128
|
+
function runClear(msg, sessionKey, perm) {
|
|
12129
|
+
const reply = (markdown) => channel.send(msg.chatId, { markdown }, { replyTo: msg.messageId }).then(
|
|
12130
|
+
() => void 0,
|
|
12131
|
+
() => void 0
|
|
12132
|
+
);
|
|
12133
|
+
if (!isAdmin(cfg, msg.senderId)) {
|
|
12134
|
+
void denyAdminCommand(msg, "clear");
|
|
12135
|
+
return;
|
|
12136
|
+
}
|
|
12137
|
+
if (active.get(sessionKey)) {
|
|
12138
|
+
void reply("\u23F3 \u8FD9\u4E00\u8F6E\u8FD8\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D `/clear`\u3002");
|
|
12139
|
+
return;
|
|
12140
|
+
}
|
|
12141
|
+
const reserved = { queue: [], requesterOpenId: msg.senderId };
|
|
12142
|
+
active.set(sessionKey, reserved);
|
|
12143
|
+
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
12144
|
+
try {
|
|
12145
|
+
const [rec, project] = await Promise.all([getSession(sessionKey), getProjectByChatId(msg.chatId)]);
|
|
12146
|
+
const be = backendFor(rec?.backend ?? project?.backend);
|
|
12147
|
+
const cwd = rec?.cwd ?? project?.cwd ?? fallbackCwd;
|
|
12148
|
+
const fresh = await be.startThread({
|
|
12149
|
+
cwd,
|
|
12150
|
+
model: rec?.model,
|
|
12151
|
+
effort: rec?.effort,
|
|
12152
|
+
mode: perm.mode,
|
|
12153
|
+
network: perm.network,
|
|
12154
|
+
autoCompact: perm.autoCompact
|
|
12155
|
+
});
|
|
12156
|
+
const old = sessions.get(sessionKey);
|
|
12157
|
+
if (old && old !== fresh) {
|
|
12158
|
+
sessions.delete(sessionKey);
|
|
12159
|
+
void old.close().catch(() => void 0);
|
|
12160
|
+
}
|
|
12161
|
+
trackSession(sessionKey, fresh);
|
|
12162
|
+
lastUsage.delete(sessionKey);
|
|
12163
|
+
await upsertSession({
|
|
12164
|
+
threadId: sessionKey,
|
|
12165
|
+
chatId: msg.chatId,
|
|
12166
|
+
cwd,
|
|
12167
|
+
sessionId: fresh.sessionId,
|
|
12168
|
+
backend: be.id,
|
|
12169
|
+
model: rec?.model,
|
|
12170
|
+
effort: rec?.effort,
|
|
12171
|
+
summary: "(\u65B0\u4F1A\u8BDD)",
|
|
12172
|
+
createdAt: Date.now(),
|
|
12173
|
+
updatedAt: Date.now()
|
|
12174
|
+
});
|
|
12175
|
+
await sendManagedCard(channel, msg.chatId, buildClearedCard(), msg.messageId, false).catch(
|
|
12176
|
+
(err) => log.fail("card", err, { phase: "clear" })
|
|
12177
|
+
);
|
|
12178
|
+
log.info("intake", "clear", { sessionKey, sessionId: fresh.sessionId, backend: be.id });
|
|
12179
|
+
} catch (err) {
|
|
12180
|
+
log.fail("intake", err, { phase: "clear" });
|
|
12181
|
+
await reply(`\u274C ${err instanceof Error ? err.message : String(err)}`);
|
|
12182
|
+
} finally {
|
|
12183
|
+
if (active.get(sessionKey) === reserved) active.delete(sessionKey);
|
|
12184
|
+
if (reserved.queue.length > 0) {
|
|
12185
|
+
await reply(`\u26A0\uFE0F \u6E05\u7A7A\u671F\u95F4\u6536\u5230\u7684 ${reserved.queue.length} \u6761\u6D88\u606F\u672A\u8FDB\u5165\u4F1A\u8BDD\uFF0C\u8BF7\u91CD\u53D1\u3002`);
|
|
12186
|
+
}
|
|
12187
|
+
}
|
|
12188
|
+
});
|
|
12189
|
+
}
|
|
11929
12190
|
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
11930
12191
|
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
11931
12192
|
const caps = backendFor(project?.backend).capabilities;
|
|
@@ -12794,6 +13055,55 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
12794
13055
|
async function resumeFromCard(evt, state, sessionId, backendId) {
|
|
12795
13056
|
const be = backendFor(backendId);
|
|
12796
13057
|
try {
|
|
13058
|
+
if (state.flat && state.sessionKey) {
|
|
13059
|
+
const sessionKey = state.sessionKey;
|
|
13060
|
+
if (active.get(sessionKey)) {
|
|
13061
|
+
resumePending.delete(evt.messageId);
|
|
13062
|
+
settleUpdate(evt.messageId, buildResumeErrorCard(state, "\u5F53\u524D\u6709\u4EFB\u52A1\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D /resume"));
|
|
13063
|
+
return;
|
|
13064
|
+
}
|
|
13065
|
+
const reserved = { queue: [], requesterOpenId: state.requesterOpenId };
|
|
13066
|
+
active.set(sessionKey, reserved);
|
|
13067
|
+
try {
|
|
13068
|
+
const history2 = await be.readHistory(state.cwd, sessionId);
|
|
13069
|
+
resumePending.delete(evt.messageId);
|
|
13070
|
+
await withTrace({ chatId: state.chatId, msgId: state.originalMsgId }, async () => {
|
|
13071
|
+
const cardState = { cwd: state.cwd, projectName: state.projectName, history: history2 };
|
|
13072
|
+
await sendManagedCard(channel, state.chatId, buildHistoryCard(cardState), state.originalMsgId, false).catch(
|
|
13073
|
+
(err) => log.fail("card", err, { phase: "resume-history-single" })
|
|
13074
|
+
);
|
|
13075
|
+
const old = sessions.get(sessionKey);
|
|
13076
|
+
if (old) {
|
|
13077
|
+
sessions.delete(sessionKey);
|
|
13078
|
+
void old.close().catch(() => void 0);
|
|
13079
|
+
}
|
|
13080
|
+
lastUsage.delete(sessionKey);
|
|
13081
|
+
const now = Date.now();
|
|
13082
|
+
await upsertSession({
|
|
13083
|
+
threadId: sessionKey,
|
|
13084
|
+
chatId: state.chatId,
|
|
13085
|
+
cwd: state.cwd,
|
|
13086
|
+
sessionId,
|
|
13087
|
+
backend: be.id,
|
|
13088
|
+
summary: history2.name || history2.preview || "(\u6062\u590D\u4F1A\u8BDD)",
|
|
13089
|
+
createdAt: now,
|
|
13090
|
+
updatedAt: now
|
|
13091
|
+
});
|
|
13092
|
+
log.info("card", "resume-done-single", { sessionKey, sessionId, turns: history2.totalTurns });
|
|
13093
|
+
});
|
|
13094
|
+
settleUpdate(evt.messageId, buildResumeDoneCard(state));
|
|
13095
|
+
} finally {
|
|
13096
|
+
if (active.get(sessionKey) === reserved) active.delete(sessionKey);
|
|
13097
|
+
if (reserved.queue.length > 0) {
|
|
13098
|
+
await channel.send(
|
|
13099
|
+
state.chatId,
|
|
13100
|
+
{ markdown: `\u26A0\uFE0F \u5207\u56DE\u671F\u95F4\u6536\u5230\u7684 ${reserved.queue.length} \u6761\u6D88\u606F\u672A\u8FDB\u5165\u4F1A\u8BDD\uFF0C\u8BF7\u91CD\u53D1\u3002` },
|
|
13101
|
+
{ replyTo: state.originalMsgId }
|
|
13102
|
+
).catch(() => void 0);
|
|
13103
|
+
}
|
|
13104
|
+
}
|
|
13105
|
+
return;
|
|
13106
|
+
}
|
|
12797
13107
|
const history = await be.readHistory(state.cwd, sessionId);
|
|
12798
13108
|
resumePending.delete(evt.messageId);
|
|
12799
13109
|
let bound = false;
|
|
@@ -14580,16 +14890,22 @@ async function doUpdate() {
|
|
|
14580
14890
|
}
|
|
14581
14891
|
const res = await installLatest();
|
|
14582
14892
|
log.info("daemon-control", "update-installed", { ok: res.ok });
|
|
14583
|
-
|
|
14893
|
+
appendServiceErr("daemon-control", `update-installed ok=${res.ok}`);
|
|
14894
|
+
if (!res.ok) {
|
|
14895
|
+
appendServiceErr("daemon-control", "install failed \u2014 keeping old version, not restarting");
|
|
14896
|
+
return;
|
|
14897
|
+
}
|
|
14584
14898
|
await doRestart("update");
|
|
14585
14899
|
}
|
|
14586
14900
|
async function doRestart(phase) {
|
|
14587
14901
|
if (!isServiceRunning()) {
|
|
14588
14902
|
log.info("daemon-control", "restart-skipped-no-service", { phase });
|
|
14903
|
+
appendServiceErr("daemon-control", `restart-skipped-no-service (phase=${phase}); service.pid \u672A\u6307\u5411\u5728\u8DD1\u7684 daemon`);
|
|
14589
14904
|
return;
|
|
14590
14905
|
}
|
|
14591
14906
|
await getServiceAdapter().restart();
|
|
14592
14907
|
log.info("daemon-control", "restart-issued", { phase });
|
|
14908
|
+
appendServiceErr("daemon-control", `restart-issued (phase=${phase})`);
|
|
14593
14909
|
}
|
|
14594
14910
|
|
|
14595
14911
|
// src/web/mount.ts
|
|
@@ -15338,6 +15654,9 @@ ${UI_PURE_JS}
|
|
|
15338
15654
|
var daemon = null; // /api/daemon
|
|
15339
15655
|
var diag = null; // /api/diagnosis \u7ED3\u679C\uFF08\u5F53\u524D bot \u7684\u63A2\u6D4B\uFF1B\u5207 Tab \u5FC5\u6E05\uFF09
|
|
15340
15656
|
var catalog = null; // /api/backends \u7F13\u5B58\uFF08\u540E\u7AEF\u7BA1\u7406\u5361 + \u9879\u76EE picker \u590D\u7528\uFF09
|
|
15657
|
+
var lastUpdate = null; // /api/update/check \u7ED3\u679C\u7F13\u5B58\uFF1A\u603B\u89C8 5s \u91CD\u6E32\u4E0D\u91CD\u67E5\uFF08\u6BCF\u6B21\u67E5\u90FD spawn npm view\uFF09
|
|
15658
|
+
var updateCheckedAt = 0; // \u4E0A\u6B21\u68C0\u67E5\u65F6\u95F4\u6233\uFF1B\u62FF\u5230\u7ED3\u679C\u8D85 30min\u3001\u8F6F\u5931\u8D25\u8D85 ~2min \u624D\u968F\u91CD\u6E32\u81EA\u52A8\u590D\u67E5
|
|
15659
|
+
var updateChecking = false;// in-flight \u95F8\uFF1Anpm view \u53EF\u80FD\u6570\u79D2~20s\uFF0C\u671F\u95F4 5s \u91CD\u6E32\u4E0D\u53E0\u53D1\u7B2C\u4E8C\u4E2A\u8BF7\u6C42
|
|
15341
15660
|
var drawerProject = null; // \u62BD\u5C49\u91CC\u6253\u5F00\u7684\u9879\u76EE\u540D
|
|
15342
15661
|
var diagBotId = null; // diag \u5C5E\u4E8E\u54EA\u4E2A bot\uFF08\u9632\u4E32\u53F0\uFF09
|
|
15343
15662
|
var bkDetailId = null; // \u540E\u7AEF Agent \u8BE6\u60C5\u89C6\u56FE\u5F53\u524D\u5C55\u5F00\u7684\u540E\u7AEF id\uFF08null=\u5217\u8868\uFF09
|
|
@@ -15536,6 +15855,14 @@ ${UI_PURE_JS}
|
|
|
15536
15855
|
fetch('/api/daemon').then(function (r) { return r.json(); })
|
|
15537
15856
|
.then(function (d) {
|
|
15538
15857
|
daemon = d;
|
|
15858
|
+
// \u8FD0\u884C\u4E2D\u7684\u5B9E\u9645\u7248\u672C\u4E00\u65E6\u8DDF\u7248\u672C\u68C0\u67E5\u7F13\u5B58\u91CC\u7684 current \u5BF9\u4E0D\u4E0A\uFF08\u591A\u534A\u662F\u521A\u66F4\u65B0+\u91CD\u542F\u5B8C\u6210\uFF09\uFF0C
|
|
15859
|
+
// \u5C31\u4F5C\u5E9F\u7F13\u5B58\u5E76\u5F53\u573A\u8865\u67E5\u4E00\u6B21\uFF08\u82E5\u5728\u603B\u89C8\u9875\uFF09\uFF0C\u628A\u300C\u6709\u65B0\u7248\u300D\u7ACB\u523B\u7FFB\u6210\u300C\u5DF2\u662F\u6700\u65B0\u7248\u300D\u2014\u2014\u4E0D\u7B49
|
|
15860
|
+
// \u4E0B\u4E00\u8F6E renderRoute\uFF0C\u6D88\u9664 loadState/loadDaemon \u540C\u5468\u671F\u7ADE\u6001\u5BFC\u81F4\u7684 ~10s \u5EF6\u8FDF\u4E0E\u6A2A\u5E45\u6B8B\u7559\u3002
|
|
15861
|
+
// \u53EA\u5728\u7248\u672C\u771F\u53D8\u65F6\u624D\u67E5\uFF08\u7A33\u6001 d.version===current\uFF09\uFF0C\u65E2\u7559\u4F4F\u66F4\u65B0\u540E\u7684\u5373\u65F6\u53CD\u9988\uFF0C\u53C8\u4E0D\u56DE\u5230 5s \u7A7A\u8F6C\u3002
|
|
15862
|
+
if (lastUpdate && d.version && d.version !== lastUpdate.current) {
|
|
15863
|
+
lastUpdate = null; updateCheckedAt = 0;
|
|
15864
|
+
if (parseRoute(location.hash).tab === 'overview') { var ub = $('updateBody'); if (ub) loadUpdate(ub); }
|
|
15865
|
+
}
|
|
15539
15866
|
if (parseRoute(location.hash).tab === 'overview') { var b = $('daemonBody'); if (b) renderDaemon(b); }
|
|
15540
15867
|
renderSidebarFoot();
|
|
15541
15868
|
renderGlobalSummary();
|
|
@@ -15849,7 +16176,14 @@ ${UI_PURE_JS}
|
|
|
15849
16176
|
daemonCard.appendChild(updateBody);
|
|
15850
16177
|
root.appendChild(daemonCard);
|
|
15851
16178
|
renderDaemon(daemonBody);
|
|
15852
|
-
|
|
16179
|
+
// \u7248\u672C\u68C0\u67E5\u4F1A\u5728\u670D\u52A1\u7AEF spawn\u300Cnpm view\u300D\uFF0C\u4E0D\u80FD\u8DDF\u7740 5s \u91CD\u6E32\u6BCF\u6B21\u90FD\u8DD1\uFF1A\u6709\u7F13\u5B58\u5C31\u76F4\u63A5\u6E32\uFF0C
|
|
16180
|
+
// \u53EA\u5728\u9996\u6B21\u6216\u7F13\u5B58\u8FC7\u671F\uFF0830min\uFF09\u65F6\u624D\u771F\u67E5\u4E00\u6B21\u3002\u624B\u52A8\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u6309\u94AE\u4ECD\u5F3A\u5236\u5237\u65B0\u3002
|
|
16181
|
+
if (lastUpdate) {
|
|
16182
|
+
renderUpdate(updateBody, lastUpdate);
|
|
16183
|
+
if (Date.now() - updateCheckedAt > 30 * 60 * 1000) loadUpdate(updateBody);
|
|
16184
|
+
} else {
|
|
16185
|
+
loadUpdate(updateBody);
|
|
16186
|
+
}
|
|
15853
16187
|
|
|
15854
16188
|
// \u{1F916} \u673A\u5668\u4EBA\uFF08\u5728 Feishu Bridge \u5361\u4E0B\u9762\uFF0C\u5404\u81EA\u5360\u6EE1\u5BBD\u5EA6\uFF09
|
|
15855
16189
|
var botsCard = el('div', 'card');
|
|
@@ -16208,9 +16542,19 @@ ${UI_PURE_JS}
|
|
|
16208
16542
|
}
|
|
16209
16543
|
|
|
16210
16544
|
function loadUpdate(box) {
|
|
16545
|
+
if (updateChecking) return; // \u5728\u9014\u5C31\u4E0D\u53E0\u53D1\uFF1A\u6162\u68C0\u67E5\uFF08npm view \u6700\u957F 20s\uFF09\u671F\u95F4 5s \u91CD\u6E32\u4E0D\u91CD\u590D spawn
|
|
16546
|
+
updateChecking = true;
|
|
16547
|
+
updateCheckedAt = Date.now(); // \u4E50\u89C2\u5360\u4F4D\uFF1Ain-flight \u671F\u95F4 30min \u8FB9\u754C\u4E0D\u518D\u88AB\u53CD\u590D\u5224\u771F
|
|
16211
16548
|
fetch('/api/update/check').then(function (r) { return r.json(); })
|
|
16212
|
-
.then(function (u) {
|
|
16213
|
-
|
|
16549
|
+
.then(function (u) {
|
|
16550
|
+
lastUpdate = u;
|
|
16551
|
+
// \u8F6F\u5931\u8D25\uFF1A\u975E dev \u5374\u6CA1\u62FF\u5230 latest\uFF08\u591A\u534A registry \u4E0D\u53EF\u8FBE/\u8D85\u65F6\uFF0C\u670D\u52A1\u7AEF\u4ECD\u56DE 200\uFF09\uFF0C\u53EA\u7F13\u5B58 ~2min
|
|
16552
|
+
// \u4FBF\u81EA\u52A8\u590D\u67E5\uFF0C\u522B\u628A\u300C\u67E5\u8BE2\u5931\u8D25\u300D\u9489\u4F4F 30min\uFF1B\u62FF\u5230 latest \u6216 dev \u6A21\u5F0F\u624D\u6309\u5B8C\u6574 30min TTL\u3002
|
|
16553
|
+
updateCheckedAt = (u && (u.dev || u.latest)) ? Date.now() : Date.now() - 28 * 60 * 1000;
|
|
16554
|
+
renderUpdate(box, u);
|
|
16555
|
+
})
|
|
16556
|
+
.catch(function () { box.textContent = '\u26A0\uFE0F \u7248\u672C\u68C0\u67E5\u5931\u8D25\uFF08\u7F51\u7EDC\u6216 npm registry\uFF09'; })
|
|
16557
|
+
.then(function () { updateChecking = false; }); // finally\uFF1A\u65E0\u8BBA\u6210\u8D25\u90FD\u677E\u95F8
|
|
16214
16558
|
}
|
|
16215
16559
|
function renderUpdate(box, u) {
|
|
16216
16560
|
box.textContent = '';
|
|
@@ -17984,6 +18328,7 @@ async function runSupervisor(bots) {
|
|
|
17984
18328
|
const supervisorStartedAt = Date.now();
|
|
17985
18329
|
const cliEntry = process.argv[1];
|
|
17986
18330
|
if (!cliEntry) throw new Error("supervisor: \u65E0\u6CD5\u89E3\u6790 CLI \u5165\u53E3\uFF08process.argv[1] \u4E3A\u7A7A\uFF09");
|
|
18331
|
+
recordServicePid();
|
|
17987
18332
|
const childEnv = { ...process.env };
|
|
17988
18333
|
delete childEnv[SERVICE_ENV_FLAG];
|
|
17989
18334
|
let shuttingDown = false;
|
|
@@ -18250,6 +18595,7 @@ async function runOnboardingConsole() {
|
|
|
18250
18595
|
releaseLock = acquireSingleInstanceLock("__onboarding__");
|
|
18251
18596
|
} catch (err) {
|
|
18252
18597
|
if (err instanceof BridgeAlreadyRunningError) {
|
|
18598
|
+
clearServicePid();
|
|
18253
18599
|
console.error(`\u2717 ${err.message}`);
|
|
18254
18600
|
process.exitCode = 1;
|
|
18255
18601
|
return;
|
|
@@ -18318,6 +18664,7 @@ async function runSingle(botName) {
|
|
|
18318
18664
|
releaseLock = acquireSingleInstanceLock(cfg.accounts.app.id);
|
|
18319
18665
|
} catch (err) {
|
|
18320
18666
|
if (err instanceof BridgeAlreadyRunningError) {
|
|
18667
|
+
clearServicePid();
|
|
18321
18668
|
console.error(`\u2717 ${err.message}`);
|
|
18322
18669
|
log.info("run", "already-running", { pid: err.pid });
|
|
18323
18670
|
process.exitCode = 1;
|
|
@@ -18873,6 +19220,9 @@ program.command("web").description("\u672C\u673A Web \u63A7\u5236\u53F0\uFF08\u5
|
|
|
18873
19220
|
program.command("__daemon-control <action>", { hidden: true }).action(async (action) => {
|
|
18874
19221
|
await runDaemonControl(action);
|
|
18875
19222
|
});
|
|
19223
|
+
program.command("__win-relaunch", { hidden: true }).action(async () => {
|
|
19224
|
+
await runWinRelaunch();
|
|
19225
|
+
});
|
|
18876
19226
|
var bot = program.command("bot").description("\u98DE\u4E66\u673A\u5668\u4EBA\u7BA1\u7406\uFF08\u591A\u673A\u5668\u4EBA\uFF09");
|
|
18877
19227
|
bot.command("init [name]").description("\u6CE8\u518C\u4E00\u4E2A\u98DE\u4E66\u673A\u5668\u4EBA\u5E76\u6388\u6743\uFF08\u53EF\u9009\u77ED\u540D\uFF09").action(async (name) => {
|
|
18878
19228
|
await runBotInit(name);
|
package/package.json
CHANGED