nx-kv 0.8.4 → 0.9.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +47 -2
  3. package/assets/kv-submit/SKILL.md +12 -4
  4. package/assets/kv-submit/references/submit-workflow.md +9 -2
  5. package/assets/kv-waittask/SKILL.md +23 -1
  6. package/assets/kv-waittask/references/kv-waittask-workflow.md +26 -4
  7. package/assets/nx-kv/SKILL.md +48 -0
  8. package/assets/nx-kv/references/todo-commands.md +52 -0
  9. package/package.json +1 -1
  10. package/src/core/attachstore.js +269 -0
  11. package/src/core/config.js +22 -0
  12. package/src/core/fileapi.js +243 -0
  13. package/src/core/kvapi.js +3 -1
  14. package/src/core/paths.js +9 -0
  15. package/src/modules/file/index.js +100 -0
  16. package/src/modules/file/service.js +214 -0
  17. package/src/modules/localtask/index.js +44 -21
  18. package/src/modules/localtask/service.js +238 -46
  19. package/src/modules/localtask/view.jsx +72 -16
  20. package/src/modules/setting/index.js +33 -0
  21. package/src/modules/setting/service.js +48 -0
  22. package/src/modules/todo/attach.js +96 -0
  23. package/src/modules/todo/editor.jsx +229 -0
  24. package/src/modules/todo/index.js +72 -0
  25. package/src/modules/todo/markdown.jsx +53 -0
  26. package/src/modules/todo/service.js +171 -10
  27. package/src/modules/todo/upload.js +107 -0
  28. package/src/modules/todo/view.jsx +83 -10
  29. package/src/runtime/api.js +6 -1
  30. package/src/runtime/registry.js +3 -1
  31. package/src/web/frontend/style.css +133 -0
  32. package/src/web/public/assets/{CliHints-D70_R4vQ.js → CliHints-CGOWc046.js} +1 -1
  33. package/src/web/public/assets/{index-CmCYxz0e.css → index-BDsJPdhM.css} +1 -1
  34. package/src/web/public/assets/{index-2OK9SG2y.js → index-BVbLNACS.js} +2 -2
  35. package/src/web/public/assets/{view-C5ictDJi.js → view-BBdEL07x.js} +1 -1
  36. package/src/web/public/assets/view-BlGIceud.js +3 -0
  37. package/src/web/public/assets/view-CwFWDigR.js +1 -0
  38. package/src/web/public/assets/{view-gzBv_kjb.js → view-DUy79STG.js} +1 -1
  39. package/src/web/public/index.html +2 -2
  40. package/src/modules/localtask/popup.js +0 -285
  41. package/src/web/public/assets/view-DxwcTgBZ.js +0 -1
  42. package/src/web/public/assets/view-l_lnGEBC.js +0 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,112 @@
2
2
 
3
3
  本文件记录对外可见的变更。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。
4
4
 
