dsh-lark-bot 0.12.0 → 0.13.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 +48 -9
- package/dist/cli.js +201 -48
- package/dist/cli.js.map +1 -1
- package/dist/plugin.js +121 -27
- package/dist/plugin.js.map +1 -1
- package/package.json +47 -1
package/README.md
CHANGED
|
@@ -136,6 +136,7 @@ Just send a normal message to the bot in Feishu to get started. Common commands:
|
|
|
136
136
|
| 命令 Command | 作用 Description |
|
|
137
137
|
| --- | --- |
|
|
138
138
|
| `/new` `/reset` | 开始新会话<br>Start a new session |
|
|
139
|
+
| `/newg <群名>` | 自动新建群聊(拉你入群)并开新会话,当前会话保留<br>Auto-create a group chat (with you invited) and start a fresh session there; the current session is untouched |
|
|
139
140
|
| `/cd <path>` | 切换工作目录并重置会话<br>Change working directory and reset the session |
|
|
140
141
|
| `/ws list` | 查看命名工作空间<br>List named workspaces |
|
|
141
142
|
| `/ws save <name>` | 保存当前工作空间<br>Save the current workspace |
|
|
@@ -170,6 +171,10 @@ Just send a normal message to the bot in Feishu to get started. Common commands:
|
|
|
170
171
|
|
|
171
172
|
Images in Feishu messages are downloaded to the local media directory and passed to dsh; text files are read and their content is injected into the task context.
|
|
172
173
|
|
|
174
|
+
**`/newg <群名>`**:通过飞书 API 自动新建一个私密群、把发送者拉入群,并回复群链接——在新群里发消息即为新 scope / 新会话,当前会话不受影响。需要应用具备 `im:chat` 与 `im:chat.members:write_only` 权限(在开发者后台「权限管理」申请)。
|
|
175
|
+
|
|
176
|
+
**`/newg <group name>`**: auto-creates a private group via the Feishu API, invites the sender, and replies with a group link — chatting in the new group starts a fresh scope/session while the current session is untouched. Requires the `im:chat` and `im:chat.members:write_only` scopes (apply in the developer console).
|
|
177
|
+
|
|
173
178
|
同一 scope(私聊 / 群聊 / 话题)默认允许 **2 个任务并行**(`DSH_LARK_SCOPE_CONCURRENCY` 或
|
|
174
179
|
`/concurrency` 调整):连续发来的多条消息会以独立 run 并行推进,每个 run 使用独立的 dsh
|
|
175
180
|
session 与独立 runId,`/status` 展示全部运行中的 run,`/stop` 一次性终止全部任务。
|
|
@@ -718,6 +723,40 @@ The same dist is also published to GitHub Packages as `@plutokeating/dsh-lark-bo
|
|
|
718
723
|
|
|
719
724
|
See "Community Listings" in the next section for ecosystem registration status.
|
|
720
725
|
|
|
726
|
+
## 作者 | Author
|
|
727
|
+
|
|
728
|
+
本项目由 **PlutoKeating** 开发并维护。作者专注于自动化与开发者工具,习惯从真实使用场景出发
|
|
729
|
+
做软件:本项目正是从“用飞书 / Lark 群聊驱动 DeepSeek Agent”的日常需求长出来的,逐步演进为
|
|
730
|
+
一套带守护、自愈与一键升级能力的完整桥接方案。更多信息见个人主页:
|
|
731
|
+
[PlutoKeating](https://github.com/PlutoKeating)。
|
|
732
|
+
|
|
733
|
+
This project is developed and maintained by **PlutoKeating**, who focuses on automation and
|
|
734
|
+
developer tooling and prefers building software from real usage. It grew out of the daily need
|
|
735
|
+
to drive DeepSeek agents from Feishu/Lark group chats, evolving into a complete bridge with
|
|
736
|
+
guardian, self-healing, and one-command upgrade capabilities. See the author's profile:
|
|
737
|
+
[PlutoKeating](https://github.com/PlutoKeating).
|
|
738
|
+
|
|
739
|
+
## 贡献者 | Contributors
|
|
740
|
+
|
|
741
|
+
感谢以下贡献者(按合入 / 提交时间):
|
|
742
|
+
|
|
743
|
+
Thanks to the following contributors (by merge / submission time):
|
|
744
|
+
|
|
745
|
+
| 贡献者 Contributor | 贡献 Contribution | 状态 Status |
|
|
746
|
+
| :--- | :--- | :--- |
|
|
747
|
+
| [koprivnikarurnaa-oss](https://github.com/koprivnikarurnaa-oss) | [PR #9](https://github.com/PlutoKeating/dsh-lark-bot/pull/9):web 单写者适配器 + self-heal v2 + 守护自动重启<br>Web single-writer adapter + self-heal v2 + guardian auto-relaunch | ✅ 已合入<br>Merged |
|
|
748
|
+
| [Normanyin](https://github.com/Normanyin) | [PR #11](https://github.com/PlutoKeating/dsh-lark-bot/pull/11):`/newg` 自动建群命令<br>`/newg` auto-create group chat command | ✅ 已合入(cherry-pick)<br>Merged (cherry-pick) |
|
|
749
|
+
|
|
750
|
+
> 说明:GitHub 贡献者图按 commit 作者邮箱归因。PR #9 合入时的提交使用了本地通用身份
|
|
751
|
+
> `dsh-user <dsh-user@local>`(未绑定 GitHub 账号),因此未自动计入贡献者图;本表为仓库侧
|
|
752
|
+
> 的明确署名,PR #11 的提交身份已绑定其账号,合入后会自动计入。
|
|
753
|
+
>
|
|
754
|
+
> Note: GitHub's contributor graph attributes commits by author email. The commits merged via
|
|
755
|
+
> PR #9 carried a local generic identity (`dsh-user <dsh-user@local>`, not linked to a GitHub
|
|
756
|
+
> account), so they are not auto-counted in the graph; this table is the repository's explicit
|
|
757
|
+
> acknowledgment. PR #11's commits are authored under the contributor's linked account and will
|
|
758
|
+
> be credited automatically once merged.
|
|
759
|
+
|
|
721
760
|
## 许可与安全 | License & Security
|
|
722
761
|
|
|
723
762
|
- **许可证**:GNU Affero General Public License v3.0(见 `LICENSE`)。
|
|
@@ -820,39 +859,39 @@ and relaunches the full profile on `/safemode exit`.
|
|
|
820
859
|
| :--- | :--- | :--- |
|
|
821
860
|
| [awesome-dsh-plugins](https://github.com/AdamPlatin123/awesome-dsh-plugins) | ✅ 已收录 · 运行级可用<br>Listed · runtime-verified | 社区榜单标注 `✅ 运行级可用`,2026-08-14 agent 实测通过;收录条目更新至 v0.8.0([PR #127](https://github.com/AdamPlatin123/awesome-dsh-plugins/pull/127) 已合并),v0.10.2 同步已跟进([#139](https://github.com/AdamPlatin123/awesome-dsh-plugins/issues/139) 最新评论)<br>Shown as `✅ 运行级可用` in the community leaderboard; agent-tested on 2026-08-14; v0.8.0 entry merged via [PR #127](https://github.com/AdamPlatin123/awesome-dsh-plugins/pull/127); v0.10.2 sync requested in [#139](https://github.com/AdamPlatin123/awesome-dsh-plugins/issues/139) |
|
|
822
861
|
| [dshfind](https://dshfind.com/zh/plugins/PlutoKeating/dsh-lark-bot) | ✅ 已收录 · 详情页已上线<br>Listed · detail page live | 中英日韩四语详情页已上线(含安装命令与亮点),条目名称正常([issue #2](https://github.com/hikariming/dshfind/issues/2) 已关闭);v0.10.2 数据刷新已提交([issue #6](https://github.com/hikariming/dshfind/issues/6));顶部徽章 / 展示卡来自 dshfind<br>Four-language detail page is live (install command & highlights), entry name fixed ([issue #2](https://github.com/hikariming/dshfind/issues/2) closed); v0.10.2 data refresh requested in [issue #6](https://github.com/hikariming/dshfind/issues/6); the header badge / card comes from dshfind |
|
|
823
|
-
| [omdsh-dev/community](https://github.com/orgs/omdsh-dev/discussions/
|
|
862
|
+
| [omdsh-dev/community](https://github.com/orgs/omdsh-dev/discussions/11) | ✅ 已提交收录申请<br>Submission submitted | `[Plugin]` 收录申请(Discussion #11)已通过;v0.8.0 更新、六项独家亮点与 v0.10.1 / v0.10.2 稳定性修复说明均已发布在该讨论<br>`[Plugin]` submission (Discussion #11) accepted; v0.8.0 update, the six-exclusive-highlights summary and the v0.10.1 / v0.10.2 stability-fix notes are all posted there |
|
|
824
863
|
|
|
825
864
|
**更新请求进度 / Update request status**:
|
|
826
865
|
|
|
827
866
|
- awesome-dsh-plugins 收录条目更新:[#127](https://github.com/AdamPlatin123/awesome-dsh-plugins/pull/127) — ✅ 已合并
|
|
828
867
|
- dshfind 数据刷新请求(含条目名称异常修正):[#2](https://github.com/hikariming/dshfind/issues/2) — ✅ 已关闭,详情页已更新
|
|
829
|
-
- omdsh-dev/community 收录讨论更新:[Discussion #
|
|
868
|
+
- omdsh-dev/community 收录讨论更新:[Discussion #11 更新评论](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18021998) — ✅ 已发布
|
|
830
869
|
- awesome-dsh-plugins 榜单行同步至 v0.10.2:[#139 最新评论](https://github.com/AdamPlatin123/awesome-dsh-plugins/issues/139#issuecomment-5304382615) — 📨 已提交
|
|
831
870
|
- dshfind 数据刷新至 v0.10.2:[#6 跟进评论](https://github.com/hikariming/dshfind/issues/6#issuecomment-5304382647) — 📨 已提交
|
|
832
|
-
- omdsh-dev/community v0.10.1 更新说明:[Discussion #
|
|
833
|
-
- omdsh-dev/community v0.10.2 更新说明:[Discussion #
|
|
871
|
+
- omdsh-dev/community v0.10.1 更新说明:[Discussion #11 评论](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18034706) — ✅ 已发布
|
|
872
|
+
- omdsh-dev/community v0.10.2 更新说明:[Discussion #11 评论](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18034903) — ✅ 已发布
|
|
834
873
|
|
|
835
874
|
**Update requests**:
|
|
836
875
|
|
|
837
876
|
- awesome-dsh-plugins entry refresh: [#127](https://github.com/AdamPlatin123/awesome-dsh-plugins/pull/127) — ✅ merged
|
|
838
877
|
- dshfind data-refresh request (incl. fixing the entry name): [#2](https://github.com/hikariming/dshfind/issues/2) — ✅ closed, detail page updated
|
|
839
|
-
- omdsh-dev/community listing update: [Discussion #
|
|
878
|
+
- omdsh-dev/community listing update: [Discussion #11 update comment](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18021998) — ✅ posted
|
|
840
879
|
- awesome-dsh-plugins leaderboard sync to v0.10.2: [#139 comment](https://github.com/AdamPlatin123/awesome-dsh-plugins/issues/139#issuecomment-5304382615) — 📨 submitted
|
|
841
880
|
- dshfind data refresh to v0.10.2: [#6 follow-up](https://github.com/hikariming/dshfind/issues/6#issuecomment-5304382647) — 📨 submitted
|
|
842
|
-
- omdsh-dev/community v0.10.1 update: [Discussion #
|
|
843
|
-
- omdsh-dev/community v0.10.2 update: [Discussion #
|
|
881
|
+
- omdsh-dev/community v0.10.1 update: [Discussion #11 comment](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18034706) — ✅ posted
|
|
882
|
+
- omdsh-dev/community v0.10.2 update: [Discussion #11 comment](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18034903) — ✅ posted
|
|
844
883
|
|
|
845
884
|
**亮点跟进 / Highlights follow-ups**(六项独家能力与 issue #6 设计实现):
|
|
846
885
|
|
|
847
886
|
- awesome-dsh-plugins 榜单行同步(仓库描述 → 最新)与 agent-test 报告名称异常:[#139](https://github.com/AdamPlatin123/awesome-dsh-plugins/issues/139) — 📨 已提交(维护方已确认,等待渲染周期同步)
|
|
848
887
|
- dshfind 详情页补「对话内管理模型和密钥」亮点:[#2 跟进评论](https://github.com/hikariming/dshfind/issues/2#issuecomment-5301019067) — 📨 已提交
|
|
849
|
-
- omdsh 六项独家亮点补充(含 Guardian 设计实现):[Discussion #
|
|
888
|
+
- omdsh 六项独家亮点补充(含 Guardian 设计实现):[Discussion #11 亮点评论](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18026370) — 📨 已提交
|
|
850
889
|
|
|
851
890
|
**Highlights follow-ups** (six exclusive capabilities & the issue #6 design):
|
|
852
891
|
|
|
853
892
|
- awesome-dsh-plugins leaderboard row sync (repo description → latest) & agent-test name anomaly: [#139](https://github.com/AdamPlatin123/awesome-dsh-plugins/issues/139) — 📨 submitted (maintainer confirmed; awaiting the snapshot/render cycle)
|
|
854
893
|
- dshfind detail page: add the in-chat model/key management highlight: [#2 follow-up](https://github.com/hikariming/dshfind/issues/2#issuecomment-5301019067) — 📨 submitted
|
|
855
|
-
- omdsh six-exclusive-highlights summary (incl. the Guardian design): [Discussion #
|
|
894
|
+
- omdsh six-exclusive-highlights summary (incl. the Guardian design): [Discussion #11 highlights comment](https://github.com/orgs/omdsh-dev/discussions/11#discussioncomment-18026370) — 📨 submitted
|
|
856
895
|
|
|
857
896
|
## 免责声明 | Disclaimer
|
|
858
897
|
|
package/dist/cli.js
CHANGED
|
@@ -167,7 +167,7 @@ var init_own_package = __esm({
|
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
// src/adapters/dsh/acp-runtime.ts
|
|
170
|
-
import { spawn as spawn4 } from "
|
|
170
|
+
import { spawn as spawn4 } from "cross-spawn";
|
|
171
171
|
import { existsSync as existsSync4, realpathSync as realpathSync2 } from "fs";
|
|
172
172
|
import { readFileSync as readFileSync3 } from "fs";
|
|
173
173
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
@@ -1395,7 +1395,7 @@ var SdkDshAdapter = class {
|
|
|
1395
1395
|
init_dsh_compat();
|
|
1396
1396
|
init_dsh_runtime();
|
|
1397
1397
|
init_own_package();
|
|
1398
|
-
import { spawn as spawn3 } from "
|
|
1398
|
+
import { spawn as spawn3 } from "cross-spawn";
|
|
1399
1399
|
import { existsSync as existsSync3, realpathSync } from "fs";
|
|
1400
1400
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1401
1401
|
import { mkdir, writeFile } from "fs/promises";
|
|
@@ -2127,9 +2127,9 @@ async function writeFileAtomic(target, data, options = {}) {
|
|
|
2127
2127
|
}
|
|
2128
2128
|
}
|
|
2129
2129
|
async function rmSilently(path) {
|
|
2130
|
-
const { rm:
|
|
2130
|
+
const { rm: rm5 } = await import("fs/promises");
|
|
2131
2131
|
try {
|
|
2132
|
-
await
|
|
2132
|
+
await rm5(path, { force: true });
|
|
2133
2133
|
} catch {
|
|
2134
2134
|
}
|
|
2135
2135
|
}
|
|
@@ -3289,11 +3289,16 @@ function extractQuestionAnswer(kind, value, options) {
|
|
|
3289
3289
|
}
|
|
3290
3290
|
|
|
3291
3291
|
// src/session/heal.ts
|
|
3292
|
-
import { mkdir as mkdir5, readdir,
|
|
3292
|
+
import { cp, mkdir as mkdir5, readdir, rm } from "fs/promises";
|
|
3293
3293
|
import { homedir as homedir5 } from "os";
|
|
3294
3294
|
import { join as join8 } from "path";
|
|
3295
|
-
var
|
|
3296
|
-
var
|
|
3295
|
+
var BROKEN_ANCHORED = /session\s+["'`]?[A-Za-z0-9_-]+["'`]?\s+already has a persisted log(?: on disk)? that does not match this live session(?: \(id collision\))?|does not match this live session\s*\(id collision\)/i;
|
|
3296
|
+
var CORRUPT_ANCHORED = /corrupt(?:ed)? session log|session log[^\n]{0,80}?\bseq(?:uence)? gap\b|\bseq(?:uence)? gap\b[^\n]{0,80}?session log/i;
|
|
3297
|
+
function classifySessionError(message) {
|
|
3298
|
+
if (BROKEN_ANCHORED.test(message)) return "broken";
|
|
3299
|
+
if (CORRUPT_ANCHORED.test(message)) return "corrupt";
|
|
3300
|
+
return void 0;
|
|
3301
|
+
}
|
|
3297
3302
|
async function archiveSessionDir(sessionId) {
|
|
3298
3303
|
const home = homedir5();
|
|
3299
3304
|
const dshHome = process.env.DSH_HOME?.trim() || join8(home, ".dsh");
|
|
@@ -3316,12 +3321,14 @@ async function archiveSessionDir(sessionId) {
|
|
|
3316
3321
|
return void 0;
|
|
3317
3322
|
};
|
|
3318
3323
|
const found = await walk(root);
|
|
3319
|
-
if (found === void 0) return false;
|
|
3324
|
+
if (found === void 0) return { found: false };
|
|
3320
3325
|
const larkHome = process.env.DSH_LARK_HOME?.trim() || join8(home, ".dsh-lark");
|
|
3321
3326
|
const bakRoot = join8(larkHome, "_archived-sessions");
|
|
3327
|
+
const archivePath = join8(bakRoot, `${sessionId}-${Date.now()}`);
|
|
3322
3328
|
await mkdir5(bakRoot, { recursive: true });
|
|
3323
|
-
await
|
|
3324
|
-
|
|
3329
|
+
await cp(found, archivePath, { recursive: true, force: true });
|
|
3330
|
+
await rm(found, { recursive: true, force: true });
|
|
3331
|
+
return { found: true, archivePath };
|
|
3325
3332
|
}
|
|
3326
3333
|
|
|
3327
3334
|
// src/bridge/run-flow.ts
|
|
@@ -3344,10 +3351,20 @@ async function runAgentBatch(input) {
|
|
|
3344
3351
|
} catch (error) {
|
|
3345
3352
|
if (resuming) {
|
|
3346
3353
|
log.warn("run-flow", "resume-fallback", { scope: input.scope, sessionId });
|
|
3347
|
-
if (sessionId !== void 0 &&
|
|
3354
|
+
if (sessionId !== void 0 && classifySessionError(errorMessage(error)) === "corrupt") {
|
|
3348
3355
|
try {
|
|
3349
|
-
await archiveSessionDir(sessionId);
|
|
3350
|
-
|
|
3356
|
+
const archived = await archiveSessionDir(sessionId);
|
|
3357
|
+
if (archived.archivePath !== void 0) {
|
|
3358
|
+
log.info("session", "heal-archived", {
|
|
3359
|
+
sessionId,
|
|
3360
|
+
archivePath: archived.archivePath
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
} catch (archiveError) {
|
|
3364
|
+
log.fail("session", "heal-archive-failed", {
|
|
3365
|
+
sessionId,
|
|
3366
|
+
error: archiveError
|
|
3367
|
+
});
|
|
3351
3368
|
}
|
|
3352
3369
|
}
|
|
3353
3370
|
input.sessions.clearSession(input.scope);
|
|
@@ -3428,20 +3445,32 @@ async function runAttempt(input, cwd, sessionId, resuming, replyOptions) {
|
|
|
3428
3445
|
if (timedOut) return;
|
|
3429
3446
|
state = applyEvent(state, event, stopRequested);
|
|
3430
3447
|
state = { ...state, lastActivityMs: Date.now() };
|
|
3431
|
-
|
|
3448
|
+
const healKind = event.type === "error" && event.terminationReason === "failed" ? classifySessionError(event.message) : void 0;
|
|
3449
|
+
if (event.type === "error" && event.terminationReason === "failed" && healKind !== void 0 && !(resuming && !sawActivity)) {
|
|
3432
3450
|
const brokenId = input.sessions.getRaw(input.scope)?.sessionId;
|
|
3433
3451
|
if (brokenId !== void 0) {
|
|
3434
|
-
|
|
3435
|
-
if (
|
|
3452
|
+
let archivePath;
|
|
3453
|
+
if (healKind === "corrupt") {
|
|
3436
3454
|
try {
|
|
3437
|
-
await archiveSessionDir(brokenId);
|
|
3438
|
-
|
|
3455
|
+
const archived = await archiveSessionDir(brokenId);
|
|
3456
|
+
archivePath = archived.archivePath;
|
|
3457
|
+
if (archivePath !== void 0) {
|
|
3458
|
+
log.info("session", "heal-archived", {
|
|
3459
|
+
sessionId: brokenId,
|
|
3460
|
+
archivePath
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
} catch (error) {
|
|
3464
|
+
log.fail("session", "heal-archive-failed", {
|
|
3465
|
+
sessionId: brokenId,
|
|
3466
|
+
error
|
|
3467
|
+
});
|
|
3439
3468
|
}
|
|
3440
3469
|
}
|
|
3441
3470
|
input.sessions.clear(input.scope);
|
|
3442
3471
|
await input.channel.sendMarkdown(
|
|
3443
3472
|
input.chatId,
|
|
3444
|
-
|
|
3473
|
+
healKind === "corrupt" ? `\u26A0\uFE0F \u4F1A\u8BDD\u8BB0\u5F55\u635F\u574F\uFF0C\u5DF2\u5F52\u6863\u5E76\u91CD\u7F6E\uFF08\u5F52\u6863\uFF1A\`${archivePath ?? "\u5F52\u6863\u5931\u8D25"}\`\uFF09\u3002\u8BF7\u91CD\u65B0\u53D1\u9001\u4F60\u7684\u6D88\u606F\u3002` : "\u26A0\uFE0F \u4F1A\u8BDD\u72B6\u6001\u5F02\u5E38\uFF0C\u5DF2\u91CD\u7F6E\u4F1A\u8BDD\u6620\u5C04\uFF08\u5386\u53F2\u65E5\u5FD7\u4FDD\u7559\uFF0C\u672A\u5220\u9664\uFF09\u3002\u8BF7\u91CD\u65B0\u53D1\u9001\u4F60\u7684\u6D88\u606F\u3002",
|
|
3445
3474
|
{ ...replyOptions }
|
|
3446
3475
|
);
|
|
3447
3476
|
void run.stop();
|
|
@@ -3519,20 +3548,32 @@ async function runAttempt(input, cwd, sessionId, resuming, replyOptions) {
|
|
|
3519
3548
|
state = markInterrupted(state);
|
|
3520
3549
|
if (resuming && !sawActivity) throw error;
|
|
3521
3550
|
const runErrorText = errorMessage(error);
|
|
3522
|
-
|
|
3551
|
+
const healKind = classifySessionError(runErrorText);
|
|
3552
|
+
if (healKind !== void 0) {
|
|
3523
3553
|
const brokenSessionId = input.sessions.getRaw(input.scope)?.sessionId;
|
|
3524
3554
|
if (brokenSessionId !== void 0) {
|
|
3525
|
-
|
|
3526
|
-
if (
|
|
3555
|
+
let archivePath;
|
|
3556
|
+
if (healKind === "corrupt") {
|
|
3527
3557
|
try {
|
|
3528
|
-
await archiveSessionDir(brokenSessionId);
|
|
3529
|
-
|
|
3558
|
+
const archived = await archiveSessionDir(brokenSessionId);
|
|
3559
|
+
archivePath = archived.archivePath;
|
|
3560
|
+
if (archivePath !== void 0) {
|
|
3561
|
+
log.info("session", "heal-archived", {
|
|
3562
|
+
sessionId: brokenSessionId,
|
|
3563
|
+
archivePath
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
} catch (archiveError) {
|
|
3567
|
+
log.fail("session", "heal-archive-failed", {
|
|
3568
|
+
sessionId: brokenSessionId,
|
|
3569
|
+
error: archiveError
|
|
3570
|
+
});
|
|
3530
3571
|
}
|
|
3531
3572
|
}
|
|
3532
3573
|
input.sessions.clear(input.scope);
|
|
3533
3574
|
await input.channel.sendMarkdown(
|
|
3534
3575
|
input.chatId,
|
|
3535
|
-
|
|
3576
|
+
healKind === "corrupt" ? `\u26A0\uFE0F \u4F1A\u8BDD\u8BB0\u5F55\u635F\u574F\uFF0C\u5DF2\u5F52\u6863\u5E76\u91CD\u7F6E\uFF08\u5F52\u6863\uFF1A\`${archivePath ?? "\u5F52\u6863\u5931\u8D25"}\`\uFF09\u3002\u8BF7\u91CD\u65B0\u53D1\u9001\u4F60\u7684\u6D88\u606F\u3002` : "\u26A0\uFE0F \u4F1A\u8BDD\u72B6\u6001\u5F02\u5E38\uFF0C\u5DF2\u91CD\u7F6E\u4F1A\u8BDD\u6620\u5C04\uFF08\u5386\u53F2\u65E5\u5FD7\u4FDD\u7559\uFF0C\u672A\u5220\u9664\uFF09\u3002\u8BF7\u91CD\u65B0\u53D1\u9001\u4F60\u7684\u6D88\u606F\u3002",
|
|
3536
3577
|
{ ...replyOptions }
|
|
3537
3578
|
);
|
|
3538
3579
|
return;
|
|
@@ -3648,7 +3689,7 @@ function errorMessage(error) {
|
|
|
3648
3689
|
}
|
|
3649
3690
|
|
|
3650
3691
|
// src/config/dsh-config.ts
|
|
3651
|
-
import { mkdir as mkdir6, readFile as readFile3, rm, writeFile as writeFile4 } from "fs/promises";
|
|
3692
|
+
import { mkdir as mkdir6, readFile as readFile3, rm as rm2, writeFile as writeFile4 } from "fs/promises";
|
|
3652
3693
|
import { homedir as homedir6 } from "os";
|
|
3653
3694
|
import { dirname as dirname4, join as join9 } from "path";
|
|
3654
3695
|
import { Document, parseDocument } from "yaml";
|
|
@@ -3776,7 +3817,7 @@ async function withFileLock(filename, operation) {
|
|
|
3776
3817
|
try {
|
|
3777
3818
|
return await operation();
|
|
3778
3819
|
} finally {
|
|
3779
|
-
await
|
|
3820
|
+
await rm2(lockPath, { force: true }).catch(() => void 0);
|
|
3780
3821
|
}
|
|
3781
3822
|
}
|
|
3782
3823
|
var DshProviderManager = class {
|
|
@@ -4681,6 +4722,7 @@ var HELP = [
|
|
|
4681
4722
|
"**dsh-lark-bot \u547D\u4EE4**",
|
|
4682
4723
|
"",
|
|
4683
4724
|
"- `/new` `/reset` \u2014 \u5F00\u59CB\u65B0\u4F1A\u8BDD",
|
|
4725
|
+
"- `/newg <\u7FA4\u540D>` \u2014 \u81EA\u52A8\u65B0\u5EFA\u7FA4\u804A\uFF08\u62C9\u4F60\u5165\u7FA4\uFF09\u5E76\u5F00\u65B0\u4F1A\u8BDD\uFF0C\u5F53\u524D\u4F1A\u8BDD\u4FDD\u7559",
|
|
4684
4726
|
"- `/cd <path>` \u2014 \u5207\u6362\u5DE5\u4F5C\u76EE\u5F55\u5E76\u91CD\u7F6E\u4F1A\u8BDD",
|
|
4685
4727
|
"- `/ws list|save <name>|use <name>|remove <name>` \u2014 \u7BA1\u7406\u5DE5\u4F5C\u7A7A\u95F4",
|
|
4686
4728
|
"- `/status` \u2014 \u67E5\u770B\u5F53\u524D\u72B6\u6001",
|
|
@@ -4720,6 +4762,54 @@ async function handleNew(_args, ctx) {
|
|
|
4720
4762
|
interrupted > 0 ? `\u5DF2\u4E2D\u65AD ${String(interrupted)} \u4E2A\u4EFB\u52A1\u5E76\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002` : "\u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002"
|
|
4721
4763
|
);
|
|
4722
4764
|
}
|
|
4765
|
+
var MAX_GROUP_NAME_LENGTH = 60;
|
|
4766
|
+
function groupAppLink(chatId) {
|
|
4767
|
+
return `https://applink.feishu.cn/client/chat/open?chatId=${encodeURIComponent(chatId)}`;
|
|
4768
|
+
}
|
|
4769
|
+
async function handleNewGroup(args, ctx) {
|
|
4770
|
+
const name = args.trim();
|
|
4771
|
+
if (!name) {
|
|
4772
|
+
await reply5(ctx, "\u7528\u6CD5\uFF1A`/newg <\u7FA4\u540D>` \u2014 \u81EA\u52A8\u65B0\u5EFA\u7FA4\u804A\u5E76\u5F00\u59CB\u65B0\u4F1A\u8BDD");
|
|
4773
|
+
return;
|
|
4774
|
+
}
|
|
4775
|
+
if (name.length > MAX_GROUP_NAME_LENGTH) {
|
|
4776
|
+
await reply5(
|
|
4777
|
+
ctx,
|
|
4778
|
+
`\u7FA4\u540D\u8FC7\u957F\uFF08\u4E0A\u9650 ${String(MAX_GROUP_NAME_LENGTH)} \u5B57\u7B26\uFF0C\u5F53\u524D ${String(name.length)}\uFF09\u3002`
|
|
4779
|
+
);
|
|
4780
|
+
return;
|
|
4781
|
+
}
|
|
4782
|
+
if (!ctx.channel.createChat) {
|
|
4783
|
+
await reply5(ctx, "\u5F53\u524D\u6E20\u9053\u4E0D\u652F\u6301\u81EA\u52A8\u5EFA\u7FA4\u3002");
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4786
|
+
if (!ctx.senderId) {
|
|
4787
|
+
await reply5(ctx, "\u65E0\u6CD5\u8BC6\u522B\u53D1\u9001\u8005 open_id\uFF0C\u4E0D\u80FD\u81EA\u52A8\u5EFA\u7FA4\u3002");
|
|
4788
|
+
return;
|
|
4789
|
+
}
|
|
4790
|
+
try {
|
|
4791
|
+
const { chatId } = await ctx.channel.createChat({
|
|
4792
|
+
name,
|
|
4793
|
+
chatType: "private",
|
|
4794
|
+
chatMode: "group",
|
|
4795
|
+
inviteUserIds: [ctx.senderId],
|
|
4796
|
+
userIdType: "open_id"
|
|
4797
|
+
});
|
|
4798
|
+
await reply5(
|
|
4799
|
+
ctx,
|
|
4800
|
+
[
|
|
4801
|
+
`\u2705 \u5DF2\u521B\u5EFA\u7FA4\u804A\uFF1A**${name}**`,
|
|
4802
|
+
`- \u7FA4 ID\uFF1A\`${chatId}\``,
|
|
4803
|
+
`- \u5DF2\u5C06\u4F60\u52A0\u5165\u7FA4\u804A\uFF0C\u65B0\u4F1A\u8BDD\u5C06\u5728\u7FA4\u91CC\u81EA\u52A8\u5F00\u59CB\uFF08\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53D7\u5F71\u54CD\uFF09`,
|
|
4804
|
+
"",
|
|
4805
|
+
`\u{1F449} [\u6253\u5F00\u7FA4\u804A](${groupAppLink(chatId)})`
|
|
4806
|
+
].join("\n")
|
|
4807
|
+
);
|
|
4808
|
+
} catch (error) {
|
|
4809
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4810
|
+
await reply5(ctx, `\u274C \u5EFA\u7FA4\u5931\u8D25\uFF1A\`${message}\``);
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4723
4813
|
async function handleCd(args, ctx) {
|
|
4724
4814
|
const path = args.trim();
|
|
4725
4815
|
if (!path) {
|
|
@@ -4998,6 +5088,7 @@ async function handleHelp(_args, ctx) {
|
|
|
4998
5088
|
var handlers = {
|
|
4999
5089
|
"/new": handleNew,
|
|
5000
5090
|
"/reset": handleNew,
|
|
5091
|
+
"/newg": handleNewGroup,
|
|
5001
5092
|
"/cd": handleCd,
|
|
5002
5093
|
"/ws": handleWs,
|
|
5003
5094
|
"/status": handleStatus,
|
|
@@ -5053,6 +5144,9 @@ function adaptLarkChannel(channel) {
|
|
|
5053
5144
|
async sendCard(chatId, card, options) {
|
|
5054
5145
|
await channel.send(chatId, { card }, toLarkSendOptions(options));
|
|
5055
5146
|
},
|
|
5147
|
+
async createChat(opts) {
|
|
5148
|
+
return channel.createChat(opts);
|
|
5149
|
+
},
|
|
5056
5150
|
async streamCard(chatId, initial, producer, options) {
|
|
5057
5151
|
await channel.stream(
|
|
5058
5152
|
chatId,
|
|
@@ -6714,7 +6808,7 @@ import { join as join15 } from "path";
|
|
|
6714
6808
|
// src/guardian/install.ts
|
|
6715
6809
|
init_own_package();
|
|
6716
6810
|
init_process();
|
|
6717
|
-
import { mkdir as mkdir12, readFile as readFile12, rm as
|
|
6811
|
+
import { mkdir as mkdir12, readFile as readFile12, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
6718
6812
|
import { existsSync as existsSync5 } from "fs";
|
|
6719
6813
|
import { homedir as homedir8 } from "os";
|
|
6720
6814
|
import { join as join14 } from "path";
|
|
@@ -6944,7 +7038,7 @@ async function uninstallGuardian(options) {
|
|
|
6944
7038
|
if (process.platform === "linux") {
|
|
6945
7039
|
await run("systemctl", ["--user", "disable", "--now", `${GUARDIAN_LABEL}.service`]);
|
|
6946
7040
|
const unitPath = join14(root, ".config", "systemd", "user", `${GUARDIAN_LABEL}.service`);
|
|
6947
|
-
await
|
|
7041
|
+
await rm3(unitPath, { force: true });
|
|
6948
7042
|
await run("systemctl", ["--user", "daemon-reload"]);
|
|
6949
7043
|
messages.push("systemd user unit \u5DF2\u79FB\u9664\u5E76\u505C\u6B62\u3002");
|
|
6950
7044
|
} else if (process.platform === "darwin") {
|
|
@@ -6952,7 +7046,7 @@ async function uninstallGuardian(options) {
|
|
|
6952
7046
|
const plistPath = join14(root, "Library", "LaunchAgents", `${label}.plist`);
|
|
6953
7047
|
const uid = typeof options.userOverride === "string" ? options.userOverride : String(process.getuid?.() ?? 501);
|
|
6954
7048
|
await run("launchctl", ["bootout", `gui/${uid}`, plistPath]);
|
|
6955
|
-
await
|
|
7049
|
+
await rm3(plistPath, { force: true });
|
|
6956
7050
|
messages.push("launchd LaunchAgent \u5DF2\u79FB\u9664\u3002");
|
|
6957
7051
|
} else if (process.platform === "win32") {
|
|
6958
7052
|
const startupDir = join14(
|
|
@@ -6963,11 +7057,11 @@ async function uninstallGuardian(options) {
|
|
|
6963
7057
|
"Programs",
|
|
6964
7058
|
"Startup"
|
|
6965
7059
|
);
|
|
6966
|
-
await
|
|
7060
|
+
await rm3(join14(startupDir, `${GUARDIAN_LABEL}.cmd`), { force: true });
|
|
6967
7061
|
messages.push("Windows \u542F\u52A8\u9879\u5DF2\u79FB\u9664\u3002");
|
|
6968
7062
|
} else {
|
|
6969
7063
|
const fallbackPath = join14(root, ".dsh-lark", "guardian", `${GUARDIAN_LABEL}.sh`);
|
|
6970
|
-
await
|
|
7064
|
+
await rm3(fallbackPath, { force: true });
|
|
6971
7065
|
messages.push("\u542F\u52A8\u811A\u672C\u5DF2\u79FB\u9664\u3002");
|
|
6972
7066
|
}
|
|
6973
7067
|
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");
|
|
@@ -7324,11 +7418,11 @@ function packageSpecFor(name, version) {
|
|
|
7324
7418
|
// src/upgrade/runtime.ts
|
|
7325
7419
|
init_own_package();
|
|
7326
7420
|
import { existsSync as existsSync7 } from "fs";
|
|
7327
|
-
import { mkdir as mkdir15, rm as
|
|
7421
|
+
import { mkdir as mkdir15, rm as rm4, symlink } from "fs/promises";
|
|
7328
7422
|
import { join as join18 } from "path";
|
|
7329
7423
|
init_acp_runtime();
|
|
7330
7424
|
async function defaultRelink(linkPath, target) {
|
|
7331
|
-
await
|
|
7425
|
+
await rm4(linkPath, { recursive: true, force: true });
|
|
7332
7426
|
await mkdir15(join18(linkPath, ".."), { recursive: true });
|
|
7333
7427
|
await symlink(target, linkPath, process.platform === "win32" ? "junction" : "dir");
|
|
7334
7428
|
}
|
|
@@ -7848,6 +7942,8 @@ var MAX_TRANSCRIPT_ENTRIES = 30;
|
|
|
7848
7942
|
var SAFE_RUN_TIMEOUT_MS = 10 * 6e4;
|
|
7849
7943
|
var SAFE_SDK_PROVISION_TIMEOUT_MS = 3 * 6e4;
|
|
7850
7944
|
var SAFE_CARD_TICK_MS = 5e3;
|
|
7945
|
+
var RELAUNCH_COOLDOWN_MS = 6e4;
|
|
7946
|
+
var RELAUNCH_READY_TIMEOUT_MS = 15e3;
|
|
7851
7947
|
var GuardianService = class {
|
|
7852
7948
|
options;
|
|
7853
7949
|
state;
|
|
@@ -7858,6 +7954,8 @@ var GuardianService = class {
|
|
|
7858
7954
|
transcripts = /* @__PURE__ */ new Map();
|
|
7859
7955
|
downStreak = 0;
|
|
7860
7956
|
lastRelaunchAt;
|
|
7957
|
+
relaunchPending = false;
|
|
7958
|
+
relaunchStartedAt;
|
|
7861
7959
|
lastHeartbeatFreshAt;
|
|
7862
7960
|
timer;
|
|
7863
7961
|
ticking = false;
|
|
@@ -7871,6 +7969,8 @@ var GuardianService = class {
|
|
|
7871
7969
|
pollMs: options.pollMs ?? 2e3,
|
|
7872
7970
|
staleMs: options.staleMs ?? 15e3,
|
|
7873
7971
|
engineDeadMs: options.engineDeadMs ?? 12e4,
|
|
7972
|
+
relaunchCooldownMs: options.relaunchCooldownMs ?? RELAUNCH_COOLDOWN_MS,
|
|
7973
|
+
relaunchReadyTimeoutMs: options.relaunchReadyTimeoutMs ?? RELAUNCH_READY_TIMEOUT_MS,
|
|
7874
7974
|
takeoverGracePolls: options.takeoverGracePolls ?? 2,
|
|
7875
7975
|
sendDelayMs: options.sendDelayMs ?? 600,
|
|
7876
7976
|
safeAdapterMode: options.safeAdapterMode ?? "auto",
|
|
@@ -8004,6 +8104,15 @@ var GuardianService = class {
|
|
|
8004
8104
|
this.dshUp = up;
|
|
8005
8105
|
if (up) {
|
|
8006
8106
|
this.downStreak = 0;
|
|
8107
|
+
if (this.relaunchPending || this.state.relaunchedPid !== void 0) {
|
|
8108
|
+
this.relaunchPending = false;
|
|
8109
|
+
this.relaunchStartedAt = void 0;
|
|
8110
|
+
this.state.relaunchedPid = void 0;
|
|
8111
|
+
await this.save();
|
|
8112
|
+
this.log().info("guardian", "bridge-relaunch-ready", {
|
|
8113
|
+
dshProfile: this.state.dshProfile
|
|
8114
|
+
});
|
|
8115
|
+
}
|
|
8007
8116
|
await this.setSeenUp();
|
|
8008
8117
|
if (this.state.mode !== "standby" || this.channel !== void 0) {
|
|
8009
8118
|
await this.stopAllSafeRuns();
|
|
@@ -8018,22 +8127,61 @@ var GuardianService = class {
|
|
|
8018
8127
|
if (!this.state.profileSeenUp) return;
|
|
8019
8128
|
this.downStreak += 1;
|
|
8020
8129
|
if (this.downStreak < this.options.takeoverGracePolls) return;
|
|
8130
|
+
if (this.relaunchPending) {
|
|
8131
|
+
const ready = heartbeatFresh || await (this.options.findProcess ?? findProfileProcess)(
|
|
8132
|
+
this.state.dshProfile
|
|
8133
|
+
) !== void 0;
|
|
8134
|
+
if (ready) {
|
|
8135
|
+
this.relaunchPending = false;
|
|
8136
|
+
this.relaunchStartedAt = void 0;
|
|
8137
|
+
this.log().info("guardian", "bridge-relaunch-ready", {
|
|
8138
|
+
dshProfile: this.state.dshProfile
|
|
8139
|
+
});
|
|
8140
|
+
return;
|
|
8141
|
+
}
|
|
8142
|
+
if (now - (this.relaunchStartedAt ?? now) > this.options.relaunchReadyTimeoutMs) {
|
|
8143
|
+
this.relaunchPending = false;
|
|
8144
|
+
this.relaunchStartedAt = void 0;
|
|
8145
|
+
this.state.relaunchedPid = void 0;
|
|
8146
|
+
await this.save();
|
|
8147
|
+
this.log().fail("guardian", new Error("bridge relaunch did not come up within the readiness window"), {
|
|
8148
|
+
dshProfile: this.state.dshProfile
|
|
8149
|
+
});
|
|
8150
|
+
} else {
|
|
8151
|
+
return;
|
|
8152
|
+
}
|
|
8153
|
+
}
|
|
8021
8154
|
let relaunchedNow = false;
|
|
8022
8155
|
try {
|
|
8023
|
-
|
|
8024
|
-
if (this.lastRelaunchAt === void 0 || now2 - this.lastRelaunchAt > 6e4) {
|
|
8156
|
+
if (this.lastRelaunchAt === void 0 || now - this.lastRelaunchAt > this.options.relaunchCooldownMs) {
|
|
8025
8157
|
const bin = this.dshBin;
|
|
8026
|
-
if (bin
|
|
8027
|
-
const
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
this.
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8158
|
+
if (bin) {
|
|
8159
|
+
const stillDown = await (this.options.findProcess ?? findProfileProcess)(
|
|
8160
|
+
this.state.dshProfile
|
|
8161
|
+
);
|
|
8162
|
+
if (stillDown === void 0) {
|
|
8163
|
+
const spawn7 = this.options.spawnDetachedFn ?? spawnDetached;
|
|
8164
|
+
const spawned = spawn7("node", [
|
|
8165
|
+
bin,
|
|
8166
|
+
"--profile",
|
|
8167
|
+
this.state.dshProfile
|
|
8168
|
+
]);
|
|
8169
|
+
if (spawned.pid !== void 0) {
|
|
8170
|
+
this.state.relaunchedPid = spawned.pid;
|
|
8171
|
+
this.lastRelaunchAt = now;
|
|
8172
|
+
this.relaunchPending = true;
|
|
8173
|
+
this.relaunchStartedAt = now;
|
|
8174
|
+
relaunchedNow = true;
|
|
8175
|
+
await this.save();
|
|
8176
|
+
this.log().info("guardian", "bridge-relaunched", {
|
|
8177
|
+
pid: spawned.pid,
|
|
8178
|
+
dshProfile: this.state.dshProfile
|
|
8179
|
+
});
|
|
8180
|
+
}
|
|
8181
|
+
} else {
|
|
8182
|
+
this.log().warn("guardian", "bridge-relaunch-skipped", {
|
|
8183
|
+
dshProfile: this.state.dshProfile,
|
|
8184
|
+
pid: stillDown.pid
|
|
8037
8185
|
});
|
|
8038
8186
|
}
|
|
8039
8187
|
}
|
|
@@ -8359,7 +8507,12 @@ var GuardianService = class {
|
|
|
8359
8507
|
}
|
|
8360
8508
|
const spawn7 = this.options.spawnDetachedFn ?? spawnDetached;
|
|
8361
8509
|
const spawned = spawn7("node", [bin, "--profile", this.state.dshProfile]);
|
|
8362
|
-
if (spawned.pid !== void 0)
|
|
8510
|
+
if (spawned.pid !== void 0) {
|
|
8511
|
+
this.state.relaunchedPid = spawned.pid;
|
|
8512
|
+
this.lastRelaunchAt = (this.options.now ?? Date.now)();
|
|
8513
|
+
this.relaunchPending = true;
|
|
8514
|
+
this.relaunchStartedAt = this.lastRelaunchAt;
|
|
8515
|
+
}
|
|
8363
8516
|
await this.save();
|
|
8364
8517
|
await this.sendMarkdown(
|
|
8365
8518
|
msg.chatId,
|