nx-kv 0.8.4 → 0.8.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  本文件记录对外可见的变更。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
4
4
 
5
+ ## [0.8.5] - 2026-10-13
6
+
7
+ ### Added
8
+
9
+ - **本地任务新增「全局任务池」(跨目录共享队列 + 工作窃取)**,参考 Go 调度器的
10
+ local runq + global runq 两层模型:
11
+ - `wait` 的领取顺序变为:本目录队列 → **全局任务池** → 阻塞。本地取不到时自动
12
+ 去全局领,全局也没有才阻塞;**显式传 `--cwd` 不会关闭全局兜底**(防止调用方
13
+ 习惯性传 cwd 导致池子无人领取)。返回新增 `source: "local" | "global"` 标明
14
+ 任务来源,旧字段全部保留。
15
+ - 发布进池子:`localtask add --global "<任务文本>"`(CLI/HTTP 同名 flag;
16
+ `--global` 时 `--cwd` 被忽略)。不限定接收方、谁闲谁领的任务走这里。
17
+ - 多个 agent 同时阻塞时,全局任务按阻塞先后(FIFO)实时投递给最早等待的;
18
+ 跨进程由既有 0.5s 轮询兜底,同样本地优先、全局其次。
19
+ - 全局主题在 `localtask list` 与面板「本地任务」页永远置顶(名称「全局任务」,
20
+ key 固定 `__GLOBAL__`,卡片带「全局」徽章);面板回填表单新增「全局」勾选,
21
+ 选中全局主题卡片时只能推全局。
22
+ - 全局主题默认受保护:`topic remove --key __GLOBAL__` 需 `--force`、
23
+ `topic remove-many` 将其计入 `skipped`(reason `protected`)、`prune` 永不清理;
24
+ 它不是等待源——`cancel` 返回 `global_not_waitable`、永不计入等待数。
25
+ - 任务级 `get/update/remove/reorder` 新增 `--key` 寻址(与 `--cwd` 二选一),
26
+ 全局主题没有 cwd、只能按 key 管理。
27
+
28
+ ### Fixed
29
+
30
+ - **落盘并发安全**:`saveStore` 进程内串行化(链式队列)——此前同进程两个写操作
31
+ 重叠时共用同一个 `.tmp` 中转文件,先完成的 rename 会把 tmp 消耗掉、后一个
32
+ rename 直接 ENOENT 崩溃(全局唤醒与等待复位并发时必现);rename 另加 Windows
33
+ EPERM/EACCES 小退避重试(目标文件被并发读者/杀软短暂锁住的瞬态失败)。
34
+
5
35
  ## [0.8.4] - 2026-10-12
6
36
 
7
37
  ### Added
@@ -26,13 +26,18 @@ nx-kv localtask add "<任务文本>" --cwd "<目标工作目录>" --json
26
26
 
27
27
  # 可选:顺带刷新该目录主题的现状展示(面板/弹窗顶部那句话)
28
28
  nx-kv localtask add "<任务文本>" --cwd "<目标目录>" --summary "<该目录当前现状>" --json