5
+ ## [0.9.0] - 2026-10-16
6
+
7
+ ### Added
8
+
9
+ - **任务附件:正文里用标准 markdown 嵌图片与文档,并可同步到本地给 agent 直接读。**
10
+
11
+ - **嵌入语法就是标准 markdown**,不发明新方言(解析实现只有一处:
12
+ `src/modules/todo/attach.js`,Node 与浏览器共用):
13
+ `![说明](fileId)` 图片(面板内联预览)、`[文件名](fileId)` 其他文件(docx/pdf…)。
14
+ `fileId` 是后端图床的不透明 id(32 位 hex,原文件名带后缀时结尾接 `.<ext>`)。
15
+ **判定刻意收窄**:URL(含 `://`)、站点路径(`/…`)、相对路径(`./…`)一律按
16
+ 普通 markdown 原样保留 —— 否则任务里写条普通链接就会被当成附件,
17
+ 然后预览 404、同步白跑一趟网络。
18
+ - **新模块 `file`**:`file upload / list / get / remove / content / sync`。
19
+ 上传走后端 multipart(`POST /api/v1/files`,字段名 `file`);`file content` 在
20
+ HTTP 侧把后端出图内容原样透传(面板 `<img src="/api/file/content/<id>">` 用),
21
+ CLI 侧只打印地址与本地路径。
22
+ - **`file sync`:把附件同步到本机** —— 存在的唯一理由是让 agent **读本地文件而不是读网**。
23
+ 默认落到 `~/.nx-kv/attachments/<groupId|default>/`(`NX_KV_ATTACHMENTS` 可改根目录),
24
+ 同目录 `manifest.json` 记「fileId → 本地文件名 + 大小」,所以二次同步跳过未变更的。
25
+ 本地文件名优先取正文里的说明文字(`![设计稿.png](id)` → `设计稿.png`),否则退回
26
+ fileId 并按 Content-Type 补扩展名(没有扩展名,Windows 上双击打不开)。
27
+ `--ids` 只同步指定附件;`--prune` 清理本地已不在后端的(**仅在全量同步时生效**——
28
+ 带 `--ids` 再 prune 会把没列出来的附件一起删掉);`--dryRun` 只列不落盘。
29
+ - **`todo attach` / `todo attachments`**:把附件**追加**到任务正文;列出某条任务
30
+ 引用了哪些附件、本地是否已有。`todo attach` 会拒绝不像 fileId 的值(比如 URL)。
31
+ ⚠️ 追加会改写正文,而定位按正文内容(`--ref`)—— 连续操作要用**新的** text。
32
+ - **新模块 `setting` + 面板开关:「读任务时自动下载附件」。**
33
+ `nx-kv setting get` / `nx-kv setting set --autoSyncAttachments[=false]`
34
+ (面板在「清单」页工具栏)。开启后 `todo list` / `todo get` 会同步正文引用的附件,
35
+ 给每个任务附加 `attachments[]`(`id / kind / name / localPath / synced`,
36
+ **localPath 是本地绝对路径**),并用 `attachmentsSync` 汇报这一轮
37
+ `downloaded / skipped / failures`。**下载失败不阻断读取** —— 附件拿不到与
38
+ 清单读不到是两件事,失败如实进 `failures`,本地已有旧版本时继续用它。
39
+ 默认**关**:开启意味着每次 list/get 都触网,对「只想看一眼清单」是纯负担。
40
+ - **`attachments` 标注与开关无关**:关掉开关后不再同步,但「这条任务挂了附件」
41
+ 仍会出现在 `attachments[]` 里(`localPath` 反映本地已有则给路径)。
42
+ - 前端:`todo` 的编辑从单行 dialog 换成完整编辑器弹窗(`editor.jsx`)——
43
+ **粘贴(Ctrl+V)/ 拖入 / 选择文件** 三条路都能上传,自动插到光标处、实时预览;
44
+ 列表里图片以缩略图内联、其他文件显示为可点附件(`markdown.jsx`)。
45
+ - **Ctrl+V 直接粘截图**是主路径:截图工具不给文件名,`upload.js` 会替它起
46
+ `粘贴图片-<时间戳>.png`(这个名字会一路用下去:正文说明文字 + 落盘文件名,
47
+ 退化成 `image.png` 就再也认不出是哪张)。一次粘多张带序号,不重名。
48
+ - 拖拽挂在 **window** 上而不是编辑区上:往弹窗里丢文件、落点差一点也该接住,
49
+ 而不是让浏览器把文件打开;只在拖**文件**时介入(拖选中的文字不弹提示)。
50
+ - 批量上传**先收齐再一次性插入** —— 边传边插会跟光标位置打架
51
+ (插入后 DOM 里光标还没更新,第二个附件会算错落点、变成倒序)。
52
+ - 纯文本粘贴不拦截,交给浏览器默认行为插到光标处。
53
+ - 快捷键 <kbd>Ctrl</kbd>+<kbd>Enter</kbd> 保存;字符计数与插入语法提示常驻。
54
+ - 「快速新增」那一栏也能 Ctrl+V 贴图:贴张截图再回车,任务与附件一次成型。
55
+ - 「附件自动下载」做成胶囊开关(面板「清单」页),悬停显示附件会落到哪个目录。
56
+ - `core/fileapi.js`(后端文件客户端,与 kvapi 共用同一套信封判定)、
57
+ `core/attachstore.js`(本地镜像与台账)、`core/config.resolveGroupId`
58
+ (模块间共享的 group 回落规则 —— todo 读 KV 与 file 选分组/本地目录必须同一条,
59
+ 否则会出现「任务读 A 组、附件同步 B 组」这种不报错的错位)。
60
+ - 本机服务的请求体上限 4MB → 24MB:面板上传走 base64 JSON(膨胀 ~33%),
61
+ 10MB 的图会变成 ~13MB 的请求体。
62
+
63
+ ### Changed
64
+
65
+ - `core/kvapi.js` 的 `call` 改为导出,供 `core/fileapi.js` 复用(两处各写一遍
66
+ 信封判定迟早分叉)。
67
+ - `todo list` / `todo get` 的每个任务可能多出 `attachments[]`;开启自动下载时
68
+ 顶层多出 `attachmentsSync`。未开启时**没有** `attachmentsSync`,其余字段与旧版一致。
69
+
70
+ ### Tests
71
+
72
+ - `tests/fake-backend.mjs` 增加文件端点,并**手写 multipart 解析** ——
73
+ 用 JSON 假装上传就测不出「boundary 丢了」「字段名写错」。
74
+ 假后端刻意**不返回 originalName**,与真实后端一致:本地文件名只能自己解决。
75
+ - `tests/smoke.mjs` 新增 25 项:上传(图片/docx)、list/get/remove、content 直出字节、
76
+ 嵌入追加、`file sync` 落盘与二次跳过、开关打开后读任务自动下载并回传全路径、
77
+ 关掉后不再同步但标注仍在、非法 fileId 被拒。
78
+ - `tests/unit/attach.test.mjs` 新增 9 项:嵌入解析的边界(普通链接不许被误判、
79
+ 去重保序、说明文字取首次出现、往返一致)。
80
+
81
+ ## [0.8.5] - 2026-10-13
82
+
83
+ ### Added
84
+
85
+ - **本地任务新增「全局任务池」(跨目录共享队列 + 工作窃取)**,参考 Go 调度器的
86
+ local runq + global runq 两层模型:
87
+ - `wait` 的领取顺序变为:本目录队列 → **全局任务池** → 阻塞。本地取不到时自动
88
+ 去全局领,全局也没有才阻塞;**显式传 `--cwd` 不会关闭全局兜底**(防止调用方
89
+ 习惯性传 cwd 导致池子无人领取)。返回新增 `source: "local" | "global"` 标明
90
+ 任务来源,旧字段全部保留。
91
+ - 发布进池子:`localtask add --global "<任务文本>"`(CLI/HTTP 同名 flag;
92
+ `--global` 时 `--cwd` 被忽略)。不限定接收方、谁闲谁领的任务走这里。
93
+ - 多个 agent 同时阻塞时,全局任务按阻塞先后(FIFO)实时投递给最早等待的;
94
+ 跨进程由既有 0.5s 轮询兜底,同样本地优先、全局其次。
95
+ - 全局主题在 `localtask list` 与面板「本地任务」页永远置顶(名称「全局任务」,
96
+ key 固定 `__GLOBAL__`,卡片带「全局」徽章);面板回填表单新增「全局」勾选,
97
+ 选中全局主题卡片时只能推全局。
98
+ - 全局主题默认受保护:`topic remove --key __GLOBAL__` 需 `--force`、
99
+ `topic remove-many` 将其计入 `skipped`(reason `protected`)、`prune` 永不清理;
100
+ 它不是等待源——`cancel` 返回 `global_not_waitable`、永不计入等待数。
101
+ - 任务级 `get/update/remove/reorder` 新增 `--key` 寻址(与 `--cwd` 二选一),
102
+ 全局主题没有 cwd、只能按 key 管理。
103
+
104
+ ### Fixed
105
+
106
+ - **落盘并发安全**:`saveStore` 进程内串行化(链式队列)——此前同进程两个写操作
107
+ 重叠时共用同一个 `.tmp` 中转文件,先完成的 rename 会把 tmp 消耗掉、后一个
108
+ rename 直接 ENOENT 崩溃(全局唤醒与等待复位并发时必现);rename 另加 Windows
109
+ EPERM/EACCES 小退避重试(目标文件被并发读者/杀软短暂锁住的瞬态失败)。
110
+
5
111
  ## [0.8.4] - 2026-10-12
