lark-relay 0.4.3 → 0.4.4
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 +1 -1
- package/lib/collect.js +1 -1
- package/lib/help.js +8 -8
- package/lib/status.js +37 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Lark 事件是**流式**的 -- 进程不在的时刻,消息**永久丢失**,不
|
|
|
34
34
|
## 命令
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
lark-relay collect # 底座:全部 profile 各起 consume -> 原子落盘(
|
|
37
|
+
lark-relay collect # 底座:全部 profile 各起 consume -> 原子落盘(常驻服务)
|
|
38
38
|
lark-relay take … # 场景1:阻塞等一批 -> 输出 -> 退出
|
|
39
39
|
lark-relay dispatch <task> # 场景2:常驻循环 = take + 唤起 claude
|
|
40
40
|
lark-relay status # 谁在跑 / 各 app 积压 / 各游标位置
|
package/lib/collect.js
CHANGED
|
@@ -120,7 +120,7 @@ async function runCollect(opts) {
|
|
|
120
120
|
`collect 依赖 lark-cli 提供认证与事件总线。检查:\n` +
|
|
121
121
|
` which ${larkcli.CLI} # 装了吗、在 PATH 里吗\n` +
|
|
122
122
|
` ${larkcli.CLI} profile list # 能跑吗\n` +
|
|
123
|
-
|
|
123
|
+
`常驻服务(systemd 单元/launchd plist)的 PATH 要用 fnm default alias 的稳定路径,不能用会话级的 multishell 路径`,
|
|
124
124
|
)
|
|
125
125
|
e.userFacing = true
|
|
126
126
|
throw e
|
package/lib/help.js
CHANGED
|
@@ -53,7 +53,7 @@ function takeHelp(apps) {
|
|
|
53
53
|
const COLLECT_HELP = `把全部 lark-cli profile 的事件收下来,原子落盘。零参数、零配置。
|
|
54
54
|
|
|
55
55
|
lark-relay collect # 前台跑(调试)
|
|
56
|
-
|
|
56
|
+
常驻:Linux 用 systemd,macOS 用 launchd(单元模板见仓库 systemd//launchd 目录)
|
|
57
57
|
|
|
58
58
|
app 列表实时读 \`lark-cli profile list\`,新增 profile 自动纳入,无需改配置。
|
|
59
59
|
每 app 一个子进程;某个挂了单独重启,不影响其他。
|
|
@@ -65,7 +65,7 @@ tokenStatus 为 expired 的 profile 自动跳过并 warn(永久失败,重试是
|
|
|
65
65
|
崩了、claude 跑 30 分钟、会话关几小时,都不丢消息。
|
|
66
66
|
|
|
67
67
|
装完先 \`lark-relay status\` 确认 -- collect 行应为 running;不在跑看
|
|
68
|
-
\`
|
|
68
|
+
服务日志(Linux \`journalctl -u lark-relay-collect\`;macOS \`log show --predicate 'process == "lark-relay"'\`)。
|
|
69
69
|
|
|
70
70
|
落盘:~/.lark-relay/store/<app>/<YYYY-MM-DD>/<纳秒>_<pid>_<seq>.json
|
|
71
71
|
回收由 collect 进程内每小时自查一次,删超期的整个日期目录(不另起 gc 单元/timer)。
|
|
@@ -82,16 +82,16 @@ const STATUS_HELP = `一眼看清全局:谁在跑、积压多少、游标在哪
|
|
|
82
82
|
|
|
83
83
|
排障入口:某消费者「落后」很多 -> 它的 take/dispatch 没在跑或卡住;
|
|
84
84
|
「无消费者」= 白采,可考虑 --exclude。
|
|
85
|
-
collect 行不是 running ->
|
|
85
|
+
collect 行不是 running -> 看服务日志(systemd journal 或 launchd 统一日志),
|
|
86
86
|
collect 停摆的每一秒都在丢消息。`
|
|
87
87
|
|
|
88
88
|
const DISPATCH_HELP = `常驻:等消息 -> 唤起 claude 干活 -> 结果回群。无人在场也跑。
|
|
89
89
|
|
|
90
90
|
lark-relay dispatch --list # 列出所有 dispatch 任务及状态
|
|
91
|
-
lark-relay dispatch <task> --once # 前台跑一轮(
|
|
92
|
-
|
|
91
|
+
lark-relay dispatch <task> --once # 前台跑一轮(调试,不进常驻服务)
|
|
92
|
+
常驻:Linux 用 systemd,macOS 用 launchd(单元模板见仓库 systemd//launchd 目录)
|
|
93
93
|
|
|
94
|
-
--list 输出:任务名 / app / 群数 /
|
|
94
|
+
--list 输出:任务名 / app / 群数 / 常驻服务是否 active / 上次 ledger 时间
|
|
95
95
|
|
|
96
96
|
任务发现:扫 $LR_ROOT/*/dispatch.yaml -- 有文件即是任务,无需注册表。
|
|
97
97
|
LR_ROOT 在单元里指定(唯一的目录绑定,与代码无关)。
|
|
@@ -147,7 +147,7 @@ const GUIDE = `lark-relay -- Lark 事件中继站。三个命令:收下来 / 我
|
|
|
147
147
|
前置:collect 必须在跑
|
|
148
148
|
lark-relay status # collect 行应为 running
|
|
149
149
|
进程不在的时刻消息永久丢失,不是延迟送达。
|
|
150
|
-
不在跑 ->
|
|
150
|
+
不在跑 -> 起常驻服务(Linux systemd / macOS launchd,模板见仓库;或 \`lark-relay collect\` 前台调试)
|
|
151
151
|
|
|
152
152
|
take 三步(照做)
|
|
153
153
|
1. 查群 ID lark-cli --profile <app> im +chat-search --as user --query "<群名>"
|
|
@@ -162,7 +162,7 @@ take 三步(照做)
|
|
|
162
162
|
建 dispatch 任务
|
|
163
163
|
1. 在任务目录写 dispatch.yaml(\`lark-relay dispatch --help\` 有模板)
|
|
164
164
|
2. lark-relay dispatch <task> --once # 前台验一轮
|
|
165
|
-
3.
|
|
165
|
+
3. 起常驻服务(Linux systemd / macOS launchd,模板见仓库)
|
|
166
166
|
|
|
167
167
|
常见错误
|
|
168
168
|
· 拿不到消息 -> 九成是 bot 不在群。验 bot 必须 --as user,
|
package/lib/status.js
CHANGED
|
@@ -39,6 +39,39 @@ function unitMainPid(unit) {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// 服务管理器按平台分治:Linux systemd / macOS launchd。
|
|
43
|
+
// 单元名/label 是部署约定,与 systemd 单元、launchd plist 同源;
|
|
44
|
+
// LR_COLLECT_LABEL / LR_DISPATCH_LABEL_PREFIX 是改名部署的逃生舱。
|
|
45
|
+
const COLLECT_LABEL = process.env.LR_COLLECT_LABEL || 'com.adaex.lark-relay-collect'
|
|
46
|
+
const DISPATCH_LABEL_PREFIX = process.env.LR_DISPATCH_LABEL_PREFIX || 'com.adaex.lark-relay-dispatch'
|
|
47
|
+
|
|
48
|
+
function launchctlState(label) {
|
|
49
|
+
try {
|
|
50
|
+
const out = execFileSync('launchctl', ['print', `system/${label}`], { encoding: 'utf8' })
|
|
51
|
+
const state = (out.match(/^\s*state\s*=\s*(\S+)/m) || [])[1]
|
|
52
|
+
const pid = Number((out.match(/^\s*pid\s*=\s*(\d+)/m) || [])[1]) || null
|
|
53
|
+
return { state: state === 'running' ? 'active' : state || 'inactive', pid }
|
|
54
|
+
} catch {
|
|
55
|
+
return { state: 'inactive', pid: null }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// kind: 'collect' | 'dispatch';dispatch 带任务名。
|
|
60
|
+
function serviceState(kind, name) {
|
|
61
|
+
if (process.platform === 'darwin') {
|
|
62
|
+
const label = kind === 'collect' ? COLLECT_LABEL : `${DISPATCH_LABEL_PREFIX}.${name}`
|
|
63
|
+
return launchctlState(label)
|
|
64
|
+
}
|
|
65
|
+
const unit = kind === 'collect' ? 'lark-relay-collect.service' : `lark-relay-dispatch@${name}.service`
|
|
66
|
+
return { state: unitState(unit), pid: unitMainPid(unit) }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function collectHint() {
|
|
70
|
+
return process.platform === 'darwin'
|
|
71
|
+
? `sudo launchctl bootstrap system /Library/LaunchDaemons/${COLLECT_LABEL}.plist`
|
|
72
|
+
: 'systemctl enable --now lark-relay-collect'
|
|
73
|
+
}
|
|
74
|
+
|
|
42
75
|
// 消费者 = cursors/ 下的目录。每个记录它在各 app 上的游标位置。
|
|
43
76
|
function listConsumers() {
|
|
44
77
|
let names
|
|
@@ -75,9 +108,7 @@ function lagOf(app, cursor) {
|
|
|
75
108
|
}
|
|
76
109
|
|
|
77
110
|
async function buildStatus() {
|
|
78
|
-
const
|
|
79
|
-
const state = unitState(collectUnit)
|
|
80
|
-
const pid = unitMainPid(collectUnit)
|
|
111
|
+
const { state, pid } = serviceState('collect')
|
|
81
112
|
|
|
82
113
|
let profiles = []
|
|
83
114
|
try {
|
|
@@ -111,7 +142,6 @@ async function buildStatus() {
|
|
|
111
142
|
|
|
112
143
|
const tasks = []
|
|
113
144
|
for (const t of listTasks()) {
|
|
114
|
-
const unit = `lark-relay-dispatch@${t.name}.service`
|
|
115
145
|
let lastLedgerMs = null
|
|
116
146
|
for (const chat of t.config.chats || []) {
|
|
117
147
|
try {
|
|
@@ -123,12 +153,12 @@ async function buildStatus() {
|
|
|
123
153
|
name: t.name,
|
|
124
154
|
app: t.config.app,
|
|
125
155
|
chats: (t.config.chats || []).length,
|
|
126
|
-
state:
|
|
156
|
+
state: serviceState('dispatch', t.name).state,
|
|
127
157
|
lastLedgerMs,
|
|
128
158
|
})
|
|
129
159
|
}
|
|
130
160
|
|
|
131
|
-
return { collect: { state, pid, usable, expired }, apps: rows, tasks }
|
|
161
|
+
return { collect: { state, pid, usable, expired, hint: collectHint() }, apps: rows, tasks }
|
|
132
162
|
}
|
|
133
163
|
|
|
134
164
|
function formatStatus(s) {
|
|
@@ -145,7 +175,7 @@ function formatStatus(s) {
|
|
|
145
175
|
out.push(head)
|
|
146
176
|
if (s.collect.state !== 'active') {
|
|
147
177
|
out.push(' ⚠ collect 没在跑 —— 此刻的消息会永久丢失,不是延迟送达')
|
|
148
|
-
out.push(
|
|
178
|
+
out.push(` ${s.collect.hint}`)
|
|
149
179
|
}
|
|
150
180
|
out.push('')
|
|
151
181
|
|