29
+
30
+ # 不限定接收方、谁闲谁领 → 推到全局任务池(--cwd 被忽略):
31
+ # 任何目录的 agent 在本地队列空时都会自动领取(返回 source:"global")
32
+ nx-kv localtask add --global "<任务文本>" --json
29
33
  ```
30
34
 
31
35
  返回 `{status:"ok", task:{id,text,...}, queued|delivered}`:
32
36
 
33
37
  - `queued:true`(跨进程提交的常态)→ 已入队;目标目录里正在 wait 的 agent
34
- 会在约 0.5 秒内取走,没人等就排在队列里等它下次领取。
35
- - 记下 `task.id`,向用户报告「已提交到哪个目录、第几号任务」。
38
+ 会在约 0.5 秒内取走,没人等就排在队列里等它下次领取。`--global` 提交则排在
39
+ 全局任务池,任何目录空闲的 agent 自动领取。
40
+ - 记下 `task.id`,向用户报告「已提交到哪个目录(或全局池)、第几号任务」。
36
41
 
37
42
  ## 任务文本怎么写(一句话版)
38
43
 
@@ -48,12 +53,14 @@ nx-kv localtask add "<任务文本>" --cwd "<目标目录>" --summary "<该目
48
53
  ## 硬规则
49
54
 
50
55
  1. **`--cwd` 必须填接收方 agent 实际所在的目录**(目录名即主题;同名目录共用
51
- 队列)。填错了任务就排到了没人看的队列——不确定就先问用户。
56
+ 队列)。填错了任务就排到了没人看的队列——不确定就先问用户。**不限定接收方**
57
+ 的通用任务才走 `--global`(无需 cwd)。
52
58
  2. **任务文本自包含**:不写「如上所述」「刚才那个」,不引用本对话;路径写全。
53
59
  3. **提交前把提炼结果给用户过目**(尤其需求是提炼出来的而非用户原话时),
54
60
  确认后再 `add`。
55
61
  4. **大需求拆成多条按序提交**,每条一个可独立完成的单元;顺序重要时提交后用
56
- `localtask reorder` 校正优先级。
62
+ `localtask reorder` 校正优先级。**全局池不保顺序**——「先做 A 再做 B」这类
63
+ 有依赖的步骤必须 `--cwd` 指名提交到同一目录队列,不要进池子。
57
64
 
58
65
  ## 什么时候不用
59
66
 
@@ -63,6 +70,7 @@ nx-kv localtask add "<任务文本>" --cwd "<目标目录>" --summary "<该目
63
70
  | 主动领取排给**本目录**的任务 | kv-localget |
64
71
  | 操作远程 KV 后端的 todo 清单(需登录) | nx-kv |
65
72
  | 给**正在等待**的 agent 补一条任务 | 本 skill(add 到它的目录) |
73
+ | 不限定接收方、让任何空闲 agent 自取 | 本 skill(`add --global` 推全局任务池) |
66
74
 
67
75
  ## 与其它 skill 的分工
68
76
 
@@ -64,11 +64,18 @@
64
64
  ## 三、提交命令与返回
65
65
 
66
66
  ```bash
67
+ # 指名提交:投进目标目录的队列(常态;务必加 --json)
67
68
  nx-kv localtask add "<任务文本>" --cwd "<目标工作目录>" --json