6
112
 
7
113
  ### Added
package/README.md CHANGED
@@ -6,6 +6,10 @@ KV 清单(todo)管理中枢。一个 `npx` 命令起一个 Web 面板,同
6
6
  对接 GoFrame KV 后端(默认 `http://47.110.80.47:8988`),
7
7
  数据落在**四把 key** 上:`todo:open` / `todo:done` / `todo:freeze` / `todo:topics`。
8
8
 
9
+ 任务正文还能直接**嵌图片与文档**(标准 markdown:`![说明](fileId)` / `[文件名](fileId)`)——
10
+ 附件存在后端图床的同一用户空间里,既能同步到本地给 agent 直接读(`nx-kv file sync`),
11
+ 也能在读任务时自动下载并把本地绝对路径一并交出去。
12
+
9
13
  ## 快速开始
10
14
 
11
15
  ```bash
@@ -66,6 +70,47 @@ nx-kv group members 24 # 成员
66
70
  完整命令表由 action 声明自动生成:`nx-kv help`。
67
71
  命令与 HTTP 端点互相可查:`nx-kv routes`,反查用 `nx-kv routes --http "POST /api/todo"`。
68
72
 
73
+ ## 附件(图片 / 文档)
74
+
75
+ 任务正文直接用**标准 markdown** 引用附件,附件本体存在后端图床的同一用户空间里:
76
+
77
+ ```
78
+ 修一下首页 logo ![设计稿](3f2a1b…9c) # 图片:面板内联预览
79
+ 补充材料见 [需求说明.docx](b71e…04.docx) # 其他文件:点开即下载/预览
80
+ ```
81
+
82
+ 括号里是后端返回的 `fileId`(32 位 hex,原文件名带后缀时会接 `.<ext>`)。
83
+ URL 与相对路径不算附件,按普通 markdown 原样保留(不会误判)。
84
+
85
+ ```bash
86
+ nx-kv file upload --path "D:\设计\首页.png" # 上传到当前用户空间
87
+ nx-kv todo attach --ref "修一下首页 logo" --path "D:\设计\首页.png" # 上传并嵌进正文
88
+ nx-kv todo attachments --ref "修一下首页 logo" # 这条任务引用了哪些附件
89
+
90
+ nx-kv file sync # 把附件同步到 ~/.nx-kv/attachments/<工作空间>/
91
+ nx-kv file sync --ids a,b # 只同步指定附件
92
+ nx-kv file sync --prune # 顺带清掉本地已不在后端的(仅全量同步时生效)
93
+ ```
94
+
95
+ 面板里编辑任务时,**Ctrl+V 直接粘截图**、把文件拖进来、或点「选择文件」——
96
+ 都会自动上传并插到光标处,右侧实时预览;列表里图片显示为缩略图、其他文件是可点附件。
97
+ 「快速新增」那一栏也支持粘贴图片。
98
+
99
+ **给 agent 的关键开关**:开启「读任务时自动下载」后,`todo list` / `todo get`
100
+ 的每个任务会多出 `attachments[]`,里面是**本地绝对路径**,另有 `attachmentsSync`
101
+ 汇报下载/跳过/失败。读本地文件因此不再受网络与跨域影响:
102
+
103
+ ```bash
104
+ nx-kv setting set --autoSyncAttachments # 开
105
+ nx-kv setting set --autoSyncAttachments=false # 关(默认)
106
+ ```
107
+
108
+ 面板里对应「清单」页工具栏的「附件自动下载」勾选。本地文件名优先取正文里的说明文字
109
+ (`![设计稿.png](id)` → `设计稿.png`),否则退回 fileId 并补上扩展名;
110
+ 同目录 `manifest.json` 记「fileId → 文件名 + 大小」,所以二次同步会跳过未变更的。
111
+
112
+ 单独的命令参考见 `nx-kv help file` 与 skill 的 `todo-commands`。
113
+
69
114
  ## 本地任务(localtask,不需登录)
70
115
 
71
116
  本机任务队列,按**工作目录**划分(目录名即主题):提交(`add`)、领取(`wait`)、
@@ -156,8 +201,8 @@ nx-kv todo get --ref "旧记录" --topic fr # 或按主题收窄
156
201
  所以「Web 上能做的,CLI 都能做」是结构保证而非口头约定。
157
202
 
158
203
  ```
