lark-relay 0.4.2 → 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 CHANGED
@@ -34,7 +34,7 @@ Lark 事件是**流式**的 -- 进程不在的时刻,消息**永久丢失**,不
34
34
  ## 命令
35
35
 
36
36
  ```bash
37
- lark-relay collect # 底座:全部 profile 各起 consume -> 原子落盘(systemd 常驻)
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/bin/lark-relay.js CHANGED
@@ -258,7 +258,12 @@ async function cmdDispatch(argv) {
258
258
  if (a.list) return dispatch.cmdList()
259
259
  const task = a._[0]
260
260
  if (!task) {
261
- process.stdout.write(`${help.DISPATCH_HELP}\n`)
261
+ // 空参 = 求教,和 take 一致(exit 0);带了 flag 却没任务名是写错,短错误
262
+ if (argv.length === 0) {
263
+ process.stdout.write(`${help.DISPATCH_HELP}\n`)
264
+ return 0
265
+ }
266
+ process.stderr.write('需要任务名($LR_ROOT/<任务名>/dispatch.yaml)。看现有任务:`lark-relay dispatch --list`\n')
262
267
  return 2
263
268
  }
264
269
  return await dispatch.run(task, { once: !!a.once })
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
- `systemd 单元里 PATH 要用 fnm default alias 的稳定路径,不能用会话级的 multishell 路径`,
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
- systemctl enable --now lark-relay-collect # 常驻(推荐)
56
+ 常驻:Linux systemd,macOS launchd(单元模板见仓库 systemd//launchd 目录)
57
57
 
58
58
  app 列表实时读 \`lark-cli profile list\`,新增 profile 自动纳入,无需改配置。
59
59
  每 app 一个子进程;某个挂了单独重启,不影响其他。
@@ -64,6 +64,9 @@ tokenStatus 为 expired 的 profile 自动跳过并 warn(永久失败,重试是
64
64
  8s 后才起 consumer,收到 0 条)。collect 独立常驻,才能让 take/dispatch 侧
65
65
  崩了、claude 跑 30 分钟、会话关几小时,都不丢消息。
66
66
 
67
+ 装完先 \`lark-relay status\` 确认 -- collect 行应为 running;不在跑看
68
+ 服务日志(Linux \`journalctl -u lark-relay-collect\`;macOS \`log show --predicate 'process == "lark-relay"'\`)。
69
+
67
70
  落盘:~/.lark-relay/store/<app>/<YYYY-MM-DD>/<纳秒>_<pid>_<seq>.json
68
71
  回收由 collect 进程内每小时自查一次,删超期的整个日期目录(不另起 gc 单元/timer)。
69
72
 
@@ -78,15 +81,17 @@ const STATUS_HELP = `一眼看清全局:谁在跑、积压多少、游标在哪
78
81
  lark-relay status --json # 机器可读
79
82
 
80
83
  排障入口:某消费者「落后」很多 -> 它的 take/dispatch 没在跑或卡住;
81
- 「无消费者」= 白采,可考虑 --exclude。`
84
+ 「无消费者」= 白采,可考虑 --exclude
85
+ collect 行不是 running -> 看服务日志(systemd journal 或 launchd 统一日志),
86
+ collect 停摆的每一秒都在丢消息。`
82
87
 
83
88
  const DISPATCH_HELP = `常驻:等消息 -> 唤起 claude 干活 -> 结果回群。无人在场也跑。
84
89
 
85
90
  lark-relay dispatch --list # 列出所有 dispatch 任务及状态
86
- lark-relay dispatch <task> --once # 前台跑一轮(调试,不进 systemd)
87
- systemctl enable --now lark-relay-dispatch@<task>
91
+ lark-relay dispatch <task> --once # 前台跑一轮(调试,不进常驻服务)
92
+ 常驻:Linux systemd,macOS 用 launchd(单元模板见仓库 systemd//launchd 目录)
88
93
 
89
- --list 输出:任务名 / app / 群数 / systemd 单元是否 active / 上次 ledger 时间
94
+ --list 输出:任务名 / app / 群数 / 常驻服务是否 active / 上次 ledger 时间
90
95
 
91
96
  任务发现:扫 $LR_ROOT/*/dispatch.yaml -- 有文件即是任务,无需注册表。
92
97
  LR_ROOT 在单元里指定(唯一的目录绑定,与代码无关)。
@@ -115,7 +120,13 @@ mode 三个场景(一个键定死全部行为,不用拼组合)
115
120
  最佳实践
116
121
  · 边界写 instructions,别指望 --add-dir 目录的 CLAUDE.md(启动不加载)
117
122
  · 改完 yaml 先 --once 跑一轮验证,再 enable
118
- · 高风险发布仍建议人工执行`
123
+ · 高风险发布仍建议人工执行
124
+
125
+ 退出码(--once)
126
+ 0 跑了一轮,或没等到消息但正常结束
127
+ 1 取批异常(看 stderr)
128
+ 2 任务不存在或配置错
129
+ 4 等消息超时(5min 没消息)`
119
130
 
120
131
  const GUIDE = `lark-relay -- Lark 事件中继站。三个命令:收下来 / 我来取 / 派给别人。
121
132
 
@@ -136,6 +147,7 @@ const GUIDE = `lark-relay -- Lark 事件中继站。三个命令:收下来 / 我
136
147
  前置:collect 必须在跑
137
148
  lark-relay status # collect 行应为 running
138
149
  进程不在的时刻消息永久丢失,不是延迟送达。
150
+ 不在跑 -> 起常驻服务(Linux systemd / macOS launchd,模板见仓库;或 \`lark-relay collect\` 前台调试)
139
151
 
140
152
  take 三步(照做)
141
153
  1. 查群 ID lark-cli --profile <app> im +chat-search --as user --query "<群名>"
@@ -150,7 +162,7 @@ take 三步(照做)
150
162
  建 dispatch 任务
151
163
  1. 在任务目录写 dispatch.yaml(\`lark-relay dispatch --help\` 有模板)
152
164
  2. lark-relay dispatch <task> --once # 前台验一轮
153
- 3. systemctl enable --now lark-relay-dispatch@<task>
165
+ 3. 起常驻服务(Linux systemd / macOS launchd,模板见仓库)
154
166
 
155
167
  常见错误
156
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 collectUnit = 'lark-relay-collect.service'
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: unitState(unit),
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(' systemctl enable --now lark-relay-collect')
178
+ out.push(` ${s.collect.hint}`)
149
179
  }
150
180
  out.push('')
151
181
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lark-relay",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Lark event relay: collect events to disk, take a batch when you need it, or dispatch work to an AI agent.",
5
5
  "keywords": [
6
6
  "lark",