annotatable-html 0.0.0-stage → 1.45.3
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/LICENSE +21 -0
- package/README.md +352 -2
- package/agent/annot_answer.js +7 -0
- package/agent/annot_edit.js +6 -0
- package/agent/annot_poll.js +19 -0
- package/agent/annot_read.js +5 -0
- package/agent/annot_reply.js +50 -0
- package/bin/cli.js +323 -0
- package/lib/claude.js +242 -0
- package/lib/codex-desktop.js +130 -0
- package/lib/codex-headless.js +129 -0
- package/lib/codex.js +216 -0
- package/lib/core.js +2206 -0
- package/lib/document.js +56 -0
- package/lib/duty-process.js +190 -0
- package/lib/duty-worker.js +5 -0
- package/lib/duty.js +334 -0
- package/lib/lang-en.js +462 -0
- package/lib/opencode.js +195 -0
- package/lib/poll-process.js +42 -0
- package/lib/serve.js +318 -0
- package/lib/vendor/marked.cjs +2811 -0
- package/lib/vendor/temml.cjs +14710 -0
- package/package.json +51 -6
- package/templates/SKILL.md +53 -0
- package/templates/annotatable-doc.html +5702 -0
- package/templates/annotatable.theme.css +28 -0
- package/templates/commands/html4agent.md +6 -0
- package/templates/opencode/annotatable-html.js +12 -0
- package/templates/references/compatibility.md +14 -0
- package/templates/references/hosts.md +47 -0
- package/templates/references/operations.md +37 -0
- package/templates/theme-studio.html +330 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 山居岁月
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,353 @@
|
|
|
1
|
-
#
|
|
1
|
+
# annotatable-html(HTML4agent)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**可批注 HTML:人类在浏览器划线批注,AI agent 读批注并回答。** 对话追问、AI 回答可再批注、每次改动有版本快照可回滚;人还能像 Word 一样直接编辑正文(✏️ 编辑模式 + 🧭 导航窗格),人手改动同样进版本账本、可回滚。单文件、零依赖、离线可用、不绑定任何一家 AI 服务。
|
|
4
|
+
|
|
5
|
+
**Annotatable HTML docs: humans highlight and comment in the browser, AI agents read the annotations and reply — with threads, re-annotatable answers, Word-style inline editing and version history. Single file, zero dependencies.**
|
|
6
|
+
|
|
7
|
+
产品哲学是 **annotation-first reading**:批注不是附加工具,而是阅读本身的一部分——即使是"隐藏所有按钮"的阅读模式,划线批注依然可用。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 快速上手
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx annotatable-html skill # 安装批注工作流 skill;OpenCode 同时安装会话插件
|
|
15
|
+
npx annotatable-html init # 可选:导出目录内 agent 工具、模板与主题
|
|
16
|
+
npx annotatable-html new 我的文档.html # 新建一份可批注文档
|
|
17
|
+
npx annotatable-html md 笔记.md # 一键 Markdown → 可批注 HTML(正文遵照原文,仅加批注层)
|
|
18
|
+
npx annotatable-html serve # 本地服务器:全自动保存(可选但推荐)
|
|
19
|
+
npx annotatable-html inject 任意.html # 给已有 HTML 注入批注层(原文件备份进同目录 .annotatable/)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
使用循环:
|
|
23
|
+
|
|
24
|
+
1. **人**:用 Edge/Chrome 打开 html,首次按提示绑定文件(serve 模式免绑定),选中文字 → ✍ 批注 / 🖍 高亮。批注自动保存进文件本体;
|
|
25
|
+
2. **AI**:人类说「读批注」,agent 跑 `annotatable read` → 通读对话线逐条回答 → 写 `replies.json` → `annotatable reply`(回答追加为对话线新轮次,rev+1 并存版本快照);
|
|
26
|
+
3. **人**:刷新页面,回答以绿色块出现在批注上;可「⤢ 展开大页」细读——**回答本身还能继续划线批注、页内追问**;
|
|
27
|
+
4. 任何时刻可打开「⏪ 版本历史」回滚到任意版本(回滚也是一次新提交,可再撤销)。
|
|
28
|
+
|
|
29
|
+
全部命令:`init` / `new` / `md` / `serve` / `read` / `reply` / `edit` / `poll` / `duty` / `inject` / `theme` / `template` / `studio` / `skill`(`annotatable help` 查看用法)。
|
|
30
|
+
|
|
31
|
+
**批注协议通过 skill 安装**:`annotatable skill` 安装 SKILL.md 和 references/ 下的按需说明,带版本自检。默认兼容 ZCode、Claude Code、Codex、OpenCode 和 agents 共享仓;slash 命令仅安装到 ZCode、Claude Code。OpenCode 同时安装会话上下文插件。
|
|
32
|
+
|
|
33
|
+
`init` 只导出目录内工具、模板和主题,不创建或更新工作区的 `AGENTS.md`。旧版的 `--agents-md` 参数已停用;已有指针、用户规则和全局指令文件均保持原样。工具不向这些文件写入值守状态或悬单清单;只检查用户指定文档的批注。
|
|
34
|
+
|
|
35
|
+
**教学与批注分开**:批注中的疑问统一回复到原对话。工具不按“教我、入门”等词切换教学模式,不决定课程、检查点或学习进度。独立教学工作流自行决定内容与格式。旧 answers-store 保留兼容读写;teach 教学生成命令已停用。
|
|
36
|
+
|
|
37
|
+
**凭页面地址接管**:同一台电脑上,直接把正在运行的 localhost 页面地址交给新会话。`annotatable read http://localhost:端口/文档.html` 会定位本地文档;之后挂当前会话的 poll。read、reply、edit、poll 均接受页面地址。服务须为新版 annotatable serve;旧服务需重启一次以提供定位接口。工具不扫描全局悬单。
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Codex 自动值守
|
|
42
|
+
|
|
43
|
+
在目标 Codex 会话里启动后台监听:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
annotatable poll 文档.html --codex
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
默认使用 `CODEX_THREAD_ID`;可用 `--codex-thread ID` 指定会话。桌面会话继承了 `CODEX_APP_TOOLS_PIPE_PATH` 时,工具优先使用桌面的 `read_thread` 和 `send_message_to_thread` 接口。通知会明确标为“批注工具自动通知”,发送到同一会话,保留模型和权限设置。后台进程须继承该环境变量。
|
|
50
|
+
|
|
51
|
+
无桌面连接时,使用 Node.js 22+ 的 app-server 通知入口。目标会话必须已加载到该服务;仅能读取历史记录时会提前报错,不启动监听。工具不会在其他服务恢复同一会话。可用 `--codex-transport desktop|app-server` 显式选择入口。桌面调用的来源轮次默认从会话历史读取;已知当前轮次时可传 `--codex-turn ID`,避免依赖历史服务。原生 CLI 不在标准位置时,加 `--codex-bin 路径`。
|
|
52
|
+
|
|
53
|
+
`CODEX_READY` 只表示预检通过,`CODEX_NOTIFIED` 只表示通知已被接口接受。确认自动值守须实测空闲会话被唤醒、HTML 中回复完成,并重新挂监听。失败会报 `CODEX_NOTIFY_ERROR`;发送超时的结果不确定,须先核对会话,不能直接重发。超时和纯高亮不会启动模型。普通 `poll` 保留原来的 Node.js 14+ 支持和退出码。
|
|
54
|
+
|
|
55
|
+
需要桌面退出后继续回复时,可显式启用自动续接(Windows、Node.js 22+):
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
annotatable duty 文档.html --codex
|
|
59
|
+
annotatable duty 文档.html --status
|
|
60
|
+
annotatable duty 文档.html --stop
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
后台程序管理保存服务和监听。桌面在线时沿用桌面唤醒;确认桌面主进程及运行时退出后,才由无头 Codex 恢复同一聊天。无头结束后再交还桌面,沿用模型和权限。使用输出的 `pageUrl` 打开文档;也支持已有 HTTP localhost 页面地址。不要同时为同页同聊天挂 poll。此功能不设置自启、不改 AGENTS.md;当前工作区 v1.45.2 尚未发布 npm,使用源码或本地安装包。独立页已完成真实退出、无头回复及重开后唤醒验收,细节见 [续接方案与验收](docs/Codex批注续接方案.md)。
|
|
64
|
+
|
|
65
|
+
## OpenCode 自动值守
|
|
66
|
+
|
|
67
|
+
先运行 `annotatable skill --opencode`,再新开 OpenCode。安装的上下文插件会自动提供当前会话信息。
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
annotatable poll 文档.html --opencode
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
在 OpenCode 中将这条命令挂到后台。命中后通过官方异步会话接口通知当前会话,agent 回复后重新挂监听。也可手动指定 `--opencode-url URL --opencode-session ID`。服务认证沿用 `OPENCODE_SERVER_PASSWORD` 和可选的 `OPENCODE_SERVER_USERNAME`。
|
|
74
|
+
|
|
75
|
+
`OPENCODE_NOTIFIED` 表示接口接受;回复完成以 HTML 中的 AI 轮次为准。连接失败报 `OPENCODE_NOTIFY_ERROR`,超时和纯高亮不通知。此模式保持零运行时依赖。
|
|
76
|
+
|
|
77
|
+
原服务退出后继续使用同一会话,可在原会话运行 `annotatable duty 文档.html --opencode`。它复用上述会话上下文插件和共用控制器;确认原服务退出后才启动原生服务恢复同一编号,保留模型、提供方和权限。重开原服务后自动交还;同页不再另挂 poll。
|
|
78
|
+
|
|
79
|
+
## Claude Code 自动续接
|
|
80
|
+
|
|
81
|
+
在原会话用 Bash 工具的 `run_in_background=true` 运行 `annotatable duty 文档.html --claude`。缺少会话信息时加 `--claude-session 原会话UUID`。首次注册会在该项目的 `.claude/settings.local.json` 安装一组原生启动与回合结束钩子。当前进程按通知自动续挂;重开后由原生钩子接管,用户无需重挂。
|
|
82
|
+
|
|
83
|
+
原 Claude 进程退出后,后台使用原生 `--resume` 恢复同一 UUID,保留模型、配置和权限。在原项目重开同一会话时,启动钩子自动接回通知,无需发送启动提示。钩子复用现有控制器与会话锁,其他会话静默退出,无头子进程跳过自己的钩子;最后一个注册停止后移除本工具的项目钩子,保留用户设置。无需额外插件或 npm 依赖,不配置全局钩子。当前使用源码入口,Windows / Node.js 22+,详见 [宿主说明](templates/references/hosts.md)。ZCode 暂不提供无头续接。
|
|
84
|
+
|
|
85
|
+
## 自定义外观与模板(v1.17.0)
|
|
86
|
+
|
|
87
|
+
两条路,都通向同一套主题机制(`annotatable.theme.css` 的 `:root` 变量 + 追加覆盖 CSS):
|
|
88
|
+
|
|
89
|
+
**① 主题工坊(GUI,推荐)**:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx annotatable-html studio # 生成工坊页并自动开浏览器(默认 127.0.0.1:8974)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
左侧按钮/滑杆调 **配色**(4 组预设 + 9 个取色器)、**字体**(正文/标题)、**根字号 / 行高 / 页面宽度**,右侧是真实模板的实时预览(划线、批注面板、🧭 导航窗格都能直接试——页宽与泊位窗格互不干扰)。调完点:
|
|
96
|
+
|
|
97
|
+
- **💾 保存到当前目录** → 写入 `annotatable.theme.css`,之后该目录下 `new` / `md` / `inject` 自动应用;
|
|
98
|
+
- **🌍 设为全局默认** → 写入 `~/.annotatable/theme.css`,任何目录都生效(目录级优先)。
|
|
99
|
+
|
|
100
|
+
**② 把喜欢的 HTML 一键变成默认模板**:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npx annotatable-html template --use 我的风格.html # 整页保留原样式,注入批注层,存为全局默认
|
|
104
|
+
npx annotatable-html template --reset # 撤销,回到内置模板
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
之后 `new` / `md` 生成的新文档都长你那个 HTML 的样子,批注/导航/编辑/历史功能全量可用。规则:
|
|
108
|
+
|
|
109
|
+
- 正文容器自动择优锚定(`<article>` → `<main>` → content 系 div → body 整体包裹);
|
|
110
|
+
- 模板优先级:`--template` 显式指定 > 目录内 `annotatable.template.html` > 全局 `~/.annotatable/template.html` > 内置;主题同理(目录 `annotatable.theme.css` > 全局);
|
|
111
|
+
- 注意:你 HTML 里的相对路径图片/链接换位置后会失效(装完有告警提示)。
|
|
112
|
+
|
|
113
|
+
只想微调配色的话:`annotatable theme` 导出主题文件手改也行,模板版式挂钩(`--wrap-max` / `--root-fs` / `--lh` / `--font-body` / `--font-head`)都可直接写进去。
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 工作原理
|
|
118
|
+
|
|
119
|
+
三个角色,一个数据库——**HTML 文件本体就是数据库**:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
浏览器(人类) HTML 文件本体 AI agent
|
|
123
|
+
┌──────────────────┐ ┌───────────────────────┐ ┌─────────────────────┐
|
|
124
|
+
│ 批注层 JS(自包含) │─写回──▶ │ annotations-store JSON │◀─read──│ annotatable read │
|
|
125
|
+
│ 划线/弹窗/面板/历史 │◀─渲染── │ (哨兵注释间的数据区块) │ │ (只解析 store 区块) │
|
|
126
|
+
│ ✏️ Word 编辑模式 │ │ + history 版本账本 │─reply─▶│ reply / edit / poll │
|
|
127
|
+
└──────────────────┘ └───────────────────────┘ │ (rev+1 + 快照写回) │
|
|
128
|
+
▲ └─────────────────────┘
|
|
129
|
+
可选:annotatable serve(127.0.0.1 静态伺服 + 受控写回端点,全自动保存)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- 批注数据存在于 HTML 内部的 `annotations-store` JSON 区块。agent 运行 read 获取待回应对话,无需读取整份 HTML。旧文档的 `answers-store` 扩展内容继续保留;普通回复不写入该区块。
|
|
133
|
+
- 任何写回必须 **rev+1 并维护 history 快照**(rev 铁律):浏览器靠 rev 检测冲突拒绝互相覆盖,工具链已封装;
|
|
134
|
+
- 状态机:批注 `status:"new"` → agent 回答后置 `answered` → 人类可追问(新轮次重新入账)。
|
|
135
|
+
|
|
136
|
+
## 项目架构
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
bin/cli.js 命令入口(init/new/md/theme/template/studio/skill/serve/inject/read/reply/edit/poll)
|
|
140
|
+
lib/core.js 全部核心逻辑(950 行):readPending / applyReplies / applyEdits
|
|
141
|
+
(含 buildHtmlIndex 锚定索引器 + bestOccurrence 消歧)/ pollAnnotations
|
|
142
|
+
/ injectHtml(备份统一落同目录 .annotatable/)/ installSkill / 双层账本
|
|
143
|
+
/ mdDoc+mdToBody(内置 marked/temml,GFM 转换与公式编译)/ templateUse+templateReset
|
|
144
|
+
/ studioHtml+loadThemeChain(主题工坊与模板四级优先级)
|
|
145
|
+
/ annotParts(注入四件套提取器,inject 与 templateUse 共用防双源)
|
|
146
|
+
lib/serve.js 本地伴随服务器(122 行):静态伺服 + /__annot/ping + /__annot/save 受控写回
|
|
147
|
+
+ /__annot/studio 工坊页 + /__annot/theme 工坊保存
|
|
148
|
+
lib/codex.js 可选 Codex 通知(poll --codex):官方会话接口、失败预检、静默超时
|
|
149
|
+
lib/opencode.js 可选 OpenCode 通知(poll --opencode):当前会话预检、异步消息与服务认证
|
|
150
|
+
lib/document.js 从本机页面地址定位文档,供新会话按地址接管
|
|
151
|
+
lib/poll-process.js 两种通知共用的原监听子进程与结构化命中读取
|
|
152
|
+
templates/
|
|
153
|
+
annotatable-doc.html 单文件模板(约 4000 行,产品本体):文档版式 + 批注层 + Word 编辑引擎 + 首绑引导;
|
|
154
|
+
:root 暴露 wrap-max / root-fs / lh / font-body / font-head 外观挂钩
|
|
155
|
+
theme-studio.html 主题工坊壳(studioHtml 装配:控件 + 真实模板实时预览 + 保存)
|
|
156
|
+
SKILL.md 唯一技能入口
|
|
157
|
+
references/ 宿主、正文操作和旧数据兼容说明(按需读取)
|
|
158
|
+
annotatable.theme.css 默认主题(init 复制,可改后自动应用)
|
|
159
|
+
commands/ slash 命令定义(/html4agent 等)
|
|
160
|
+
agent/ init 分发给用户目录的薄封装:annot_read / annot_reply / annot_edit / annot_poll.js
|
|
161
|
+
test/run.js 极简自测(187 项断言,无框架)
|
|
162
|
+
test/token-audit.js Token 消耗审计(o200k_base 实测,仓库自包含可复现,本文数据来源)
|
|
163
|
+
demo/ 示例文档
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
npm 包 `annotatable-html`(bin 命令 `annotatable`),`files` 只含 `bin/ lib/ agent/ templates/`——安装即得完整工具链。
|
|
167
|
+
|
|
168
|
+
## 代码设计(六个核心决策)
|
|
169
|
+
|
|
170
|
+
1. **单文件自包含**。模板一个文件装下全部:文档版式 CSS、批注层 CSS、UI 骨架 HTML、协议注释、store 数据、全部 JS(单 IIFE 闭包,函数不上 window)。数据与代码同文件 → 拷文件即拷走全部状态,邮件发送/归档/离线阅读都成立。代价是模板大(约 4000 行),由哨兵分区管理(见第 6 条)。
|
|
171
|
+
|
|
172
|
+
2. **批注锚定 = TextQuoteSelector 四元组**。每条批注存 `{quote 原句, prefix 前文, suffix 后文, ordinal 出现序号}`(W3C Web Annotation 思路),文档小幅改版后仍能重定位。实现上 `core.js buildHtmlIndex` 与浏览器端 `buildIndex` **同规则双实现**:建索引时统一折叠空白;同一原句多处命中时 `bestOccurrence` 按前缀右对齐/后缀左对齐的贴近度打分选位,全 0 分回退 ordinal(兼容老数据)。**双实现改动必须两侧同步**(有测试钩住)。
|
|
173
|
+
|
|
174
|
+
3. **rev 铁律 + 双层账本**。主文件 store 只留骨架 history(`{rev,time,op,n,bodyOps?}`,默认 60 条、可调 10~500),全量快照写同目录 `.annotatable/<文件名>.hist.json`(默认 1000 版、可调 50~5000,带 DOC_ID 归属校验防复制文件互染);外置档案写失败自动降级回主文件(上限 120)。快照容量规则在模板与 core.js 亦为双实现同步。
|
|
175
|
+
|
|
176
|
+
4. **bodyOps 纸条:人手与 AI 同账回滚**。人手编辑保存时做**块级 diff**(掐头去尾取中段)折算成 `{o原文, n新文, b前30字符, a后30字符}` 的纸条,与 AI 的 `edit` 记进同一账本(op=`✏️ 正文编辑`)。回滚在**磁盘文本**上倒序逆放纸条,且回滚条目自己也记 bodyOps——「撤销的撤销」可组合,任意编辑↔回滚序列都正确;纸条上下文定位失败则整体中止,不留中间态。纸条 >20KB 只存外置档案,不撑主文件。
|
|
177
|
+
|
|
178
|
+
5. **防烤入三重**。运行时状态(悬浮预览、尾节垫片、编辑态类、阅读态类、同步球状态)绝不写进文件:① 保存时 `buildAnnotatedHTML` 主动剥离;② 启动时 boot 清理老版本浏览器烤入的残留;③ `@media print` 隐藏。三者缺一都会在真实使用中累积垃圾(v1.16 曾吃过 4 个重复悬浮提示的亏)。
|
|
179
|
+
|
|
180
|
+
6. **哨兵分区支撑 inject**。批注层样式与 UI 骨架各有一对 `═══ … BEGIN/END ═══` 哨兵注释,`inject` 只提取哨兵区注入宿主(v1.17.0 起抽成 annotParts 四件套提取器,inject 与 templateUse 共用防双源);注入的 CSS 变量统一加 `--annot-` 前缀(`annotVarPrefix`)防污染宿主样式;`__ANNOT_NO_TOC__` 标志让注入版不接管宿主目录(v1.16.36 起首绑引导对注入版照常弹)。给批注层加新 UI 忘了放进哨兵对 = 注入版静默缺功能(v1.12 踩过)。
|
|
181
|
+
|
|
182
|
+
## 功能实现方法
|
|
183
|
+
|
|
184
|
+
### 批注侧(人类 → 文件)
|
|
185
|
+
- **划线批注**:`mouseup` 捕获选区(正文与回答大页两个根通用)→ 提取四元组锚点 → 渲染为 `<mark class="annot">`(重叠区间合并)→ 划选工具条 → 批注弹窗以**对话线程**(thread 轮次数组)组织 → `persist()` 序列化回 store 落盘;
|
|
186
|
+
- **回答大页**:AI 回答渲染成一篇可批注的子文档,子批注用 `scope` 字段记父批注 id——回答可以被继续划线、页内追问,追问经 `read` 重新进入 agent 视野;
|
|
187
|
+
- **批注面板**:按 AI 回应轮次分组折叠、点外部空白收起、已回应轮次默认折叠;历史条目展示本轮批注摘要。
|
|
188
|
+
|
|
189
|
+
### 写回侧(agent → 文件)
|
|
190
|
+
- **read**:只解析 store 区块,列 `status:"new"` 批注的完整对话线(原句+上下文+全部轮次);
|
|
191
|
+
- **reply**:`replies.json` → 追加 ai 轮次 + 置 answered + 快照 + rev+1;
|
|
192
|
+
- **edit**:`edits.json`(批语是祈使句「这里改成X」时)→ `buildHtmlIndex` 定位原句 → 替换为 newText → **重算该批注锚定**(批注跟着正文走)→ bodyOps 记账供回滚;混合批注用 reply 字段一并答疑;
|
|
193
|
+
- **poll**:阻塞轮询,只比对 `{rev, 总数, 待回应}` 签名(文件被写盘中时静默跳过本轮);人类批注保存后打印 `POLL_HIT <json>` 以命中退出码结束——放进 agent harness 的后台任务,进程结束即唤醒 agent 续接读批注循环。退出码按 harness 选(v1.38.4):zcode 等「任务退出即唤醒」的用默认(命中 0 / `--timeout` 到点 3);Claude Code 等非零才唤醒的加 `--exit-code 1`,超时轮空不想叫人再加 `--exit-code-timeout 0`。另:read/poll/serve 触碰文档目录时会自动把 init 分发的过期 lib/agent 拷贝同步到当前包版(旧版留 `.bak-kit-*` 备份)——拷贝静止腐烂曾是「同一份数据 read 与 poll 判定分裂」的根因。
|
|
194
|
+
|
|
195
|
+
### Word 编辑模式(人类直改正文)
|
|
196
|
+
- **✏️ 进入**:`article` 设 contenteditable + 悬浮工具栏(加粗/斜体/字号步进/五色字/五色高亮/格式刷双击连续/💬批注);批注 mark 进编辑态时整体剥离(批注是层不是正文);点五色高亮出 ✕ 小钮可删高亮(unwrapMark,文字保留,Ctrl+Z 可撤销);
|
|
197
|
+
- **💾 保存**:块级 diff 折算 bodyOps 纸条入账(见设计第 4 条);编辑期间批注改动只标 dirty,随 Word 保存一起落盘;编辑态绝不落盘(防烤入);
|
|
198
|
+
- **🧭 结构操作**(标题升降级/整节移位)在非编辑态点用时,经 `wordStructuralChange` 自动走「进入编辑 → 改 → 保存 → 退出」,用户无感;
|
|
199
|
+
- **查找替换**:文本节点内替换(跨标签匹配不支持,界面如实提示);
|
|
200
|
+
- **Markdown 快捷输入**:编辑态行首敲 `#`–`######`、`-`/`*`、`1.`/`1)`、`>` 后按空格,整块即时转为标题/无序列表/有序列表/引用——标记删除与块转换全走 execCommand,Ctrl+Z 可逐步撤销;标题行尾回车自动新建正文段(Word 习惯),标题中部回车保持浏览器原生同级拆分;```` ``` ````+空格或工具栏「</> 代码块」钮把当前段整块转为代码块——块内回车换行、``` 独占一行+空格收尾回正文段,悬停代码块右上角出「⧉ 复制」一键复制(块内 `<br>` 折算成换行符)。
|
|
201
|
+
|
|
202
|
+
### 🧭 导航窗格
|
|
203
|
+
- **目录树**:现场扫描 h1–h5 生成(**零 token**,注入版同享);行与标题严格 1:1——折叠箭头是空 span + CSS 三角,不污染 `row.textContent`(外部探针/e2e 依赖行文本==标题文本的约定);
|
|
204
|
+
- **窗口化**:标题栏拖拽(一拖即脱离左泊位变悬浮)+ 原生 resize,开关/位置/尺寸按 `annot:nav:<SCOPE>` 记 localStorage;泊位时给 body 写入与窗格等宽的左内边距 `--annot-nav-space`(ResizeObserver 跟随),`min-width:1100px` 生效、窄屏自动悬浮;
|
|
205
|
+
- **章节折叠**:每行 ▾/▶ 点按折/展整棵子树(`stopPropagation` 不触发跳转);`applyFolds()` 每次从祖先链重算可见性——外层折叠吞掉内层、展开外层后内层保持折叠,嵌套天然正确;折叠状态按标题文本存(上限 60 条防撑爆),随文件记忆;搜索时忽略折叠全展开、清空恢复;
|
|
206
|
+
- **精准跳转**:绝对坐标 `scrollTo(top = 标题top - 88px)`(弃用 scrollIntoView center——长卡片页会甩到屏中);尾节受浏览器滚动钳制物理上滚不到 88px,点行时按缺口给文档尾垫 `#annot-tailpad`(Word 式 scroll-past-end,运行时垫片,保存剥离);
|
|
207
|
+
- **Del 移除书签**:窗格开着时点选标题行(`.sel` 高亮)按 Del,只把这条书签从目录移除——**正文一字不动**(连标题行都保留,文件零写入);按标题文本随窗格状态记忆(localStorage),窗格底部 ♻️ 一键恢复;焦点在输入框或弹层开着时不拦 Del。
|
|
208
|
+
|
|
209
|
+
### Markdown 全面兼容
|
|
210
|
+
- **一键转换 `annotatable md <文件.md>`**:marked GFM 解析,正文完全遵照原文(表格/代码围栏/任务列表/删除线照常渲染),表格外包 `.table-wrap` 套模板容器样式,页面标题取首个一级标题(`--title` 可覆盖、`-o` 指定输出,默认与 md 同目录同名);只叠加批注层,批注/编辑/历史全部可用;
|
|
211
|
+
- **内置依赖(vendored)**:两处第三方库以单文件形式内置在包内 `lib/vendor/`(均 MIT):**marked 4.3.0**(Markdown→HTML,`md` 转换)、**temml 0.14.0**(LaTeX→MathML,公式编译,编译失败自动保留 LaTeX 原文)。二者均为**惰性加载**——仅 `md` 命令付出 require 成本,其余命令零加载;`npm install annotatable-html` **不拉任何外部依赖**,离线/内网环境可装。升级方式:用新版 CJS 单文件覆盖 `lib/vendor/` 对应文件后跑全量测试。
|
|
212
|
+
|
|
213
|
+
### 📖 阅读模式
|
|
214
|
+
一键隐藏全部功能按钮(fab/✏️/同步球/wordbar/查找),导航窗格**收起不隐藏**(把手可随时复开);📖 自身保持可见作为退出入口;Esc 分级退出(先关残留弹层再退阅读);与编辑模式互斥。**划线批注在阅读模式照常可用**——这是产品哲学,不是遗漏。
|
|
215
|
+
|
|
216
|
+
### 引导、保存与自愈
|
|
217
|
+
- **首绑引导层**:未绑定的文档打开 800ms 后(等句柄恢复与 serve 探测落地)弹全屏两步引导:①复制文件夹路径 ②绑定文件夹(推荐)/只绑定本文件;绑定成功记 sessionStorage 本会话不再弹;卡片只放「📁 绑定文件夹(推荐)」一个绑定选项,右上角小字「先不绑定,直接浏览 →」(v1.16.39 精简);引导**文案本身可手改**——入口在批注面板底部「✏️ 引导文案」(走标准 persist 记账),inject 宿主无手改入口(v1.16.36);
|
|
218
|
+
- **保存三通道**:① serve 同源 POST(全自动,全浏览器)② File System Access 句柄(file:// 打开,Edge/Chrome,首次点选授权)③ 下载带批注的 HTML(降级);
|
|
219
|
+
- **镜像自愈**:批注/句柄在 IndexedDB 留镜像缓存,key 按 `DOC_ID:location.pathname` 隔离(复制文件不互染);文件本体永远是数据源,镜像只是加速与自愈;
|
|
220
|
+
- **历史面板**:日期分组、⚓回滚锚、点行聚合预览(全程 textContent 渲染防自产 XSS)、容量可调(⚙)。
|
|
221
|
+
|
|
222
|
+
### 服务器安全(lib/serve.js)
|
|
223
|
+
仅监听 127.0.0.1;`/__annot/save` 只收 `application/json`(拦跨站简单请求)、Origin 必须为空或 localhost、写入目标限定伺服目录内的 `.html`(防路径越界)、body 上限 20MB;中文文件名的百分号编码会先解码(曾因此全军覆没过的坑)。
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Token 消耗(v1.20.0 实测)
|
|
228
|
+
|
|
229
|
+
分词器 o200k_base(GPT-4o 系词表);回答正文本身不计入「额外」——无论哪种方式 AI 都要生成同样的回答。**复现:`node test/token-audit.js`**(需 `npm i` 装 devDependency gpt-tokenizer)。完整方法论与历史版本数据见 `docs/批注工作流Token消耗测试报告.md`。
|
|
230
|
+
|
|
231
|
+
### 使用侧:agent 每轮「读批注 → 写回」
|
|
232
|
+
|
|
233
|
+
| 待回应批注 | read 输出 | replies.json | 命令 | **每轮合计** |
|
|
234
|
+
|---|---|---|---|---|
|
|
235
|
+
| 1 条 | 141 | 41 | 30 | **212** |
|
|
236
|
+
| 5 条 | 684 | 209 | 30 | **923** |
|
|
237
|
+
| 20 条 | 2,691 | 812 | 30 | **3,533** |
|
|
238
|
+
| 50 条 | 6,706 | 2,018 | 30 | **8,754** |
|
|
239
|
+
|
|
240
|
+
边际成本 **每条批注 ≈ +174 tok**,每轮固定命令开销 ≈ 30 tok——**与文档体量完全无关**(read 只输出待回应批注,不读正文)。写回包装:`replies.json` JSON 包装 ≈13 tok/条,`edits.json` ≈16 tok/条(newText 新正文属本来就要生成的内容),`poll` 唤醒签名 ≈31 tok/次。
|
|
241
|
+
|
|
242
|
+
### 一次性成本
|
|
243
|
+
|
|
244
|
+
| 组件 | Token |
|
|
245
|
+
|---|---|
|
|
246
|
+
| references/operations.md(正文操作,按需读取) | 见当前 Token 审计 |
|
|
247
|
+
| SKILL.md(skill 入口,触发时读取) | 见当前 Token 审计 |
|
|
248
|
+
|
|
249
|
+
运行 `node test/token-audit.js` 查看当前版本的实际数量。
|
|
250
|
+
|
|
251
|
+
### 生成侧:创建一份可批注文档
|
|
252
|
+
|
|
253
|
+
| 路径 | 额外 token |
|
|
254
|
+
|---|---|
|
|
255
|
+
| `annotatable new` / `inject` / `md`(推荐) | **≈ 9**(一条命令,批注层由模板免费带入) |
|
|
256
|
+
| agent 不用工具手写完整批注层 | ≈ **5.5 万**(批注层合计 54,789 tok,`test/token-audit.js` 可查明细) |
|
|
257
|
+
|
|
258
|
+
### 对比基线(同样回答 N 条批注,以当前样例文档计)
|
|
259
|
+
|
|
260
|
+
| N | 本工作流 | 传统①AI 读整份 HTML | 传统②人类粘贴全文 |
|
|
261
|
+
|---|---|---|---|
|
|
262
|
+
| 1 | 212 | 61,484(省 **99%**) | 2,377(省 **91%**) |
|
|
263
|
+
| 5 | 923 | 61,484(省 **98%**) | 2,377(省 **61%**) |
|
|
264
|
+
| 20 | 3,533 | 61,484(省 **94%**) | 粘贴更省* |
|
|
265
|
+
| 50 | 8,754 | 61,484(省 **85%**) | 粘贴更省* |
|
|
266
|
+
|
|
267
|
+
\* 诚实注:小文档+大批注量的极端组合下,粘贴全文的原始 token 更少。但粘贴方式每轮都要重贴全文、无法增量回应、无结构化状态(谁已答/谁未答)、无版本回滚,且成本随文档体量线性增长(10 万字文档每轮 6–10 万 tok);本工作流只随批注数线性增长。批注是稀疏活动——实际使用中绝大多数轮次 ≤10 条。
|
|
268
|
+
|
|
269
|
+
### 输出侧:锚定让回答也更省
|
|
270
|
+
|
|
271
|
+
批注锚点已持久记录了原句与位置,线程式回复不需要聊天式的「复述问题+承接过渡」(docs 报告 §7 实测):单条回答省 **59%**,三轮使用生命周期(答→追问→回看)省 **67%**——第 3 轮无新批注时输出趋近于零。这是结构使然,不是提示词纪律。
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 浏览器支持
|
|
276
|
+
|
|
277
|
+
- **Edge / Chrome**:完整能力。file:// 打开经 File System Access API 自动写回(首次绑定授权一次);
|
|
278
|
+
- **Firefox / Safari**:配合 `annotatable serve` 同样获得完整能力(服务器模式自动写回);不用服务器时批注可用,写回退化为「下载带批注的 HTML」。
|
|
279
|
+
|
|
280
|
+
## 服务器模式(真·全自动)
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
annotatable serve # 伺服当前目录,http://localhost:8973
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
浏览器打开伺服地址即用:无需绑定、无需授权,批注经同源请求静默写回文件(安全边界见上)。左下角同步小球显示 ✓ 绿色即生效。
|
|
287
|
+
|
|
288
|
+
## 给第三方脚本用的保存清理钩子(v1.31.0)
|
|
289
|
+
|
|
290
|
+
批注文档欢迎叠加自定义交互壳(翻页条、悬浮目录……)。但保存=整页序列化,**运行时自建的 DOM 会被烤进文件**——下次打开静态壳与运行时壳并存,`getElementById` 命中静态死元素,控件集体失灵(重复 id 撞车)。给脚本加一行注册即可断根:
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
// 方式一:选择器数组——保存时从序列化结果里删掉这些运行时节点
|
|
294
|
+
window.__annotSaveStrip = ['.pf-bar', '.pf-toc', '#my-float'];
|
|
295
|
+
|
|
296
|
+
// 方式二:清理函数——接收整棵克隆树,自查自删(适合动态规则)
|
|
297
|
+
window.__annotSaveStrip = function (clone) {
|
|
298
|
+
clone.querySelectorAll('.my-shell[data-runtime]').forEach(n => n.remove());
|
|
299
|
+
};
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- 注册前后行为完全兼容:不注册的脚本维持原样(工具不擅自删你的节点);
|
|
303
|
+
- 静态写在源码里的 `<script>` 标签不受影响,只有运行时节点被剥离;
|
|
304
|
+
- 忘了注册也有兜底:agent 侧 `read/reply` 的结构体检会报「重复 id N 个——疑似运行时 DOM 被烤入磁盘」。
|
|
305
|
+
|
|
306
|
+
## live-sync 磁盘漂移检测(v1.32.0)
|
|
307
|
+
|
|
308
|
+
热载以 store rev 为准绳有个盲区:**改盘但不 bump rev 的写手**(agent 的 python 直改、维护手术、模板升级脚本)曾被页面整体无视——旧会话与磁盘静默脱钩,还继续以旧结构保存放大污染。现在页面维护一份「壳指纹」(正文区 + 批注/回答两个 store 区块剥掉后的磁盘文本),每次探盘前后对比:
|
|
309
|
+
|
|
310
|
+
- **壳变了**(脚本/样式/壳 DOM)→ 页面顶部常驻琥珀条:「磁盘已有正文之外的更新」+ **刷新对齐**(先把防抖里的最后一笔冲落盘再 reload,零丢失)/ **知道了**;
|
|
311
|
+
- **只有正文变**(rev 不动)→ 照旧直接热载上屏,不打扰;
|
|
312
|
+
- **rev 递增且壳也变** → 条上话术缓和为「正文已热同步;另有正文之外的更新,建议刷新」;
|
|
313
|
+
- **正常 agent 回注**(只动 store+正文)→ 壳指纹不变,零提示零噪音;页面自己保存也用写出内容续基线,绝不自报。
|
|
314
|
+
|
|
315
|
+
局限:inject 模式(正文区含批注 UI 的宿主页)正文无法精确剥离,壳检测降级为只看 head 区。
|
|
316
|
+
|
|
317
|
+
## 值守心跳与状态灯(v1.33.0)
|
|
318
|
+
|
|
319
|
+
值守 poll 是一次性进程(命中即退出唤醒 agent),靠 agent 一班班续挂——**上下文耗尽 / 会话关闭的瞬间链条会静默断掉**:serve 不知道没有 watcher,页面一切正常,人对着页面喊话几十分钟无人应答(真实事故:42 分钟值守真空)。现在链条的生死变得可观测:
|
|
320
|
+
|
|
321
|
+
- **心跳**:poll 存活期间每轮写 `.annotatable/<文件>.watch.json`(pid / startedAt / lastBeat / state);命中或超时退出时改写为 `handoff`(交班)——进程死了心跳就停,文件成为「最后值岗」的物证;
|
|
322
|
+
- **页面状态灯**(右下角,serve 模式常驻):🟢 **值守中**(心跳 ≤15s)/🟡 **心跳超龄**(watcher 疑似暴毙)/🟡 **换班中**(交班 ≤5 分钟,agent 应答/重挂中)/🔴 **无人值守 · 最后心跳 X 分钟前**/🔴 **未挂值守**——让人在朝深渊喊话之前,先看见灯是红的。file:// 页面无值守概念,灯不出现;
|
|
323
|
+
- **`/__annot/status?path=…`**:serve 只读端点,报 watcher 存活与心跳年龄(撕裂心跳按无心跳保守渲染);
|
|
324
|
+
- **断档补捞**:`read` 与 `poll` 启动时对比待回应批注的出生时间与上一班最后心跳,报告「值守离线期收到 N 条批注(最早一条等待 X)」——两班之间收到的批注不再只靠进程考古才发现,agent 一眼定优先级。
|
|
325
|
+
|
|
326
|
+
架构级后续(信箱 inbox.jsonl、常驻 `watch` 命令)已设计待拍板,见 issue #6。
|
|
327
|
+
|
|
328
|
+
## 让任意 agent 触发本工作流
|
|
329
|
+
|
|
330
|
+
- **安装工作流**:运行 `npx annotatable-html skill`;可用 `--codex`、`--opencode` 等参数选择工具。
|
|
331
|
+
- **生成文档**:对 agent 说「生成一份可批注的 HTML 文档《XX》」。需要目录内工具或主题时再运行 `init`。
|
|
332
|
+
- **接续批注**:告诉 agent 目标文档,再说「读批注」或「恢复值守」。通用会话启动时不扫描全局待办。
|
|
333
|
+
|
|
334
|
+
## 测试
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
node test/run.js # 锚定、账本、回滚、协议、渲染、技能安装与宿主指令保留
|
|
338
|
+
npm run test:hosts # 续接、通知、回复传参和页面接管回归(Node.js 22+)
|
|
339
|
+
node test/token-audit.js # Token 审计(本文档数据来源)
|
|
340
|
+
node test/theme-e2e.js # 主题/自定义模板 端到端(11 项)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
宿主回归默认使用隔离夹具,不调用真实模型或关闭桌面。真实宿主验证及范围见 [续接方案](docs/Codex批注续接方案.md);脱敏结果见 [验收摘要](record/continuation-verification.json)。原始会话、账号上下文、浏览器资料和安装备份仅保留在本机,排除在 Git 与 npm 包之外。
|
|
344
|
+
|
|
345
|
+
另有十余个无头 e2e(puppeteer-core + Edge,经 `annotatable serve` 的 http 访问)覆盖 编辑→落盘→回滚全链路、面板分组、注入宿主、首绑引导、导航窗格几何与跳转精度、尾节垫片、阅读模式、章节折叠、Markdown 全链路(md 转换→活批注面→编辑态 md 输入→导航书签移除/恢复)、块拖拽(⠿ 手柄换序→落盘,模板文档与 inject 注入宿主各测一份)、**主题工坊(studio 控件→预览烤入→保存/设全局→new/md 接入,v1.17.0)**。
|
|
346
|
+
|
|
347
|
+
## 设计说明
|
|
348
|
+
|
|
349
|
+
批注锚定采用 W3C Web Annotation(TextQuoteSelector)思路。工作流公开在包内 `templates/SKILL.md` 与按需说明中。任意具备文件读写能力的 agent(Claude Code / Codex / Cursor / ZCode / OpenCode…)均可接入。一个浏览器与一个能读写文件的 agent 即可使用。
|
|
350
|
+
|
|
351
|
+
## License
|
|
352
|
+
|
|
353
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/* 用法: node agent/annot_answer.js <html路径> <answers.json>
|
|
4
|
+
* answers.json: [{"annId":"批注id","title":"卡片标题","rungs":[{"title":"阶标题","body":"阶正文","checkpoint":"检查点(可选)","answer":"参考答案(可选)"}]}]
|
|
5
|
+
* 合并语义:id 或 annId+title 命中 → 整卡替换(「继续」时全量带上旧阶+新阶);否则新增。
|
|
6
|
+
* 写入 answers-store 区块、批注 rev+1 入账本。逻辑在 ../lib/core.js */
|
|
7
|
+
require('../lib/core.js').applyAnswers(process.argv[2], process.argv[3]);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/* 用法: node agent/annot_edit.js <html路径> <edits.json>
|
|
4
|
+
* edits.json: [{"id":"批注id","newText":"改后正文","reply":"可选说明"}]
|
|
5
|
+
* 定位批注原句→替换正文→重算该批注锚定→快照+rev+1。逻辑在 ../lib/core.js */
|
|
6
|
+
require('../lib/core.js').applyEdits(process.argv[2], process.argv[3]);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/* 用法: node agent/annot_poll.js <html路径> [--timeout 秒] [--interval 秒] [--exit-code N] [--exit-code-timeout N]
|
|
4
|
+
* 阻塞等待新批注:已有待回应立即退出;否则轮询,人类批注保存后打印 POLL_HIT <json> 并退出唤醒 agent;
|
|
5
|
+
* 超时以 3 退出(默认)。退出码按 harness 选(issue#8):
|
|
6
|
+
* zcode 等后台任务退出即唤醒 → 默认即可(命中 0 / 超时 3);
|
|
7
|
+
* Claude Code 等非零退出码才唤醒 → --exit-code 1,且超时轮空不想叫人再加 --exit-code-timeout 0。
|
|
8
|
+
* 逻辑在 ../lib/core.js */
|
|
9
|
+
var args = process.argv.slice(3);
|
|
10
|
+
function flag(name, dft) {
|
|
11
|
+
var i = args.indexOf(name);
|
|
12
|
+
return i !== -1 ? args[i + 1] : dft;
|
|
13
|
+
}
|
|
14
|
+
require('../lib/core.js').pollAnnotations(process.argv[2], {
|
|
15
|
+
timeout: flag('--timeout', 0),
|
|
16
|
+
interval: flag('--interval', 3),
|
|
17
|
+
exitCode: parseInt(flag('--exit-code', 0), 10),
|
|
18
|
+
timeoutExitCode: parseInt(flag('--exit-code-timeout', 3), 10)
|
|
19
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/* 用法:
|
|
4
|
+
* node agent/annot_reply.js <html路径> <replies.json> 批量写回
|
|
5
|
+
* node agent/annot_reply.js <html路径> --stream '段落' --id 批注id 流式开始/续段
|
|
6
|
+
* node agent/annot_reply.js <html路径> --stream --stdin --id 批注id < 段落.txt
|
|
7
|
+
* node agent/annot_reply.js <html路径> --stream-end '全文' [--id 批注id] 收尾
|
|
8
|
+
* 追加 ai 轮次、维护快照、rev+1。流式退出码:0=成功 4=被打断 5=批注已删 6=人类编辑中(让路) 7=没有打开中的流式轮。
|
|
9
|
+
* 逻辑在 ../lib/core.js */
|
|
10
|
+
var fs = require('fs');
|
|
11
|
+
var args = process.argv.slice(3);
|
|
12
|
+
var flags = {}, positional = [];
|
|
13
|
+
for (var i = 0; i < args.length; i++) {
|
|
14
|
+
if (args[i].indexOf('--') === 0) {
|
|
15
|
+
var k = args[i].slice(2);
|
|
16
|
+
if (k === 'stdin') { flags[k] = true; }
|
|
17
|
+
else if (args[i + 1] && args[i + 1].indexOf('--') !== 0) { flags[k] = args[i + 1]; i++; }
|
|
18
|
+
else { flags[k] = ''; }
|
|
19
|
+
} else positional.push(args[i]);
|
|
20
|
+
}
|
|
21
|
+
var core = require('../lib/core.js');
|
|
22
|
+
var sText, sEnd = false;
|
|
23
|
+
if (flags.stream !== undefined) { sText = flags.stream; }
|
|
24
|
+
else if (flags['stream-end'] !== undefined) { sText = flags['stream-end']; sEnd = true; }
|
|
25
|
+
if (sText !== undefined) {
|
|
26
|
+
if (flags.stdin) { try { sText = fs.readFileSync(0, 'utf8'); } catch (e) { sText = ''; } }
|
|
27
|
+
var r = core.streamReply(process.argv[2], { text: sText, id: flags.id, end: sEnd });
|
|
28
|
+
if (r.status === 'ok') {
|
|
29
|
+
console.log((r.done ? '✅ 回答完成' : r.started ? '▶️ 流式回答开始' : '➕ 已追加段落') + ' · 批注 ' + r.annId + ' · rev=' + r.rev);
|
|
30
|
+
if (r.kept) console.log('⚠️ 收尾没带全文——已保留已流式内容收尾(' + r.chars + ' 字)。本条回答以此为准,不再重发');
|
|
31
|
+
if (r.closed && r.closed.length) console.log('ℹ️ 顺手封轮(未收尾的遗留流式轮,文本保留): ' + r.closed.join(', '));
|
|
32
|
+
} else if (r.status === 'interrupted') {
|
|
33
|
+
console.log('INTERRUPTED 人类已按⏹停止(批注 ' + r.annId + ' · rev=' + r.rev + ')——本轮作答终止,不要再追加');
|
|
34
|
+
process.exitCode = 4;
|
|
35
|
+
} else if (r.status === 'target-gone') {
|
|
36
|
+
console.log('TARGET_GONE 批注 ' + r.annId + ' 已被删除——本轮作答终止');
|
|
37
|
+
process.exitCode = 5;
|
|
38
|
+
} else if (r.status === 'empty') {
|
|
39
|
+
console.log('空段落:--stream 需要文本或 --stdin');
|
|
40
|
+
process.exitCode = 2;
|
|
41
|
+
} else if (r.status === 'empty-end') {
|
|
42
|
+
console.log('空收尾被拒绝:--stream-end 需要完整全文(没有流式轮时收空尾=落一条空回答)');
|
|
43
|
+
process.exitCode = 2;
|
|
44
|
+
} else {
|
|
45
|
+
console.log('NO_STREAM 没有打开中的流式回答——首段请带 --id 批注id(退出码 7)');
|
|
46
|
+
process.exitCode = 7;
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
core.applyReplies(process.argv[2], positional[0]);
|
|
50
|
+
}
|