lark-relay 0.4.13 → 0.4.14
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/bin/lark-relay.js +17 -7
- package/lib/cursors.js +25 -9
- package/lib/help.js +10 -9
- package/package.json +1 -1
package/bin/lark-relay.js
CHANGED
|
@@ -218,7 +218,7 @@ async function cmdTake(argv) {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
function cmdCursors(argv) {
|
|
221
|
-
const CURSORS_FLAGS = ['prune', 'json', 'help']
|
|
221
|
+
const CURSORS_FLAGS = ['prune', 'force', 'json', 'help']
|
|
222
222
|
const a = parseArgs(argv, { flags: CURSORS_FLAGS, keys: [] })
|
|
223
223
|
if (a._unknown.length) return rejectUnknown('lark-relay cursors', argv, a._unknown, CURSORS_FLAGS)
|
|
224
224
|
if (a.help) {
|
|
@@ -256,26 +256,36 @@ function cmdCursors(argv) {
|
|
|
256
256
|
|
|
257
257
|
// --prune:位置参数是要删的身份;没给就只清 stale 的那些
|
|
258
258
|
const names = a._
|
|
259
|
-
const res = cursors.prune(names)
|
|
259
|
+
const res = cursors.prune(names, { force: !!a.force })
|
|
260
260
|
|
|
261
261
|
// removed 的元素是 `name/app` —— 删的单位是 app,一个身份可能盯多个
|
|
262
262
|
for (const n of res.removed) process.stdout.write(`已删 ${n}\n`)
|
|
263
263
|
for (const n of res.missing) process.stderr.write(`没有这个游标身份:${n}\n`)
|
|
264
264
|
|
|
265
|
-
//
|
|
265
|
+
// 在跑的不删 -- take 会立刻把游标写回来。只有显式点名时才报告(见 cursors.prune)
|
|
266
266
|
for (const r of res.refused) {
|
|
267
267
|
process.stderr.write(
|
|
268
268
|
`跳过 ${r.name}:有 take 正在用它(pid ${r.pid})。\n` +
|
|
269
269
|
` 要清它先停掉那个 take(kill ${r.pid}),否则它会把游标立刻写回来。\n`,
|
|
270
270
|
)
|
|
271
271
|
}
|
|
272
|
-
|
|
272
|
+
|
|
273
|
+
// 「○ 暂停」的游标还能续接,删了就丢消费位置 -- 要 --force 再确认一次。
|
|
274
|
+
// 2026-09-06 真实踩到:点名删一个刚退出 take 的 idle 游标,那段消息的消费位置没了
|
|
275
|
+
for (const f of res.needForce) {
|
|
276
|
+
process.stderr.write(
|
|
277
|
+
`⚠ ${f.name}/${f.app} 是「○ 暂停」,位置 ${f.day} 仍在扫描窗口内\n` +
|
|
278
|
+
` 删了就丢续接凭据:重挂时会按 --since now 跳过这段已落盘的消息\n` +
|
|
279
|
+
` 确认要删 -> lark-relay cursors --prune ${f.name} --force\n`,
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!res.removed.length && !res.missing.length && !res.refused.length && !res.needForce.length) {
|
|
273
284
|
process.stdout.write(names.length ? '没删任何东西\n' : '没有 stale 游标,无需清理\n')
|
|
274
285
|
}
|
|
275
|
-
//
|
|
276
|
-
// 裸 prune 顺带跳过一个在跑的游标不是错误,那次清理是成功的
|
|
286
|
+
// 点名的目标一个都没删成才算失败
|
|
277
287
|
if (res.missing.length) return 2
|
|
278
|
-
if (res.refused.length
|
|
288
|
+
if ((res.refused.length || res.needForce.length) && !res.removed.length) return 3
|
|
279
289
|
return 0
|
|
280
290
|
}
|
|
281
291
|
|
package/lib/cursors.js
CHANGED
|
@@ -115,10 +115,13 @@ function removeStaleLock(name) {
|
|
|
115
115
|
/**
|
|
116
116
|
* 清理游标。
|
|
117
117
|
* @param {string[]} names 指定要删的身份(该身份下全部 app);空数组 = 只删 stale 的那些 app
|
|
118
|
-
* @
|
|
119
|
-
*
|
|
118
|
+
* @param {{force?:boolean}} opts force 才允许删「○ 暂停」(窗口内)的游标
|
|
119
|
+
* @returns {{removed:string[], refused:Array<{name:string,pid:number}>, missing:string[],
|
|
120
|
+
* needForce:Array<{name:string,app:string,day:string}>}}
|
|
121
|
+
* removed 的元素形如 `name/app` -- 删的单位是 app,不是整个身份。
|
|
122
|
+
* refused 只在**显式点名**时填充:裸 prune 遇到在跑的游标是静默跳过,不算例外
|
|
120
123
|
*/
|
|
121
|
-
function prune(names = []) {
|
|
124
|
+
function prune(names = [], opts = {}) {
|
|
122
125
|
const all = listCursors()
|
|
123
126
|
// 一个 name 可能有多个 app 行,按身份归并 -- live 判定是按身份的(锁以 name 为键)
|
|
124
127
|
const byName = new Map()
|
|
@@ -130,6 +133,7 @@ function prune(names = []) {
|
|
|
130
133
|
const removed = []
|
|
131
134
|
const refused = []
|
|
132
135
|
const missing = []
|
|
136
|
+
const needForce = []
|
|
133
137
|
|
|
134
138
|
const targets = names.length ? names : [...byName.keys()]
|
|
135
139
|
|
|
@@ -139,23 +143,35 @@ function prune(names = []) {
|
|
|
139
143
|
missing.push(name)
|
|
140
144
|
continue
|
|
141
145
|
}
|
|
142
|
-
// live 一律不删,即使显式点名:take 还在跑,它会立刻把游标写回来 --
|
|
143
|
-
// 删了是白删,还会让人以为清理没生效
|
|
144
146
|
const live = rows.find((r) => r.state === 'live')
|
|
145
147
|
if (live) {
|
|
146
|
-
|
|
148
|
+
// 显式点名时必须解释为什么没删(用户明确要求了);
|
|
149
|
+
// 而裸 prune 的意图是「清掉失效的」-- 在跑的游标本就不在这个视野里,
|
|
150
|
+
// 报告它是噪音(实测:3 个 take 在跑时,一次日常清理吐 3 段无关提示)
|
|
151
|
+
if (names.length) refused.push({ name, pid: live.pid })
|
|
147
152
|
continue
|
|
148
153
|
}
|
|
149
154
|
// 显式点名 = 清该身份的全部 app(用户明确不盯了);
|
|
150
155
|
// 裸 prune = 只清 stale 的那些 app,窗口内的留着续接
|
|
151
156
|
const victims = names.length ? rows : rows.filter((r) => r.state === 'stale')
|
|
157
|
+
let deletedAny = false
|
|
152
158
|
for (const r of victims) {
|
|
153
|
-
|
|
159
|
+
// ⚠️ 「○ 暂停」= 位置还在扫描窗口内,**删了就丢续接凭据**(重挂会按 --since now
|
|
160
|
+
// 跳过这段已落盘的消息)。stale 删掉无损,idle 不是 -- 故 idle 要 --force 兜一道。
|
|
161
|
+
// 2026-09-06 真实踩到:点名删一个刚退出 take 的 idle 游标,消费位置就此丢失
|
|
162
|
+
if (r.state === 'idle' && !opts.force) {
|
|
163
|
+
needForce.push({ name, app: r.app, day: r.day })
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
if (removeCursorApp(name, r.app)) {
|
|
167
|
+
removed.push(`${name}/${r.app}`)
|
|
168
|
+
deletedAny = true
|
|
169
|
+
}
|
|
154
170
|
}
|
|
155
|
-
if (
|
|
171
|
+
if (deletedAny) removeStaleLock(name)
|
|
156
172
|
}
|
|
157
173
|
|
|
158
|
-
return { removed, refused, missing }
|
|
174
|
+
return { removed, refused, missing, needForce }
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
module.exports = {
|
package/lib/help.js
CHANGED
|
@@ -120,21 +120,22 @@ const CURSORS_HELP = `游标治理:看清哪些还在值守,清掉已失效的
|
|
|
120
120
|
|
|
121
121
|
lark-relay cursors # 列出全部游标及状态
|
|
122
122
|
lark-relay cursors --prune # 清掉全部 stale 的
|
|
123
|
-
lark-relay cursors --prune <name>... # 清指定身份的全部 app
|
|
123
|
+
lark-relay cursors --prune <name>... # 清指定身份的全部 app
|
|
124
|
+
lark-relay cursors --prune <name> --force # 连「○ 暂停」的一起清
|
|
124
125
|
|
|
125
126
|
三态:● 在跑 / ○ 暂停(窗口内,可续接) / ⚠ stale(超窗口,续接无意义)
|
|
126
127
|
|
|
128
|
+
**只有 stale 是无条件可清的**:
|
|
129
|
+
● 在跑 不删。take 会立刻把游标写回来
|
|
130
|
+
○ 暂停 要 --force。它还能续接,删了重挂就按 --since now 跳过这段消息
|
|
131
|
+
⚠ stale 直接清。那段消息 gc 已回收,续接和 --since now 等价 -> 删它无损
|
|
132
|
+
|
|
127
133
|
删的单位是 **app**,不是身份:一个 --name 可以同时盯多个 app,
|
|
128
|
-
裸 --prune 只清其中 stale
|
|
134
|
+
裸 --prune 只清其中 stale 的那些(在跑的静默跳过)。
|
|
129
135
|
连带删该 app 的 dedup 记录(<app>.seen.json),不留会让重挂后头几条被误判重复。
|
|
130
136
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
删它不会丢任何还取得到的消息,留着只会让 status 虚报积压。
|
|
134
|
-
反过来,窗口内的「○ 暂停」不自动删 -- 那是「关了会话、过阵子接着盯」的续接凭据。
|
|
135
|
-
|
|
136
|
-
退出码:0 正常(含「跳过了在跑的那个」),2 参数错或点名的身份不存在,
|
|
137
|
-
3 点名的目标全在跑、一个都没清掉`
|
|
137
|
+
退出码:0 正常,2 参数错或点名的身份不存在,
|
|
138
|
+
3 点名的目标一个都没清掉(在跑,或需要 --force)`
|
|
138
139
|
|
|
139
140
|
const GUIDE = `lark-relay -- Lark 事件中继站。两件事:收下来 / 我来取。
|
|
140
141
|
|