159
- src/core/ 零业务语义:kvapi(后端客户端) / config(本机配置) / errors / fstree
160
- src/modules/ 功能域:system / auth / group / localtask / todo / bundled
204
+ src/core/ 零业务语义:kvapi / fileapi(后端客户端) / attachstore(附件本地镜像) / config / errors / fstree
205
+ src/modules/ 功能域:system / setting / auth / group / localtask / todo / file / bundled
161
206
  src/runtime/ 装配层:registry / spec / cli / api / server
162
207
  src/web/frontend/ React 壳
163
208
  assets/nx-kv/ 内置 skill:KV 清单操作手册(远程)
@@ -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 秒)取走,不依赖弹窗本身。
@@ -17,6 +17,7 @@ KV 清单(todo)的操作入口。四把 key 上的任务管理,对接 GoFr
17
17
  | --- | --- |
18
18
  | **领任务 / 收单**:按主题拉待办 → 读上下文 → 与用户对齐意图 → 完成后回填 | [[agent-workflow]] |
19
19
  | **查命令**:要敲哪条、退出码、数据结构、避坑 | [[todo-commands]] |
20
+ | **任务带附件**:正文里的图片 / docx 怎么读、怎么落到本地 | 本页「附件」一节 + [[todo-commands]] |
20
21
 