69
+
70
+ # 全局投递:不限定接收方,谁闲谁领(--cwd 被忽略)
71
+ nx-kv localtask add --global "<任务文本>" --json
68
72
  ```
69
73
 
70
74
  - `--cwd`:接收方 agent 的**工作目录**(主题按目录名识别,Windows 折叠大小写;
71
75
  同名目录共用队列)。
76
+ - `--global`:推到**全局任务池**——任何目录的 agent 在本地队列空时自动领取
77
+ (返回 `key` 为 `__GLOBAL__`)。适合不依赖顺序、不挑执行者的通用任务;有依赖
78
+ 顺序或只能某人做的任务必须 `--cwd` 指名提交。
72
79
  - `--summary "<现状>"`:可选,顺带刷新该主题在面板/弹窗顶部展示的现状。
73
80
  - 文本含空格必须加引号;跨执行边界传长文本时优先保持**单行**(多行参数可能
74
81
  被平台切掉后半截),用 ①②③ 编号替代换行。
@@ -78,7 +85,7 @@ nx-kv localtask add "<任务文本>" --cwd "<目标工作目录>" --json
78
85
  | 字段 | 含义 |
79
86
  | --- | --- |
80
87
  | `task.id` | 任务编号(该队列内自增),向用户报告用它 |
81
- | `queued:true` | 已入队(跨进程提交的常态);那边正在 wait 的 agent 约 0.5 秒内取走 |
88
+ | `queued:true` | 已入队(跨进程提交的常态);那边正在 wait 的 agent 约 0.5 秒内取走(全局投递则由任何空闲 agent 领取) |
82
89
  | `delivered:true` | 提交进程内正好有 wait 在等、当场送达(罕见) |
83
90
 
84
91
  文本非法(空、超 20000 字)或目录非法 → 报错 exit 1,按错误处理并修正后重试。
@@ -100,7 +107,7 @@ nx-kv localtask add "<任务文本>" --cwd "<目标工作目录>" --json
100
107
 
101
108
  | 错误做法 | 后果 | 正确做法 |
102
109
  | --- | --- | --- |
103
- | `--cwd` 随手填或省略 | 任务排进错误/没人看的队列;省略时提交进**你自己的**目录 | 填接收方 agent 实际工作的目录;不确定先问 |
110
+ | `--cwd` 随手填或省略(且没用 --global) | 任务排进错误/没人看的队列;省略时提交进**你自己的**目录 | 填接收方 agent 实际工作的目录;不确定先问 |
104
111
  | 任务文本引用本对话(「如上所述」) | 接收方无从执行 | 自包含:目标/背景/注意写全,路径写全 |
105
112
  | 把没和用户对齐的猜测提交出去 | 那边的 agent 按错误需求白干一轮 | 先确认再提交;提交前给用户过目 |
106
113
  | 多行长文本直接塞 argv | 跨执行边界可能被切掉后半截(静默丢内容) | 单行 + ①②③ 编号;确要换行用引号包裹并提交后 `get` 核对 |
@@ -43,6 +43,27 @@ agent 接着干;做完再 wait——如此循环。别人通过 kv-submit /
43
43
  待领取任务**。队列里有任务时 wait 立即返回(不开窗、不阻塞);队列为空才开窗
44
44
  阻塞。不同目录 → 不同主题 → 互不串。
45
45
 
46
+ **本地空了一定去全局池领(内建兜底,没有开关)。** 即便显式传了 `--cwd`,本地
47
+ 队列取不到任务时,wait 也会自动去「全局任务池」(跨目录共享队列)领一条;全局
48
+ 也没有才阻塞。不要假设「传了 cwd 就只看本地」——习惯性传 cwd 不影响全局任务被
49
+ 领取;同样,别往全局池放"只能某人做"的任务,池子对一切空闲 agent 开放。
50
+
51
+ ## 全局任务池(工作窃取)
52
+
53
+ 除按目录划分的本地队列外,还有一个**跨目录共享**的全局主题:任何目录的 agent
54
+ 在本地队列空时自动领取池里的任务——类似 Go 调度器的 global runq:本地空就去
55
+ 全局拿,全局也空才 park 阻塞。
56
+
57
+ - **发布**:`nx-kv localtask add --global "<任务文本>" --json`——不限定接收方、
58
+ 谁闲谁领的任务走这里(`--global` 时 `--cwd` 被忽略)。
59
+ - **领取**:无需任何 flag,wait 内建兜底;返回用 `source: "local" | "global"`
60
+ 区分任务来自本目录队列还是全局池。agent 不必区分,照常执行 `task.text`。
61
+ - **顺序**:本地优先、全局兜底——本目录有排队任务时不会去抢全局的。
62
+ - **公平**:多个 agent 同时阻塞时,全局任务按阻塞先后(先到先得)投递。
63
+ - **查看/管理**:`nx-kv localtask list` 中全局主题永远置顶(名称「全局任务」,
64
+ key 固定 `__GLOBAL__`);默认受保护,`topic remove --key __GLOBAL__` 需 `--force`
65
+ 才能清空。
66
+
46
67
  ## 核心命令
47
68
 
48
69
  ```bash
@@ -54,7 +75,8 @@ nx-kv localtask wait --summary "<当前现状总结>" --json
54
75
 
55
76
  | 返回 | 含义 | agent 怎么做 |
56
77
  | --- | --- | --- |