21
22
  两份都是按需加载,不要一开始就全读进来。
22
23
 
@@ -55,6 +56,53 @@ nx-kv todo done --ref "<任务内容>" --result "改了什么 / 关键决策 /
55
56
  `--status open --topic` 是「只要待办」。收单用后者(省上下文),
56
57
  做统计/核对/回顾用前者。
57
58
 
59
+ ## 附件:正文里的图片与文档(读本地,不读网)
60
+
61
+ 任务**正文本身**就能带附件——用标准 markdown 写,不发明新语法:
62
+
63
+ ```
64
+ 修一下首页 logo ![设计稿](3f2a1b…9c) ← 图片,面板里内联预览
65
+ 补充材料见 [需求说明.docx](b71e…04.docx) ← 其他文件,点开即下载/预览
66
+ ```
67
+
68
+ `( )` 里是**后端图床的 fileId**(32 位 hex,原文件名带后缀时结尾会接 `.<ext>`)。
69
+ 除此之外的 markdown 链接(`http(s)://`、`./x.md`)一律按普通链接处理,不会被当成附件。
70
+
71
+ ```bash
72
+ nx-kv todo attachments --ref "<任务内容>" --json # 这条任务引用了哪些附件(含本地路径)
73
+ nx-kv todo attach --ref "<任务内容>" --path <本机文件> # 上传并追加到正文
74
+ nx-kv todo attach --ref "<任务内容>" --fileId <fileId> # 追加一个已上传的附件
75
+ ```
76
+
77
+ **给要读附件的 agent:先把附件落到本地,再读本地文件。**
78
+
79
+ ```bash
80
+ nx-kv file sync --json # 同步当前工作空间的附件 → ~/.nx-kv/attachments/<工作空间>/
81
+ ```
82
+
83
+ **更省事的做法是打开「读任务时自动下载」**(面板「清单」页的开关,或 CLI 一条命令):
84
+
85
+ ```bash
86
+ nx-kv setting set --autoSyncAttachments # 开
87
+ nx-kv setting set --autoSyncAttachments=false # 关(默认)
88
+ ```
89
+
90
+ 开启后,`todo list` / `todo get` 的每个任务会多出 `attachments[]`,
91
+ 里面直接给出**本地绝对路径**(`localPath`),另有 `attachmentsSync` 汇报这一轮
92
+ 下载了什么、跳过什么、失败什么:
93
+
94
+ ```json
95
+ { "id": "3f2a…9c", "kind": "image", "name": "设计稿.png",
96
+ "localPath": "C:\\Users\\me\\.nx-kv\\attachments\\24\\设计稿.png", "synced": true }
97
+ ```
98
+
99
+ 于是 agent 只要 `Read` 那个路径就行——不必拼出图 URL、不受网络和跨域影响。
100
+ 下载失败**不会**让清单读不出来:附件拿不到与任务读不到是两件事,
101
+ 失败项如实出现在 `attachmentsSync.failures` 里。
102
+
103
+ > ⚠️ 追加附件会**改写正文**,而定位是按正文内容(`--ref`)——
104
+ > 同一条任务连续操作时,要用**新的** text 去 `--ref`。
105
+
58
106
  ## ⚠️ 定位一律按内容,不按 id(最容易改错数据的地方)
59
107
 
60
108
  待办的 id **非常容易重复**,两条独立的原因:
@@ -92,6 +92,55 @@ nx-kv group current
92
92
  nx-kv group use <id|name|default> # 别名:group switch