57
- | `{status:"ok", queued:true, task}` | 队列里已有**排队任务**(含 kv-submit 委托的) | 直接执行 task |
78
+ | `{status:"ok", queued:true, source:"local", task}` | 本目录队列已有**排队任务**(含 kv-submit 委托的) | 直接执行 task |
79
+ | `{status:"ok", queued:true, source:"global", task}` | 本地空,从**全局任务池**领到任务 | 直接执行 task |
58
80
  | `{status:"ok", delivered:true, task}` | 用户在弹窗/面板**实时回填** | 执行 task |
59
81
  | `{status:"timeout", cwd, summary, timeoutMs}` | 阻塞时限内未收到回填 | 收尾、结束本轮 |
60
82
 
@@ -79,11 +79,14 @@ wait 必带一句当前现状总结(≤2000 字,连续空白会折叠为单
79
79
  "createdAt": "2026-10-11 10:00:00", "consumedAt": "2026-10-11 10:00:04",
80
80
  "roundSummary": "已完成登录,等待选择工作空间" },
81
81
  "delivered": true,
82
- "queued": false
82
+ "queued": false,
83
+ "source": "local"
83
84
  }
84
85
  ```
85
86
 
86
87
  - `delivered:true` = 实时回填;`queued:true` = 取到队列里排队的任务(二者互斥)。
88
+ - `source` = 任务来自哪个队列:`"local"`(本目录队列,含实时回填)或 `"global"`
89
+ (全局任务池);agent 不必区分,照常执行 `task.text`。
87
90
  - **任务内容永远读 `task.text`**,多行换行原样保留。
88
91
 
89
92
  超时(exit 0,业务结果而非错误):
@@ -116,7 +119,26 @@ wait 必带一句当前现状总结(≤2000 字,连续空白会折叠为单
116
119
  - agent 在哪个目录跑 wait(或 `--cwd` 指哪个目录),就取哪个目录队列的任务,
117
120
  互不串。
118
121
 
119
- ## 七、删除主题与批量删除
122
+ ## 七、全局任务池(跨目录共享,工作窃取)
123
+
124
+ 除本地队列外还有一个**跨所有目录共享**的「全局任务池」:任何目录的 agent 在
125
+ 本地队列空时自动领取池里的任务,全局也空才阻塞——类似 Go 调度器的 global runq
126
+ (本地空 → 全局拿 → 都空才 park)。
127
+
128
+ - **领取是无条件兜底**:显式传 `--cwd` 也不例外——本地取不到就一定去全局扫一眼。
129
+ 不要假设「传了 cwd 就只看本地」。
130
+ - **本地优先**:本目录有排队任务时 wait 先取本地的,不会去抢全局的。
131
+ - **发布**:`nx-kv localtask add --global "<任务文本>" --json`(`--global` 时
132
+ `--cwd` 被忽略)。适合放不限定接收方、谁闲谁领的通用任务;「只能某人做」的
133
+ 任务不要放池子,用 `--cwd` 指名提交。
134
+ - **公平**:多个 agent 同时阻塞时,全局任务按阻塞先后(先到先得)投递。
135
+ - **返回标记**:`source: "local" | "global"` 标明任务来自本目录队列还是全局池;
136
+ agent 不必区分,照常执行 `task.text`。
137
+ - **管理**:`nx-kv localtask list` 中全局主题永远置顶(名称「全局任务」,key 固定
138
+ `__GLOBAL__`);受保护——`topic remove --key __GLOBAL__` 默认拒绝,`--force`
139
+ 才能清空;`topic remove-many --all` 默认跳过它。
140
+
141
+ ## 八、删除主题与批量删除
120
142
 
121
143
  主题(工作目录)可整体删除,不必逐条删任务:
122
144
 
@@ -132,7 +154,7 @@ wait 必带一句当前现状总结(≤2000 字,连续空白会折叠为单
132
154
  - **返回**:单个 `{status:"removed",topic}`;批量 `{count,removed,skipped}`,
133
155
  skipped 列出被跳过的「等待中 / 不存在」主题。
134
156
 
135
- ## 八、常见错误与避坑
157
+ ## 九、常见错误与避坑
136
158
 
137
159
  | 错误做法 | 后果 | 正确做法 |
138
160
  | --- | --- | --- |
@@ -147,7 +169,7 @@ wait 必带一句当前现状总结(≤2000 字,连续空白会折叠为单
147
169
  | 同时开多个 wait(同一目录主题) | 只有一个收到回填,另一个等到超时 | 同一目录同一时刻只调一个 wait |
148
170
  | 没用 --json | 输出人读文本,宿主难以可靠解析 | agent 调用一律加 `--json` |
149
171
 
150
- ## 九、与常驻面板配合 / 排障
172
+ ## 十、与常驻面板配合 / 排障
151
173
 
152
174
  - 已 `nx-kv serve` 打开面板时,直接在「本地任务」页点选主题回填;正在阻塞的
153
175
  wait 通过轮询(约每 0.5 秒)取走,不依赖弹窗本身。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx-kv",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "nx-kv — KV 清单管理中枢(远程)+ 本机本地任务队列(localtask):serve 驱动 CLI 与 Web 面板,同源同构,附带可安装 skill 供 agent 提交 / 领取 / 阻塞接收任务",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,8 @@ import * as service from './service.js';
15
15
 
16
16
  const CWD = { type: 'string', hint: '工作目录(作为主题;默认当前目录)' };
17
17
  const SUMMARY = { type: 'string', required: true, hint: '当前现状(agent 当前状态,展示给用户)' };
18
+ // 任务级寻址的 key flag:全局主题没有 cwd,只能按 key 定位(__GLOBAL__)
19
+ const KEY = { type: 'string', hint: '主题 key(与 --cwd 二选一;全局任务池固定 __GLOBAL__)' };
18
20
 
19
21
  const STATUS_TAG = { pending: '待领取', consumed: '已领取' };
20
22
 
@@ -26,7 +28,7 @@ function renderOverview(d) {
26
28
  return lines.join('\n');
27
29
  }
28
30
  for (const t of d.topics) {
29
- const tag = t.waiting ? '[等待中]' : '[已结束]';
31
+ const tag = t.isGlobal ? '[全局池]' : t.waiting ? '[等待中]' : '[已结束]';
30
32
  const head = t.cwd ? `${t.name} ${t.cwd}` : t.name;
31
33
  lines.push(` ${tag} ${head} · 待领取 ${t.pending}/${t.total}`);
32
34
  if (t.summary) lines.push(` 现状: ${t.summary}`);
@@ -44,40 +46,43 @@ const renderTask = (t) =>
44
46
  .join('\n');
45
47
 
46
48
  function renderAdd(d) {
49
+ const where = d.isGlobal ? '全局任务(共享池)' : `主题「${d.name}」`;
47
50
  const head = `#${d.task.id} [${d.task.status}] ${d.task.text}`;