93
93
  ```
94
94
 
95
+ ## 附件(图片 / 文档)
96
+
97
+ | 操作 | 命令 | 路由 |
98
+ | --- | --- | --- |
99
+ | 传附件 | `nx-kv file upload --path <本机文件>` | `POST /api/file` |
100
+ | 列附件 | `nx-kv file list [--limit N]` | `GET /api/file` |
101
+ | 查单个 | `nx-kv file get <fileId>` | `GET /api/file/item` |
102
+ | 出图/下载 | `nx-kv file content <fileId>` | `GET /api/file/content/:fileId`(HTTP 直出字节) |
103
+ | 删附件 | `nx-kv file remove <fileId>` | `DELETE /api/file/item` |
104
+ | **同步到本地** | `nx-kv file sync [--ids a,b] [--dir D] [--prune] [--dryRun]` | `POST /api/file/sync` |
105
+ | 给任务挂附件 | `nx-kv todo attach --ref <内容> --path <本机文件>` | `POST /api/todo/attach` |
106
+ | 看任务挂了什么 | `nx-kv todo attachments --ref <内容>` | `GET /api/todo/attachments` |
107
+ | 读/改设置 | `nx-kv setting get` / `nx-kv setting set --autoSyncAttachments` | `GET|POST /api/setting` |
108
+
109
+ **正文里的嵌入语法**(标准 markdown,不发明方言):
110
+
111
+ ```
112
+ ![说明](fileId) 图片:面板里内联预览
113
+ [文件名](fileId) 其他文件(docx/pdf/…):点开即下载/预览
114
+ ```
115
+
116
+ `fileId` 是后端图床的不透明 id:32 位 hex,原文件名带扩展名时结尾接 `.<ext>`
117
+ (如 `3f2a…9c.docx`)。URL(含 `://`)与相对路径(`./x.md`、`/files/x`)**不算附件**,
118
+ 一律按普通 markdown 原样保留。
119
+
120
+ **本地镜像**:默认落到 `~/.nx-kv/attachments/<groupId|default>/`,同目录的
121
+ `manifest.json` 记「fileId → 本地文件名 + 大小」,所以二次同步会跳过未变更的
122
+ (不会每次重下)。文件名优先用正文里的**说明文字**(`![设计稿.png](id)` → `设计稿.png`),
123
+ 没有说明文字时退回 fileId 并补上扩展名。
124
+
125
+ ```bash
126
+ # 读任务时自动下载:开启后 todo list / get 会带回本地绝对路径
127
+ nx-kv setting set --autoSyncAttachments
128
+ nx-kv todo get --ref "<任务内容>" --json
129
+ # → 任务对象多出 attachments[](id / kind / name / localPath / synced)
130
+ # + attachmentsSync(dir / downloaded / skipped / failures)
131
+
132
+ # 手动同步(不依赖开关)
133
+ nx-kv file sync --json # 全部附件
134
+ nx-kv file sync --ids <id1>,<id2> # 只同步指定附件(读任务时按引用同步走的就是这条)
135
+ nx-kv file sync --prune # 额外删掉本地已不在后端的文件(仅全量同步时生效)
136
+ ```
137
+
138
+ `file sync --prune` 在带 `--ids` 时**刻意不生效**:过滤出几个 id 再 prune,
139
+ 会把没列出来的附件一起删掉。
140
+
141
+ 删除附件的语义:`file remove` 只删后端记录(软删),本地已同步的副本会留着;
142
+ 要连本地一起清,用 `file sync --prune`。
143
+
95
144
  ## ⚠️ 定位按内容(--ref),不按 id
96
145
 
97
146
  ```jsonc
@@ -215,3 +264,6 @@ nx-kv todo list --status open --topic bug # bug 消费者
215
264
  | 未登录就调 todo | exit 1「未登录」 | 先 `auth login` |
216
265
  | 以为切换 group 会改服务端 | 只改本机配置 | 数据一直在服务端,切换只是换个 groupId |
217
266
  | 领取待办时用了全量 `list` | done 的历史 + 长 note 白占上下文 | 一律 `--status open --topic <t>` |
267
+ | `todo attach` 之后还用**旧的** `--ref` | 报「不存在」——正文已被追加改写 | 用返回的 `task.text`(新正文)继续操作 |
268
+ | 把附件当成「读了就行」的东西 | agent 拿 `<img>` URL 去请求,受网络/跨域影响 | 先 `file sync`(或开 `--autoSyncAttachments`),读 `localPath` |
269
+ | 拿图片地址手拼 / 存进正文 | 换后端地址就全失效 | 正文只写 `fileId`(`![说明](fileId)`),地址由客户端拼 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx-kv",
3
- "version": "0.8.4",
3
+ "version": "0.9.0",
4
4
  "description": "nx-kv — KV 清单管理中枢(远程)+ 本机本地任务队列(localtask):serve 驱动 CLI 与 Web 面板,同源同构,附带可安装 skill 供 agent 提交 / 领取 / 阻塞接收任务",
5
5
  "type": "module",
6
6
  "bin": {