48
51
  return d.delivered
49
- ? `已实时回填给等待中的 agent(主题 ${d.name}): ${head}`
50
- : `已排入主题「${d.name}」队列(agent 下一次 wait 取走): ${head}`;
52
+ ? `已实时投递给等待中的 agent(${where}): ${head}`
53
+ : `已排入${where}队列(agent 下一次 wait 取走): ${head}`;
51
54
  }
52
55
 
53
- // wait 的人读输出:成功给任务,超时明确提示「本轮结束」
56
+ // wait 的人读输出:成功给任务(注明来自本地队列还是全局共享池),超时明确提示「本轮结束」
54
57
  function renderWait(r) {
55
58
  if (r.status === 'timeout') {
56
59
  const tail = r.cancelled ? '(用户在面板点了「关闭」)' : '';
57
60
  return `等待超时(${Math.round(r.timeoutMs / 1000)} 秒未回填)${tail}:主题「${r.cwd}」现状「${r.summary}」结束本轮。`;
58
61
  }
59
- const origin = r.queued ? '排队任务' : '实时回填';
62
+ const origin =
63
+ (r.source === 'global' ? '全局任务池' : '本目录队列') + (r.queued ? '排队任务' : ' · 实时回填');
60
64
  return (
61
65
  `已取得下一条任务(${origin} · 主题「${r.cwd}」现状「${r.summary}」): #${r.task.id}\n` +
62
66
  ` ${r.task.text.replace(/\n/g, '\n ')}`
63
67
  );
64
68
  }
65
69
 
66
- // cancel 的人读输出:等待中→已关闭(主题与任务保留);非等待中→无操作
70
+ // cancel 的人读输出:等待中→已关闭(主题与任务保留);非等待中→无操作;全局→不可关闭
67
71
  function renderCancel(d) {
68
72
  if (d.cancelled) return `已关闭主题「${d.name}」的等待:阻塞中的 wait 立即收尾,主题与任务保留。`;
73
+ if (d.reason === 'global_not_waitable') return '全局主题是共享任务池(不是等待源),无从关闭。';
69
74
  if (d.reason === 'not_waiting') return `主题「${d.name || d.key}」不在等待中,无需关闭。`;
70
75
  return '已关闭。';
71
76
  }
72
77
 
73
- // 批量删除的人读输出:删除清单 + 跳过清单(等待中/不存在)
78
+ // 批量删除的人读输出:删除清单 + 跳过清单(等待中/受保护/不存在)
74
79
  function renderRemoveMany(d) {
75
80
  const lines = [d.all ? `清空:删除 ${d.count} 个主题` : `已删除 ${d.count} 个主题`];
76
81
  for (const r of d.removed) lines.push(` - ${r.name}${r.cwd ? ` ${r.cwd}` : ''}`);
77
82
  if (d.skipped.length) {
78
83
  lines.push(`跳过 ${d.skipped.length} 个:`);
79
84
  for (const s of d.skipped) {
80
- const why = s.reason === 'waiting' ? '等待中' : '不存在';
85
+ const why = s.reason === 'waiting' ? '等待中' : s.reason === 'protected' ? '全局共享池' : '不存在';
81
86
  lines.push(` - [${why}] ${s.name || s.key}`);
82
87
  }
83
88
  }
@@ -108,41 +113,51 @@ export default {
108
113
  id: 'localtask.get',
109
114
  cli: ['localtask', 'get'],
110
115
  http: ['GET', '/api/localtasks/item'],
111
- summary: '查看单条任务(按 --id,定位到 --cwd 主题)',
112
- flags: { id: { type: 'number', required: true, hint: '任务编号' }, cwd: CWD },
113
- run: (ctx) => service.getTask({ id: ctx.id, cwd: ctx.cwd }),
116
+ summary: '查看单条任务(按 --id,定位到 --cwd 主题或 --key)',
117
+ flags: { id: { type: 'number', required: true, hint: '任务编号' }, cwd: CWD, key: KEY },
118
+ run: (ctx) => service.getTask({ id: ctx.id, cwd: ctx.cwd, key: ctx.key }),
114
119
  render: renderTask,
115
120
  },
116
121
  {
117
122
  id: 'localtask.add',
118
123
  cli: ['localtask', 'add'],
119
124
  http: ['POST', '/api/localtasks'],
120
- summary: '向某主题(工作目录)的队列回填一条任务(有 wait 在等则即时投递,否则排队)',
125
+ summary:
126
+ '向某主题(工作目录)的队列回填一条任务(有 wait 在等则即时投递,否则排队);--global 推到全局共享池',
121
127
  args: ['text'],
122
- flags: { cwd: CWD, summary: { type: 'string', hint: '顺带刷新该主题的当前现状(可选)' } },
123
- run: (ctx) => service.addTask({ text: ctx.text, cwd: ctx.cwd, summary: ctx.summary }),
128
+ flags: {
129
+ cwd: CWD,
130
+ summary: { type: 'string', hint: '顺带刷新该主题的当前现状(可选)' },
131
+ global: {
132
+ type: 'boolean',
133
+ hint: '推到全局主题(共享池):任何目录的 wait 在本地空时自动领取;--cwd 被忽略',
134
+ },
135
+ },
136
+ run: (ctx) =>
137
+ service.addTask({ text: ctx.text, cwd: ctx.cwd, summary: ctx.summary, global: ctx.global }),
124
138
  render: renderAdd,
125
139
  },
126
140
  {
127
141
  id: 'localtask.update',
128
142
  cli: ['localtask', 'update'],
129
143
  http: ['PATCH', '/api/localtasks/item'],
130
- summary: '编辑任务内容(按 --id,定位到 --cwd 主题)',
144
+ summary: '编辑任务内容(按 --id,定位到 --cwd 主题或 --key)',
131
145
  flags: {
132
146
  id: { type: 'number', required: true, hint: '任务编号' },
133
147
  text: { type: 'string', required: true, hint: '改成这个内容' },
134
148
  cwd: CWD,
149
+ key: KEY,
135
150
  },
136
- run: (ctx) => service.updateTask({ id: ctx.id, text: ctx.text, cwd: ctx.cwd }),
151
+ run: (ctx) => service.updateTask({ id: ctx.id, text: ctx.text, cwd: ctx.cwd, key: ctx.key }),
137
152
  render: (d) => `已更新主题「${d.name}」#${d.task.id}: ${d.task.text}`,
138
153
  },
139
154
  {
140
155
  id: 'localtask.remove',
141
156
  cli: ['localtask', 'remove'],
142
157
  http: ['DELETE', '/api/localtasks/item'],
143
- summary: '删除一条任务(按 --id,定位到 --cwd 主题)',
144
- flags: { id: { type: 'number', required: true, hint: '任务编号' }, cwd: CWD },
145
- run: (ctx) => service.removeTask({ id: ctx.id, cwd: ctx.cwd }),
158
+ summary: '删除一条任务(按 --id,定位到 --cwd 主题或 --key)',
159
+ flags: { id: { type: 'number', required: true, hint: '任务编号' }, cwd: CWD, key: KEY },
160
+ run: (ctx) => service.removeTask({ id: ctx.id, cwd: ctx.cwd, key: ctx.key }),
146
161
  render: (d) => `已删除主题「${d.name}」#${d.removed.id}: ${d.removed.text}`,
147
162
  },
148
163
 
@@ -228,9 +243,16 @@ export default {
228
243
  beforeId: { type: 'number', hint: '插到该编号之前(与 afterId 二选一)' },
229
244
  afterId: { type: 'number', hint: '插到该编号之后(与 beforeId 二选一)' },
230
245
  cwd: CWD,
246
+ key: KEY,
231
247
  },
232
248
  run: (ctx) =>
233
- service.reorderTasks({ id: ctx.id, beforeId: ctx.beforeId, afterId: ctx.afterId, cwd: ctx.cwd }),
249
+ service.reorderTasks({
250
+ id: ctx.id,
251
+ beforeId: ctx.beforeId,
252
+ afterId: ctx.afterId,
253
+ cwd: ctx.cwd,
254
+ key: ctx.key,
255
+ }),
234
256
  render: (d) => {
235
257
  const order = d.topic.tasks.filter((x) => x.status !== 'consumed').map((x) => `#${x.id}`);
236
258
  return `主题「${d.name}」待领取顺序:${order.join(' -> ') || '(无待领取)'}`;
@@ -242,7 +264,8 @@ export default {
242
264
  id: 'localtask.wait',
243
265
  cli: ['localtask', 'wait'],
244
266
  http: null,
245
- summary: '针对某主题(按 cwd 目录名识别)阻塞等待用户回填(默认 3 分钟,最大 10 分钟)',
267
+ summary:
268
+ '针对某主题(按 cwd 目录名识别)阻塞等待用户回填(默认 3 分钟,最大 10 分钟);本地空时自动领取全局共享池的任务',
246
269
  flags: {
247
270
  cwd: CWD,
248
271
  summary: SUMMARY,