@xgspz/cli-ui 0.0.0-stage → 0.6.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.
Files changed (169) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/README.md +512 -2
  3. package/dist/cli-ui-embed-D-3DvyTN.js +50279 -0
  4. package/dist/cli-ui-index-C34qFb4u.js +11209 -0
  5. package/dist/cli-ui-jszip.min-BObcE2nn.js +26 -0
  6. package/dist/cli-ui-jszip.min-DojbZPiE.js +2324 -0
  7. package/dist/cli-ui-xlsx-JVLtx0qR.js +93 -0
  8. package/dist/cli-ui.css +32 -0
  9. package/dist/cli-ui.js +17 -0
  10. package/package.json +60 -4
  11. package/src/board/BrowserPane.tsx +969 -0
  12. package/src/board/ChangesPane.tsx +185 -0
  13. package/src/board/CliBoard.tsx +484 -0
  14. package/src/board/FileViewer.tsx +721 -0
  15. package/src/board/FilesPane.tsx +262 -0
  16. package/src/board/HtmlReadingPreview.tsx +52 -0
  17. package/src/board/MarkdownPreview.tsx +134 -0
  18. package/src/board/PanelControls.tsx +40 -0
  19. package/src/board/blocks.tsx +97 -0
  20. package/src/board/context.ts +65 -0
  21. package/src/board/diffLines.ts +46 -0
  22. package/src/board/fileKind.ts +250 -0
  23. package/src/board/fileOps.ts +85 -0
  24. package/src/board/fileScope.ts +32 -0
  25. package/src/board/fileSync.ts +351 -0
  26. package/src/board/fileSyncCache.ts +250 -0
  27. package/src/board/fileSyncProcessor.ts +72 -0
  28. package/src/board/fileSyncQueue.ts +56 -0
  29. package/src/board/fileSyncTransport.ts +85 -0
  30. package/src/board/fileWatch.ts +211 -0
  31. package/src/board/folderDownload.ts +211 -0
  32. package/src/board/frameQuote.ts +52 -0
  33. package/src/board/htmlReading.ts +148 -0
  34. package/src/board/panels.ts +33 -0
  35. package/src/board/textLoad.ts +76 -0
  36. package/src/board/useBoardUpload.tsx +51 -0
  37. package/src/board/useBoardVisibility.ts +35 -0
  38. package/src/board/workspace.ts +200 -0
  39. package/src/board/zip.ts +85 -0
  40. package/src/chat/CliChat.tsx +932 -0
  41. package/src/chat/Composer.tsx +239 -0
  42. package/src/chat/GroupChat.tsx +113 -0
  43. package/src/chat/RunControls.tsx +196 -0
  44. package/src/chat/configuration.ts +15 -0
  45. package/src/chat/draft.ts +49 -0
  46. package/src/chat/export.ts +34 -0
  47. package/src/chat/queueEdit.ts +27 -0
  48. package/src/chat/useExecutionConfig.ts +93 -0
  49. package/src/components.tsx +13 -0
  50. package/src/core/blocks.ts +205 -0
  51. package/src/core/callbacks.ts +8 -0
  52. package/src/core/client.ts +822 -0
  53. package/src/core/config.ts +252 -0
  54. package/src/core/conversation.ts +577 -0
  55. package/src/core/documentUpload.ts +196 -0
  56. package/src/core/execution.ts +24 -0
  57. package/src/core/id.ts +15 -0
  58. package/src/core/identity.ts +21 -0
  59. package/src/core/provider.tsx +141 -0
  60. package/src/core/sha256.ts +21 -0
  61. package/src/core/sse.ts +51 -0
  62. package/src/core/store.ts +1103 -0
  63. package/src/core/subagents.ts +78 -0
  64. package/src/core/textDecode.ts +24 -0
  65. package/src/core/types.ts +505 -0
  66. package/src/core/xlsx.ts +142 -0
  67. package/src/elements.tsx +376 -0
  68. package/src/embed.ts +56 -0
  69. package/src/index.ts +56 -0
  70. package/src/layout/CliSplit.tsx +53 -0
  71. package/src/layout/split.ts +251 -0
  72. package/src/msglist/CliMsgList.tsx +422 -0
  73. package/src/operator/branchFlow.ts +35 -0
  74. package/src/operator/index.ts +9 -0
  75. package/src/operator/liveStream.ts +1 -0
  76. package/src/operator/node/AgentConnect.tsx +619 -0
  77. package/src/operator/node/AppearancePanel.tsx +35 -0
  78. package/src/operator/node/BranchHistory.tsx +67 -0
  79. package/src/operator/node/BranchNavigator.tsx +36 -0
  80. package/src/operator/node/BrowserPane.tsx +128 -0
  81. package/src/operator/node/BrowserPopup.tsx +22 -0
  82. package/src/operator/node/Chat.tsx +538 -0
  83. package/src/operator/node/CommandPalette.tsx +213 -0
  84. package/src/operator/node/Composer.tsx +316 -0
  85. package/src/operator/node/InsightsView.tsx +831 -0
  86. package/src/operator/node/Layout.tsx +192 -0
  87. package/src/operator/node/Markdown.tsx +61 -0
  88. package/src/operator/node/Modal.tsx +179 -0
  89. package/src/operator/node/NeedsYouMenu.tsx +54 -0
  90. package/src/operator/node/OnboardingWizard.tsx +248 -0
  91. package/src/operator/node/Orchestrator.tsx +644 -0
  92. package/src/operator/node/ProjectLanding.tsx +86 -0
  93. package/src/operator/node/ProjectsColumn.tsx +137 -0
  94. package/src/operator/node/ProviderAuth.tsx +559 -0
  95. package/src/operator/node/RunControls.tsx +2 -0
  96. package/src/operator/node/RunSwitch.tsx +113 -0
  97. package/src/operator/node/Services.tsx +245 -0
  98. package/src/operator/node/SessionRail.tsx +299 -0
  99. package/src/operator/node/SessionView.tsx +292 -0
  100. package/src/operator/node/SettingsView.tsx +690 -0
  101. package/src/operator/node/TaskBoard.tsx +352 -0
  102. package/src/operator/node/TaskChanges.tsx +526 -0
  103. package/src/operator/node/TaskHero.tsx +50 -0
  104. package/src/operator/node/TasksColumn.tsx +200 -0
  105. package/src/operator/node/Terminal.tsx +143 -0
  106. package/src/operator/node/Transcript.tsx +485 -0
  107. package/src/operator/node/ViewFile.tsx +79 -0
  108. package/src/operator/node/Welcome.tsx +101 -0
  109. package/src/operator/node/WorktreePrune.tsx +333 -0
  110. package/src/operator/node/agents.ts +58 -0
  111. package/src/operator/node/api.ts +57 -0
  112. package/src/operator/node/boardBackend.ts +63 -0
  113. package/src/operator/node/boardEvents.ts +40 -0
  114. package/src/operator/node/boardInteraction.ts +15 -0
  115. package/src/operator/node/boardState.ts +74 -0
  116. package/src/operator/node/boardWorkspace.ts +15 -0
  117. package/src/operator/node/boundedPoll.ts +47 -0
  118. package/src/operator/node/branchFlow.ts +1 -0
  119. package/src/operator/node/composerDraft.ts +78 -0
  120. package/src/operator/node/fileLinks.ts +81 -0
  121. package/src/operator/node/format.ts +292 -0
  122. package/src/operator/node/github.tsx +232 -0
  123. package/src/operator/node/icons.tsx +88 -0
  124. package/src/operator/node/liveStream.ts +323 -0
  125. package/src/operator/node/modals.tsx +766 -0
  126. package/src/operator/node/navHistory.ts +70 -0
  127. package/src/operator/node/persist.ts +26 -0
  128. package/src/operator/node/protocol/agents/optIn.ts +9 -0
  129. package/src/operator/node/protocol/agents/types.ts +394 -0
  130. package/src/operator/node/protocol/approvalFailure.ts +77 -0
  131. package/src/operator/node/protocol/authFailure.ts +66 -0
  132. package/src/operator/node/protocol/customModels.ts +23 -0
  133. package/src/operator/node/protocol/events.ts +13 -0
  134. package/src/operator/node/protocol/features.ts +70 -0
  135. package/src/operator/node/protocol/internalUsage.ts +1 -0
  136. package/src/operator/node/protocol/promptLimits.ts +52 -0
  137. package/src/operator/node/protocol/storageRetention.ts +13 -0
  138. package/src/operator/node/protocol/types.ts +335 -0
  139. package/src/operator/node/protocol/usageLimit.ts +72 -0
  140. package/src/operator/node/shared.tsx +89 -0
  141. package/src/operator/node/transcriptModel.ts +129 -0
  142. package/src/operator/node/types.ts +305 -0
  143. package/src/operator/node/useGlobalEvents.ts +97 -0
  144. package/src/operator/node/useOrchestrator.ts +697 -0
  145. package/src/operator/node/usePrefs.ts +114 -0
  146. package/src/operator/node/useRecaps.ts +102 -0
  147. package/src/operator/node/useTaskStream.ts +286 -0
  148. package/src/operator.base.css +1949 -0
  149. package/src/operator.css +1949 -0
  150. package/src/styles.css +579 -0
  151. package/src/ui/ErrorBoundary.tsx +38 -0
  152. package/src/ui/Markdown.tsx +60 -0
  153. package/src/ui/Popover.tsx +95 -0
  154. package/src/ui/QueuedMessages.tsx +110 -0
  155. package/src/ui/ReferenceCard.tsx +28 -0
  156. package/src/ui/SubagentChat.tsx +233 -0
  157. package/src/ui/TextBlock.tsx +51 -0
  158. package/src/ui/Transcript.tsx +435 -0
  159. package/src/ui/UploadProgress.tsx +46 -0
  160. package/src/ui/WorkspaceDrawer.tsx +64 -0
  161. package/src/ui/WorkspaceTabs.tsx +60 -0
  162. package/src/ui/fileLinks.ts +52 -0
  163. package/src/ui/format.ts +103 -0
  164. package/src/ui/icons.tsx +92 -0
  165. package/src/ui/reference.ts +40 -0
  166. package/src/ui/shared.tsx +38 -0
  167. package/src/ui/transcriptModel.ts +151 -0
  168. package/src/ui/useChatScrollbar.ts +29 -0
  169. package/src/ui/useComposerAutosize.ts +54 -0
package/README.md CHANGED
@@ -1,3 +1,513 @@
1
- # Temporary Holding Version
1
+ # @xgspz/cli-ui
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 发布目标为 `@xgspz/cli-ui@0.6.14`,待手动发布;下面的新包导入示例在发布并核验后使用。旧命名空间的 0.6.13 已发布,已有业务依赖不会自动切换。批量发布入口为相邻 `clawbizx/scripts/publish-shared-packages.sh`,仅在隔离打包目录转换包名及引用,工作区源码包名和开发命令保持原样。npm 发布、center 文档部署和节点升级分别执行。
4
+
5
+ 相互联动的三个组件,支持中枢对话和完整 Operator 节点工作区。缺省参数使用中枢协议;`operator` 参数承接节点的完整任务与 Git 工作流。输入框默认显示附件、执行类型和智能体;后端缺少配置能力时明确说明并禁用设置,仍可按原有默认配置聊天。
6
+
7
+ | 组件 | 作用 |
8
+ | --- | --- |
9
+ | `<CliChat>` / `<cli-chat>` | 对话:实时显示执行过程,发消息、排队、停止、回答提问、结束对话 |
10
+ | `<CliMsgList>` / `<cli-msglist>` | 对话列表,按「需要你处理 / 进行中 / 已结束」分组 |
11
+ | `<CliBoard>` / `<cli-board>` | 右侧看板:工作区文件(预览、下载)、改动、任务浏览器(实时画面、接管 / 交还) |
12
+
13
+ 三个组件**相互独立**:传入参数就能单独运行,之后由组件接管。完整说明见中枢 API 文档的「前端组件」页签(`/api-docs#fe-start`)。
14
+
15
+ ## 快速开始
16
+
17
+ 0.6.13 的普通 `CliChat` 默认包含「+、执行类型、智能体」。右下只显示一个主按钮,使用 32px 视觉圆形图标,保留 44px 点击区域。AI 回复期间空草稿显示停止,有内容且允许排队时切换为箭头,排队成功清空后恢复停止;已排队消息显示在输入框上方;0.6.14 起每条右侧有「立即发送」「编辑」「删除」(点摘要看完整内容、复制)。「立即发送」打断当前回复,排队消息随即按顺序发出,只对排在最前面的一条、且有回复在执行时可用(中枢和节点都没有调整排队顺序的接口);「编辑」先撤回,撤回成功才放回输入框,接在未发送的草稿前面,文字、附件原件、粘贴的长文本和引用都保留,撤回失败时排队消息原样保留;「删除」从队列撤回。停止处理中保持停止忙碌状态,并阻止 Enter 提交。手机最小适配宽度为 375px;窄屏和运行态仍显示执行模式图标、智能体下拉箭头,触屏不保留悬停底色。可按需显式关闭:
18
+
19
+ ```tsx
20
+ <CliChat config={{ composer: { fileUpload: false, executionMode: false, agentSelector: false } }} />
21
+ ```
22
+
23
+ 只隐藏权限和智能体选择、保留附件按钮时,使用 `config={{ composer: { executionMode: false, agentSelector: false } }}`。两个开关均可单独关闭;关闭后不显示对应入口,不保留灰色占位。默认可操作需要配套服务提供执行设置接口,并且当前对话允许修改设置;一次性任务沿用任务设置,运行中和保存中暂时不可修改。
24
+
25
+ 所有 Chat 模式均不再显示消息下方的「引用」按钮;文件预览、看板引用、宿主追加引用和已有引用内容的展示与发送能力保留。
26
+
27
+ 完整 config 清单以中枢 API 文档的「界面配置」为准:普通 Chat/List 支持对应的完整配置。Operator Chat 当前仅消费 `composer: false`、`fileUpload`、`executionMode`、`agentSelector`;Operator List 不透传顶层 config。群聊由 `group` 适配器控制,不消费顶层 config;看板使用独立 props,不能用 Chat 的 config 配置。
28
+
29
+ `createCenterClient` 自动对接配套中枢的 `GET /api/execution-config`、`GET/PATCH /api/jobs/:id/execution-config?task_id=...`。升级时先在中枢执行随仓库提供的 Prisma migration,再部署支持原子执行配置的节点和中枢。组件包本身不会迁移数据库。配置保存只适用于普通对话;已有一次性任务的暂停/继续保持原协议。旧中枢可以继续聊天,但无法使用新配置选择。
30
+
31
+ 自定义 `ConversationBackend` 实现 `getExecutionConfig`、`setExecutionConfig` 即可使用默认菜单,无需自建 `composerToolbar`。能力列表由服务端返回,不在组件中写死模型或权限。新对话选择通过 `NewConversationOptions.executionConfig` 随首轮提交;自定义 `createConversation(text, options)` 回调需转发第二个参数中的设置。宿主提供的 `group` 是多发言人群聊,执行目标仍由群适配器管理。
32
+
33
+ ```tsx
34
+ import { CliChat, CliMsgList } from "@xgspz/cli-ui";
35
+ import "@xgspz/cli-ui/styles.css";
36
+
37
+ <CliChat jobId="job_xxx" /> // 打开已有对话(对话任务的第一个对话)
38
+ <CliChat jobId="job_xxx" taskId="task_yyy" /> // 对话任务里的某个对话;taskId="new" 新开一个
39
+ <CliChat jobId="job_batch" taskId="task_zzz" /> // 一次性子任务:运行中可排队输入,支持暂停 / 继续
40
+ <CliChat jobId={null} newConversation={{ title: "周报", businessId: "biz-1" }} /> // 新对话
41
+ <CliMsgList jobIds={["job_xxx"]} onSelect={setJobId} /> // 列表
42
+ ```
43
+
44
+ 列表只显示 `jobIds` 里的任务(加上本页新建的对话)。不传 `jobIds` 就是空列表:组件不会去中枢列出这个产品线令牌下的全部对话,哪些任务给哪个用户看由宿主决定。
45
+
46
+ 三栏想左右拖动改宽度,用 `CliSplit`(Web Component:`<cli-split>`)包起来:
47
+
48
+ ```tsx
49
+ <CliCenterProvider baseUrl="/api/center">
50
+ <CliSplit storageKey="strategy"> {/* 记住拖好的宽度;panes 可改每栏初始 / 最小 / 最大宽度 */}
51
+ <CliMsgList jobIds={ids} />
52
+ <CliChat />
53
+ <CliBoard />
54
+ </CliSplit>
55
+ </CliCenterProvider>
56
+ ```
57
+
58
+ 组件可按实际项目选择两种接入方式:
59
+
60
+ - **简易模式(非安全模式)**:直接传 `token`,例如 `<CliChat baseUrl="https://cli.xgjktech.com.cn" token="产品线 token" jobId="job_xxx" />`。令牌会暴露在浏览器配置和 API 请求中,页面使用者能够查看和复用。跨域时必须由入口/网关允许当前来源和 `Authorization` 等请求头;中枢应用当前未内置 CORS,不能只传 token 就绕过浏览器限制。
61
+ - **安全模式(推荐)**:前端不传产品线 token,业务后端/代理检查当前用户的 Job/Task 访问权并注入令牌。组件缺省请求同源 `/api/center`;接入方或 AI 可按现有技术栈复用业务后端、BFF、网关或服务端路由,不强制采用 Nginx,也不要求重复新建代理服务。
62
+
63
+ 下面 Nginx 片段只说明转发,假设已有入口完成业务用户授权;单独复制它不能替代鉴权:
64
+
65
+ ```nginx
66
+ location /api/center/ {
67
+ proxy_pass https://cli.xgjktech.com.cn/;
68
+ proxy_set_header Authorization "Bearer <产品线令牌>";
69
+ proxy_buffering off;
70
+ proxy_read_timeout 1h;
71
+ }
72
+ ```
73
+
74
+ ### 第三方页面(无需 React、无需构建)
75
+
76
+ `npm run build:embed` 生成的 `dist/` 放到静态目录:
77
+
78
+ ```html
79
+ <link rel="stylesheet" href="/static/cli-ui/cli-ui.css">
80
+ <script type="module" src="/static/cli-ui/cli-ui.js"></script>
81
+ <cli-chat base-url="/api/center" job-id="job_xxx"></cli-chat>
82
+ <!-- 或者 JS 挂载:CliUI.mountChat(el, { jobId, newConversation, onTurnEnd }) → { update, unmount } -->
83
+ ```
84
+
85
+ 示例见 `demo/embed.html`:先 `npm run build:embed`,再 `npm run demo`,然后打开 http://127.0.0.1:12791/embed.html。
86
+
87
+ ## 数据怎么流转
88
+
89
+ **缺省中枢适配不依赖 callback,也不依赖业务系统。** 它在浏览器里直接和中枢收发:
90
+
91
+ - **发消息**:新对话 `POST /api/jobs`(`mode: "conversation"`),之后每条 `POST /api/jobs/:id/messages`。
92
+ - **收消息**:`GET /api/jobs/:id/stream`(SSE 长连接)推送 Agent 的输出、工具调用、提问和状态变化。
93
+ - **只依赖两样东西**:能访问中枢,以及带上产品线令牌(安全模式由服务端注入,简易模式直接传入)。
94
+
95
+ `callback.url` 是中枢推给**业务后端**的,浏览器收不到,组件也用不到。只有后端想把每轮结果存下来、即使没人开着页面也要存时才需要配。页面上想在一轮结束时做点什么,用组件的 `onTurnEnd` / `onConversationEnd`:它们是组件从实时流里算出来的,不配 callback 也会触发,载荷字段与中枢回调体相同。
96
+
97
+ 谁来建对话:
98
+ - **组件自己建**:`<CliChat jobId={null} newConversation={{ title, businessId }} />`,建好后通过 `job_change` 事件把 job_id 交给页面。
99
+ - **业务后端建**(要带 callback 时):后端调 `POST /api/jobs`,前端只传 `jobId`,这样 callback 的密钥留在服务端。
100
+
101
+ ## 启用 Ego 与传递 YAML
102
+
103
+ 新建对话时显式配置 `browser: true`。`browserPlaybook` 是 YAML 正文,`browserPacing` 是节点每次浏览器操作前的随机等待范围:
104
+
105
+ ```tsx
106
+ const yaml = await selectedFile.text(); // 业务选择的 .yaml/.yml 文件,不传文件名或 URL
107
+ <CliChat jobId={null} newConversation={{
108
+ browser: true,
109
+ browserPlaybook: yaml,
110
+ browserPacing: { min_seconds: 1, max_seconds: 2 },
111
+ }} />
112
+ ```
113
+
114
+ 默认适配器把它们映射为创建接口 `tasks[0].browser_playbook` / `browser_pacing`;YAML 校验由任务中心和节点执行,格式不合法或缺少 `browser:true` 会显示错误。React、`CliUI.mountChat` 与 Web Component 的 `new-conversation` JSON 使用相同字段。Provider 的 `newConversation` 默认项也可在文件读取完成后更新,保持原有连接和当前选择。通过同一 Job 的“新对话”入口创建时,手册与间隔沿用首对话;已创建对话的普通消息不支持覆盖这些设置。
115
+
116
+ 加号也接受 `.yaml/.yml` 作为文本附件,保留缩进、空行和结尾换行。附件用于交给 AI 阅读,不会自动启用 Ego 或替换已登记的执行手册。浏览器密码、验证码等用户输入仍走输入卡片/浏览器接管流程。
117
+
118
+ ## 组合使用(列表 + 对话 + 看板联动)
119
+
120
+ 放进同一个 `CliCenterProvider`(Web Component 写同一个 `group`),三个组件互相通信、切换时一起变:列表点哪个对话,对话和看板就切到哪个;对话里新建 / 接续出的新对话,列表跟着加上并选中、看板跟过去;对话里点文件在看板里打开。
121
+
122
+ ```tsx
123
+ <CliCenterProvider baseUrl="/api/center"
124
+ selectedId={key} // 打开 / 切换到哪个对话("job_x" 或 "job_x#task_y");不传由列表点击决定
125
+ onEvent={(e) => { if (e.type === "select") setKey(e.jobId); if (e.type === "job_change") setKey(e.key); }}>
126
+ <CliMsgList jobIds={ids} />
127
+ <CliChat />
128
+ <CliBoard />
129
+ </CliCenterProvider>
130
+ ```
131
+
132
+ - Provider 里的组件不要再传 `jobId` / `taskId`(列表不传 `selectedId`):传了的组件只看自己那个,不跟着切。
133
+ - Web Component:同一个 `group`,不写 `job-id`;从外面打开某个对话给任意一个元素写 `selected-id="job_x"`,改它就一起切。
134
+ - 不用 Provider 时,完整联动仍需传同一个稳定的 `store`;仅同步 `jobId` / `selectedId` 不会共享文件打开和引用事件。页面可在 `onSelect`、`job_change` 中同步受控选择:
135
+
136
+ ```tsx
137
+ // React 组件内;账号变化时重建 store,并取消旧账号的业务订阅。
138
+ const store = useMemo(() => createCliStore({ baseUrl: "/api/center", namespace: accountId }), [accountId]);
139
+ <CliMsgList store={store} jobIds={ids} selectedId={key} onSelect={setKey} onNew={() => setKey(null)} />
140
+ <CliChat store={store} jobId={key} onEvent={(e) => e.type === "job_change" && setKey(e.key)} />
141
+ <CliBoard store={store} jobId={key} />
142
+ ```
143
+
144
+ ## 可选群消息适配
145
+
146
+ React 宿主可给同一个 `CliChat` 传 `group`,复用共享消息、输入框和引用交互。宿主提供消息并接收唯一的 `onSend`;正文中的多个 `@` 不会在组件内分流。每条消息的 `actor` 指定实际发言人;不传 `group` 时继续使用原有中枢/Operator 能力。
147
+
148
+ ```tsx
149
+ <CliChat store={store} group={{
150
+ scopeKey: `${accountId}:${jobId}`,
151
+ messages: [{ id: "m1", role: "assistant", content: "我来记录并协调。",
152
+ actor: { id: secretaryTaskId, name: "我的秘书" } }],
153
+ onSend: sendGroupMessage,
154
+ referenceKeys: [`${jobId}#${secretaryTaskId}`],
155
+ }} />
156
+ <CliBoard store={store} jobId={jobId} taskId={secretaryTaskId}
157
+ readOnly quoteToChat autoQuoteOpenFile={false} />
158
+ ```
159
+
160
+ `scopeKey` 应包含账号和群组身份,用于隔离草稿;发送失败保留当前正文与引用,切换群组时临时消息引用重置。群聊和看板使用同一个稳定 `store`,`referenceKeys` 只列出当前群允许引用的成员对话键;看板引用随下一条消息发送,失败恢复到原键。可选 `onOpenFile(path, message)` 沿用共享 Markdown 的本地文件链接与行内路径解析,宿主核对 `message.actor.id` 的授权后打开该成员的 `CliBoard`;用户消息和无明确发言人的文件不自动猜测工作区。群适配自身不打开单任务转录流,鉴权、消息订阅和秘书协调由宿主完成。`GroupChatAdapter`、`GroupChatMessage`、`GroupChatReference` 类型可从包入口导入。
161
+
162
+ ## 右侧看板
163
+
164
+ 核心是两块,都能单独放到任何地方(React、Web Component、无需构建的一体化 JS):
165
+
166
+ ```tsx
167
+ <CliFilePreview jobId="job_xxx" path="outputs/报告.md" /> // 文件预览 + 下载
168
+ <CliBrowserView jobId="job_xxx" /> // 浏览器实时画面 + 接管
169
+ ```
170
+
171
+ ```html
172
+ <cli-file-preview base-url="/api/center" job-id="job_xxx" path="outputs/报告.md"></cli-file-preview>
173
+ <cli-browser base-url="/api/center" job-id="job_xxx"></cli-browser>
174
+ <!-- 或 CliUI.mountFilePreview(el, { jobId, path }) / CliUI.mountBrowser(el, { jobId }) -->
175
+ ```
176
+
177
+ 宿主页面 CSP、触屏、窄容器等集成约定见 [BOARD-DESIGN.md](BOARD-DESIGN.md) 第 0 节。整个看板:
178
+
179
+ ```tsx
180
+ <CliBoard jobId="job_xxx" /> // 单独用
181
+
182
+ <CliCenterProvider> // 和对话联动:对话里点文件 / 「查看浏览器」在看板里打开
183
+ <CliChat />
184
+ <CliBoard collapsed={!open} onExpand={() => setOpen(true)} onCollapse={() => setOpen(false)}
185
+ extraTabs={[{ id: "term", label: "终端", render: ({ jobId, quote }) => <MyTerminal jobId={jobId} onQuote={(t) => quote({ kind: "terminal", label: "终端输出", text: t })} /> }]} />
186
+ </CliCenterProvider>
187
+ ```
188
+
189
+ - 页签:默认文件(目录、预览、下载)和受支持的浏览器;改动(diff,只读)默认关闭,可手动添加。`tabs={["files","changes"]}` 只设置初始打开的模块和顺序;用户仍可从「管理看板模块」打开浏览器。`availableTabs` 才限制允许使用的模块。所有模块都能单独关闭、重新打开,`tabs={[]}` 表示初始全部关闭。
190
+ - 预览:Markdown、代码、文本、CSV、图片、PDF、HTML(沙箱 iframe)、Word(.docx,mammoth 转 HTML 放进无脚本沙箱)。其他类型下载。
191
+ - 自动同步:每轮聊天流结束时,用短 HTTP 检查当前看板已打开文件与可见目录;内容变化后只补缺失块。打开文件、手动刷新、回到前台和网络恢复时也会检查。没有运行中或空闲周期轮询;`pollMs`、`fileWatchIntervalMs` 仅保留参数兼容,不再安排定时请求。后台只同步实际读过的预览范围,未打开文件不预取正文。
192
+ - 同一 `CliCenterProvider` / store 中,右侧看板展开时保持双栏;看板收起、通过 CSS 隐藏或未集成时,桌面聊天顶部显示对话、工作区、受支持的浏览器及已打开文件页签。手机或不超过 640px 的聊天容器使用「打开工作区」按钮唤起右侧抽屉,引用后返回对话继续输入。页签随当前对话切换,布局变化保留草稿和附件。
193
+ - 默认 `fileOpen="auto"` 优先使用上述工作区;显式 `fileOpen="modal"` 保留弹窗,`"link"` 保留链接,`"board"` 仅在看板打开。隐藏整个聊天头部时不显示内置页签/抽屉入口,自动模式仍可回退弹窗。宿主也可以 `store.openInBoard(jobId, { kind: "file", path })`;文件和浏览器能力仍由后端及 `availableTabs` 决定。
194
+ - 下载:单个文件;文件夹(页签右上角 / 文件夹行尾)在浏览器里打包成 ZIP,最多 2000 个文件、共 300 MB。
195
+ - 弱网 / 节点忙:读文件 30 秒没响应、或传输中 60 秒没收到数据就报错,预览里有「重试」;读超过 8 秒提示「网络较慢」。打包下载时每个文件失败会自动重试 3 次,一直读不到的跳过,其余照样打包,失败清单在压缩包里的 `_下载失败的文件.txt`。
196
+ - 放大:文件预览、浏览器画面都能放大到整页(Esc 还原);接管浏览器时自动放大。图片可缩放(Ctrl / ⌘ + 滚轮)。
197
+ - 引用到对话:文件预览、浏览器的「引用到对话」把它带进下一条消息(`store.addRef`),对话输入框上方显示、可去掉;发出时合成「看板上下文」附件。自定义页签(`extraTabs`)的 `quote` 同理——终端之类的交互以后都走这条路进对话。
198
+ - 多个文件页签:点开的文件各占一个页签(最多 8 个,再开关掉最早的),可关闭(× 或鼠标中键),按对话记在 sessionStorage,刷新后还在。
199
+ - 选中引用:在预览里选中文字浮出「引用到对话」(Markdown / 代码 / 文本 / 表格 / HTML / Word 及其源文件;HTML、Word 在沙箱 iframe 里,预览用 srcdoc 插一段选区小脚本报给宿主,页面自己的脚本仍不执行);在浏览器网页里复制(⌘C / Ctrl+C)后提示里也有「引用到对话」。
200
+ - 被动浏览背景:每条消息自动附带当前文件、仍打开的页签和最近40个实际访问文件的元信息;手机回聊天/收起看板后仍保留,并标记收起状态。后台默认带入,不在输入框上方展示摘要或勾选项。不会自动读取文件正文,也不替换手动引用。`CliChat passiveContext={false}`/`CliBoard autoQuoteOpenFile={false}` 关闭;节点 `board={{autoContext:false}}`。`workspace.context()` 读取完整浏览快照,`config.refs` 继续控制旧式引用的显示。
201
+ - 浏览器弱网:操作没送达时提示(不自动重发,免得点两次);接管时发了操作 8 秒画面没变化提示并给「重连画面」;按住鼠标切走窗口自动补发松开;地址栏自动补 `https://`。
202
+ - 改动(0.6.14):打开时读一次,之后每轮结束、对话结束、实时流断线重连后自动重读,也可手动刷新;连续信号合并为一次,不做周期轮询。刷新失败时保留上次结果并标明时间;节点暂时连不上、超时或网关错误(429/502/503/504)约 3、10、30 秒后自动再读,三次后只留「重试」。节点工作区已清理显示「任务工作区已被清理…」(HTTP 410),git 读取失败显示「读取改动失败:…」(HTTP 500),节点超时 504(`diff_timeout`/中枢 `node_timeout`),不再显示为「没有改动」;组件最多等 65 秒。diff 行按位置着色,被删除的 `-- 注释`、Markdown 的 `---`、新增的 `++x` 也显示为增删;代理或单点登录回 200 网页时报 `bad_response`(所有组件请求均如此),不停在「读取改动…」。这些错误码、查看改动不与智能体提交抢占 git 索引锁、超大改动按文件截断需要配套节点服务端与中枢。
203
+ - 数据都经中枢:`/api/jobs/:id/board`、`/files`、`/files/raw/<路径>`、`/diff`、`/browser`(+ `events` / `frames` / `input`)。
204
+ - 链路、交互、故障与重试的设计见 [BOARD-DESIGN.md](BOARD-DESIGN.md)。
205
+ - 普通工作区图片、PDF与HTML相对资源继续使用既有鉴权地址或中枢30分钟短期链接;上传原件的图片和Word读取使用授权分块缓存。HTML/Markdown正文预览、下载使用下文的增量协议;上传原件在看板沙箱预览或下载,不另开同源可执行页面。
206
+ - 预览大小与显示方式:
207
+ - HTML/Markdown最多100MiB;HTML默认原样预览,需完整重组当前修订,读取或排版期间可主动切换分段阅读。512KiB首段读取仅适用于分段阅读、Markdown和源码,继续阅读时按需补块。具体缓存、兼容和性能边界见下文“0.6.7 大文件原件与增量读取”。
208
+ - 其他文本、代码、日志、CSV保留32MiB读取上限;刷新按当前修订重新验证,不再依赖尾部重叠猜测整篇是否只追加。
209
+ - Markdown分段窗口显示;CSV表格先画2000行、可继续显示;代码512KiB以内高亮,更大的按纯文本显示;Word最大50MiB。
210
+
211
+ ## 对接业务数据
212
+
213
+ 页面通过 `store` 拿文件、追加背景、发消息;看板可以不显示(不放 `CliBoard`,或 `<cli-board hidden>`)。
214
+
215
+ ```tsx
216
+ const store = createCliStore({ baseUrl: "/api/center" }); // 或 Provider 里 useCliStore();Web Component:el.store
217
+ <CliCenterProvider store={store} onTurnEnd={async (p) => { // 每轮结束拿产出
218
+ const files = await store.workspace(p.job_id).listAll();
219
+ }}>…</CliCenterProvider>
220
+
221
+ const ws = store.workspace(); // 当前选中的对话;或 store.workspace("job_xxx");Web Component:el.workspace()
222
+ await ws.list(dir); await ws.listAll(dir);
223
+ await ws.readText(path); // ≤ 20 MB,GBK 自动转码
224
+ await ws.readFile(path); // Blob,≤ 500 MB(maxBytes 可调),更大的用 fileUrl / download
225
+ await ws.zip(dir); // { blob, name, files, failed }
226
+ await ws.download(path); await ws.downloadFolder(dir);
227
+ await ws.fileUrl(path, { download: true });
228
+ ws.openFile(); ws.openFiles(); ws.open(path); await ws.info();
229
+ ws.refresh(); // 每轮完成后请求一次已打开文件的增量检查;不创建连接或周期轮询
230
+
231
+ store.appendContext(jobId, text, { title, id }); // 随下一条消息带给智能体;null = 新对话的第一条
232
+ store.removeContext(jobId, id);
233
+ await store.send(jobId, text);
234
+ ```
235
+
236
+ - 追加的背景显示在输入框上方(「补充背景」),发出后在这个对话里一直有效;同一任务里新开的对话不会自动带上。创建时的项目背景用 `newConversation.workspace.context`。每条最长 20000 字,同 `id` 替换。
237
+ - 事件:`board_change`(看板切换文件)、`context`(补充背景变化 / 发出)、`board_ref`。
238
+ - Web Component 的方法在元素注册后才有:`await customElements.whenDefined("cli-chat")`。元素上还有 `store`、`appendContext`、`removeContext`,TypeScript 里 `document.querySelector("cli-board")` 自带类型。
239
+ - 服务端渲染:组件和 `@xgspz/cli-ui/elements` 都能在服务端导入 / 渲染(`defineCliElements()` 在服务端什么都不做);Next.js 在用到组件的文件写 `"use client"`。
240
+
241
+ ## 界面配置
242
+
243
+ ```tsx
244
+ <CliChat config={{
245
+ header: { title: true, status: true }, // 顶部栏缺省隐藏;header: true 全部显示
246
+ composer: { placeholder: "给 {agent} 发消息…" }, // composer: false 只读
247
+ transcript: { tools: false },
248
+ }} />
249
+ ```
250
+
251
+ cli-chat 的顶部栏(面包屑、标题、智能体、用量、模型、状态、结束对话)缺省全部隐藏,需要哪项写哪项。各种状态(未开始、进行中、已完成……)缺省都不显示:要显示打开 chat 的 `header.status`、`transcript.status`,list 的 `card.status`(卡片状态)、`grouped`(按状态分组)。所有配置项和缺省值都在 [`src/core/config.ts`](src/core/config.ts) 里,每一项都有注释。Web Component 上用 `config='{"header":{"title":true}}'`。
252
+
253
+ 已随项目上下文发送的初始提示缺省不渲染正文、头像或徽标;需要查看时设置 `transcript.initialPrompt: true`,`initialBadge` 只控制显示后的标记。后续消息不受影响,初始提示仍保留在原始记录和导出内容中。列表顶部手动刷新按钮缺省隐藏,需要时设 `header.refresh: true`,自动更新照常运行。
254
+
255
+ `header.export: true` 在标题栏操作区显示「导出」,点击直接下载 Markdown(初始上下文、消息和工具记录)。导出范围为已加载的记录;还有未加载历史时按钮显示「导出已加载」。输入区把附件、发送和执行控制分开显示。任务运行 / 暂停期间保留可编辑输入框,新消息排队执行;后端不支持发送或连接错误时仍保留输入框和草稿,并提示原因。宿主显式设置 `composer: false` 或 `readOnly` 时隐藏输入框。
256
+
257
+ 上传附件(`composer.fileUpload`,缺省开)与看板共用原件通道:HTML/HTM、MD/Markdown 上限100MiB;图片(PNG/JPEG/GIF/WebP)、Word(.docx)、Excel(.xlsx/.xlsm)、TXT/YAML/JSON/CSV/TSV及现有代码文本格式上限10MiB。原始字节、扩展名和文件名保留,上传完成不会触发AI;用户点击发送后,消息要求AI先读取附件再处理正文。PDF、压缩包和旧版.doc/.xls仍不在上传范围。自定义客户端未提供 `uploadDocument` 时保留旧文档转文字适配,内置适配器不退回转换或全量上传。
258
+
259
+ ## 事件
260
+
261
+ 都在浏览器里从实时流算出,不需要配 callback:
262
+
263
+ - `onTurnEnd(payload)`:一轮结束。字段与中枢的 `conversation_turn` 回调体相同(`seq`、`status`、`reply`、`asked`、`business_id`……)。
264
+ - `onConversationEnd(payload)`:对话结束,总在最后一个 `onTurnEnd` 之后。字段与 `conversation_closed` 相同。
265
+ - `onEvent(event)`:全部事件,另外还有 `job_change`(新建了对话,或接续出了新 job_id;`key` 是新对话的对话键)、`select`、`error`。
266
+
267
+ ### 一个对话任务里的几个对话
268
+
269
+ 对话任务(`job_id`)里可以开几个对话(各是一个子任务、一个 Agent 会话,共用同一节点和工作区,可以同时跑)。组件用**对话键**标识一个对话:
270
+
271
+ - `"job_xxx"`:对话任务的第一个对话(只有一个对话时就是它,和以前一样);
272
+ - `"job_xxx#task_yyy"`:里面指定的那个对话;
273
+ - `"job_xxx#new"`:在这个对话任务里新开一个对话(还没发第一条消息)。
274
+
275
+ `<CliChat jobId taskId>` 两个参数,或者把对话键整个交给 `jobId` 都可以。cli-msglist 里开了几个对话的任务按对话展开,卡片上的「+」新开对话;`onSelect(id, { jobId, taskId })` 的 `id` 就是对话键,直接交给 CliChat 即可。指定了对话时,「结束对话」只关这一个(`POST /api/jobs/:id/conversations/:taskId/close`),没指定时结束整个对话任务。工具函数 `convKey` / `parseConvKey` 从包里导出。
276
+
277
+ 一次性任务(含续派,`mode` 不是 `conversation`)也能交给 CliChat:显示子任务执行过程,运行中允许输入,发送后通过 `continueTask` 排到同一对话后面。`taskId` 指定看哪个子任务,不给看最近派发的那个(续派开出新的子任务后自动跟过去)。转录里中枢的派发标记行(`[CENTER-ATTEMPT…]`)去掉,完成核对(`[CENTER-CONFIRM]`)合成一行说明。需要中枢实时流支持 `?task=` 和续派接口。
278
+
279
+ `composer.stop` 缺省开启执行控制:普通对话仍停止当前轮次;一次性子任务通过 `pauseTask` / `resumeTask` 显示「暂停 / 继续」,只操作当前对话执行链中的子任务。暂停会中止当前执行,继续会重新派发该子任务,排队消息随后执行;不是进程原地冻结。自定义后端没有提供这两个可选方法时不显示暂停 / 继续按钮。
280
+
281
+ 例外是对话式续派的子任务(续派时 `mode: "conversation"`):用 `taskId` 打开它就是一个能一直聊下去的对话——有输入框,不显示「子任务已完成」之类的状态;每发一条 = 续派一条 `continue_from` 这个子任务的子任务(接在同一个 Agent 会话里),对话键不变。需要 0.4.0 + 同版本中枢。
282
+ - Web Component 上监听 `cli-turn-end` / `cli-conversation-end` / `cli-event` 事件。
283
+ - 旧名字 `onCallback` / `cli-callback` 仍然可用,等同于前两个合在一起。
284
+
285
+ ## 顺序与容错
286
+
287
+ - 同一对话的消息按发出顺序送到 center(一条一条发);新建对话只发一次(center 建对话不去重)。
288
+ - 结果通知带 `callback_seq`,对话结束总在最后一轮之后,顺序与 center 回调一致。
289
+ - 发送失败时内容留在输入框;撤回、停止、回答、结束失败时在顶部提示;排队卡住时气泡下写明原因。
290
+ - 实时流 45 秒没数据就重连,网络恢复、切回页面或窗口获得焦点时立即重连;404 停止重连并给出提示。
291
+ - 断线先悄悄重连(0.6.14):网关回收长连接、网络抖动、休眠唤醒、中枢重启造成的断线,15 秒内重新连上就不提示,`stream` 保持原来的 `live` / `connecting`,漏掉的事件由重连后的快照补齐;一直没恢复才变为 `"reconnecting"`,聊天顶部显示「实时连接中断,正在重连…」。新连接送来快照或数据才算恢复,连上就断的不会把重试次数清零。中枢停机(`end: server_shutdown`)按断线处理,不显示为对话结束。
292
+ - 没有无限轮询或无限重试(0.6.14):实时流断线约 2 分钟内重试 7 次(401 / 403 隔 30 秒最多 3 次),用完进入 `stream: "offline"`,顶部提示并给「重新连接」;网络恢复、回到页面、获得焦点、在该对话发消息或 `store.reconnect()` 时再连。浏览器画面连续失败 8 次、改动页签暂时性错误 3 次后同样停下。列表不再定时轮询(`pollMs` 仅兼容):打开着的对话由实时流更新,其余在回到前台、获得焦点、网络恢复时刷新,也可 `store.refreshAll()`。子代理侧栏只在父对话里有新动静或回到前台时补读。
293
+
294
+ ## 本地文件链接
295
+
296
+ Agent 回复里指向本地文件的链接(`report.md`、`src/a.ts:12`、`file:///…`),识别规则与 Operator 相同。缺省在当前工作区打开(右侧看板、顶部文件页签或手机抽屉),按住 ⌘/Ctrl 点按地址在新窗口打开;`fileUrl={(path, jobId) => 地址}` 可以自己给地址。
297
+
298
+ ## 开发
299
+
300
+ 所有源码统一在 **`/Users/spzhong/cli-task-center`**,后续只在这里维护:
301
+
302
+ | 目录 | 维护内容 |
303
+ | --- | --- |
304
+ | `packages/cli-ui` | 三组件、完整工作区界面、共享状态和适配;唯一组件实现 |
305
+ | `operator` | 配套执行节点、API、runner、浏览器与宿主;`packages/cli-ui` 仅是根组件的兼容软链接 |
306
+ | `backend` | 中枢调度、鉴权、代理、业务回调与 `public/api.html` 接入文档 |
307
+ | `examples/production-workbench` | 消费实际分发包的薄宿主与生产接口联调示例 |
308
+
309
+ 不再要求旧仓库并排放置。Operator 用 `file:../packages/cli-ui` 依赖根组件,先在组件目录运行 `npm ci`,再在 `operator` 运行 `npm ci`;React 保持宿主 peer dependency。旧外部发布脚本和链接已不作为维护入口。
310
+
311
+ ```bash
312
+ cd cli-task-center/packages/cli-ui
313
+ npm install # 第一次
314
+ npm test # 测试
315
+ npm run lint # 类型检查
316
+ npm run demo # 假中枢 + 演示页 http://localhost:12790(?mode=chat / ?mode=wired / elements.html;看板用 e2e-local.sh 的 ?mode=board,单独集成看 blocks.html?job=…,先 build:embed)
317
+ npm run css:sync # 从 src/operator.base.css 生成作用域样式
318
+ npm run build:embed # 第三方一体化产物 dist/cli-ui.js + cli-ui.css
319
+ bash demo/e2e-local.sh # 连真实 center 联调:本地 center + 假节点 + 一次性本地库,演示页代理过去
320
+ bash demo/e2e-local.sh stop
321
+ bash demo/e2e-real.sh # 真实整条链路:本地 center + 源码跑的真实节点 + 本机 ego lite(看板浏览器、故障演练;不跑智能体)
322
+ bash demo/e2e-real.sh stop
323
+
324
+ # 发布前打本地候选(在 packages/cli-ui 中;npm 发布由维护者执行)
325
+ npm pack
326
+ ```
327
+
328
+ 演示使用 `demo/mock-center.mjs` 的内存数据。Operator 的 Next 构建和 Docker 镜像现已包含组件包;不要恢复旧的 packages 全目录排除规则。
329
+
330
+
331
+ ## 完整 Operator 工程与唯一维护位置
332
+
333
+ ```tsx
334
+ "use client";
335
+ import { OperatorWorkspace } from "@xgspz/cli-ui/operator";
336
+ import "@xgspz/cli-ui/operator.css";
337
+ import "@xgspz/cli-ui/styles.css";
338
+ export default function App() { return <OperatorWorkspace />; }
339
+ ```
340
+
341
+ `OperatorWorkspace` 使用 `CliMsgList`、`CliChat`、`CliBoard` 的 `operator` 参数组合,三者由同一个 `useOrchestrator` 控制项目、选中任务、运行状态、配置、分支和事件连接。宿主只保留路由、服务端和以上入口。节点的完整客户端实现位于 `src/operator/node`;根工程原有 `app/orchestrator/*`、Markdown、图标、终端和 Git 面板路径均为兼容导出,不再维护另一份界面。
342
+
343
+ 直接使用 `operator` 参数时,宿主负责传入同一个项目/任务控制器及回调,并把三个组件放入 `.cli-ui` 容器,加载上述样式。完整工作区需要 Operator 的 `/api/*`、`/live` 和 PTY 服务;它不是脱离节点服务器的离线演示。
344
+
345
+ | 能力 | 中枢默认模式 | Operator 模式 |
346
+ | --- | --- | --- |
347
+ | 列表、对话、文件/浏览器看板联动 | 同一 `CliCenterProvider`/store | 同一工作区控制器和多路复用实时连接 |
348
+ | 附件、长文本、引用 | 原件上传与看板/消息引用,草稿暂存 | 同一原件上传通道,消息引用、草稿与来源恢复 |
349
+ | 模型、推理、权限/规划 | 不伪造中枢没有提供的配置接口 | 能力列表驱动,等待真实 PATCH 成功再允许发送 |
350
+ | `@` 多来源分支 | 中枢协议目前未提供独立 Git 派生接口 | 同项目已完成任务,每个来源一个独立 worktree/分支,固定源提交 |
351
+ | 源对话结尾 → 分支选择 → 子对话 | 不虚构分支数据 | 支持横向切换和递归派生,来源历史只读 |
352
+ | Git 同步、合并、PR、冲突修复 | 只读改动看板 | 保留原完整工作流 |
353
+ | 看板/列表、依赖、自动启动、建议、回顾、设置、认证、用量、终端和服务 | 由宿主业务提供 | 保留在完整工作区中 |
354
+
355
+ `src/operator.base.css` 是共享样式源,`src/operator.css` 是 `css:sync` 的生成结果;根工程 `app/globals.css` 只导入包的样式。迁移与备份清单见仓库的 `docs/CLI_UI_MERGE.md`。
356
+
357
+ `0.6.13` 已由用户发布;2026-11-09 已核实 npm `latest` 为 `0.6.13`,已发布版本不可覆盖重发。当前本地候选版本为 `0.6.14`,尚未发布;发布后可用 `npm view @xgspz/cli-ui@0.6.14 version` 核对 registry 可用性。发布前须通过组件测试、嵌入构建、宿主构建和相关浏览器回归;不要运行会连带发布其他共享包的批量脚本。配套 Skill/MCP 使用独立版本。
358
+
359
+
360
+ ### 文件、对话与浏览器看板联动
361
+
362
+ ```tsx
363
+ // 只设置初始可见状态;用户仍能手动打开文件、改动等模块。
364
+ <OperatorWorkspace board={{ tabs: ["browser"], defaultTab: "browser" }} />
365
+ // 中枢组件用相同规则;三个组件放在同一个 Provider 中。
366
+ <CliBoard tabs={["browser"]} defaultTab="browser" />
367
+ // 只有明确限制能力时才设置 availableTabs。
368
+ <CliBoard tabs={["browser"]} availableTabs={["browser", "files"]} />
369
+ ```
370
+
371
+ Web Component 对应 `tabs="browser"`、`available-tabs="browser,files"`;`tabs=""` 表示初始全部关闭。默认配置与用户手动开关分开,模块偏好按任务/配置保存在会话存储中。
372
+
373
+ 对话里的本地文件链接及文件形态的行内代码会打开当前工作区预览:桌面右侧看板展开时在右侧打开,收起时切到顶部文件页签;手机使用右侧抽屉。Ctrl/⌘ 点击仍可独立打开。来源历史文件使用来源任务的文件接口,不切换当前任务或分支。文件、选区、网页和网页复制文字可引用回同一个草稿,保留原有输入;手机引用后返回对话。
374
+
375
+ 浏览器关闭页签只隐藏模块和断开画面连接,不结束浏览器任务;重新打开后连接当前状态。「关闭浏览器」才调用实际关闭接口。新浏览器交接能唤起隐藏的模块;旧历史交接、已经消费的打开请求和其他任务的事件不会反复唤起。浏览器模块与独立窗口共用画面/输入实现,保留触摸、键盘/输入法、网页弹窗、剪贴板、重连、接管交还,并保留节点登录配置、弹出窗口和在节点屏幕显示操作。
376
+
377
+ ### v0.6.3 历史验收(当前版本见 CHANGELOG)
378
+
379
+ 组件 308 项测试、lint、嵌入构建,以及宿主生产构建已通过;宿主 873 项测试、24 项常规真实页面回归和 1 项真实 ego lite 看板完整流程通过。宿主默认跳过的 17 项底层浏览器/playbook 环境测试,本轮未重跑;此前单独执行的记录保留在验收包中。未使用付费模型账号测试,不把模拟驱动配置测试等同于所有真实 CLI 环境验证。
380
+
381
+ 该版本已在本地准备,当前执行账户未登录 npm,尚未发布。旧 UI 已归档,47 个兼容入口由宿主 `npm run check:components` 检查;今后 UI 改动只在组件包中维护。
382
+
383
+
384
+ ### 维护前提:组件为唯一标准
385
+
386
+ 开发、修复和发布均以三个公开组件为单位。React/Web Component/embed 统一调用 `src/components.tsx`;文件预览和浏览器独立窗口同样在本包维护。旧界面只留备份和转发,不再改旧工程实现。
387
+
388
+ 宿主普通 `dev`、`build`、`test` 会先执行来源审计:错误转发、新增包外 UI/样式、组件反向引用宿主/备份、分发入口绕过组件都会失败。审计自身有 7 项回退回归;本包的具体维护规则见仓库内 `packages/cli-ui/AGENTS.md`。
389
+
390
+
391
+ ### 高可用接入与恢复
392
+
393
+ 配置保存锁、上传中断恢复和结束确认均绑定任务/会话身份,切走再回来不会提前发送或误操作其他会话。看板正常断流自动重连,主动关闭停连;卸载最后实时消费者会释放连接,卸载文件模块取消 ZIP 下载。宿主事件回调异常记录在控制台,不能阻断组件终态更新或其他通知。
394
+
395
+ Web Component 组内唯一配置源的 URL、token、line、namespace 和新会话配置会完整继承,包括晚挂载或配置旋转;明确不同凭据各用独立 store。继承配置出现歧义时显示错误,调用方需要补齐连接属性,不能依赖“第一个组件”的身份。
396
+
397
+ 标准连接按身份指纹隔离草稿、文件页签及当前 UI,不把原始 token 写入存储键。同配置刷新可恢复,换凭据后旧内容不会自动附到新身份。Cookie 鉴权宿主应提供按账号区分的 namespace,并在账号切换后重建 Provider/client。自定义 client 对象应保持稳定;不同对象保守隔离,不能只凭 URL 推断其隐藏的认证身份。历史没有身份标签的草稿保留在原分区,不自动搬入新增的认证分区。
398
+
399
+ 完整问题与验证边界见仓库 `docs/CLI_UI_RELIABILITY.md`。本轮未进行生产长期浸泡、持续并发容量或付费模型账号验收;部署可用率需结合实际环境监测。
400
+
401
+
402
+ ### 文件通知 → 云端读取 → 业务交付
403
+
404
+ ```tsx
405
+ <CliCenterProvider store={store}
406
+ onFilesChange={event => handleChanges(store.workspace(event.jobId), event)}
407
+ onFilesWatch={status => showWatchStatus(status)}>
408
+ <CliMsgList /><CliChat /><CliBoard />
409
+ </CliCenterProvider>
410
+ ```
411
+
412
+ 也可只在 `CliBoard` 上监听 `onFilesChange`;或无 UI 地订阅:
413
+
414
+ ```ts
415
+ const ws = store.workspace("job_x#task_y");
416
+ const stop = ws.watch(async event => {
417
+ for (const change of event.changes) {
418
+ if (change.kind === "deleted") { removeBusinessFile(change.path); continue; }
419
+ const info = await ws.stat(change.path);
420
+ const blob = await ws.readFile(change.path); // 也可 readText()
421
+ receiveBusinessFile(info, blob);
422
+ }
423
+ }, { paths: ["reports/a.md"], onStatus: status => showWatchStatus(status) });
424
+ // 业务生命周期结束时 stop()。
425
+ const view = ws.context(); // current / opened / history / visible / sourceId
426
+ ```
427
+
428
+ 上述业务函数由集成方实现,组件不会自动上传到外部服务。通知是当前状态变化,读取可能遇到后续改写/删除,应处理错误与密集通知;通知中的 revision 是元数据批次标识,正文 manifest 的 SHA-256 revision 才标识不可变内容快照。
429
+
430
+ 节点使用 `createOperatorWorkspace(taskId)`(从 `@xgspz/cli-ui/operator` 导入),或 `<OperatorWorkspace board={{onFilesChange, onFilesWatch}} />`。接口包含 watch/refresh/stat/readText/readFile/context/open。`onBoardContextChange` 提供浏览状态;它与文件内容变化分开。
431
+
432
+ 独立节点 `CliBoard` 只旁听组件现有任务消息流的轮末与重连快照,不另开连接或服务端订阅。宿主自行管理聊天流时,应在每轮结束(包括停止/失败)和重连完成后调用 `createOperatorWorkspace(taskId).refresh()`;中枢对应 `store.workspace(conversationKey).refresh()`。该方法按已订阅范围检查,并同步已打开文件此前读取过的内容范围;节点会包括已打开的来源任务文件。多个请求会合并,在途检查期间的新轮末会补查一次。自动事件调用 `refresh()`,用户点击重试才调用 `refresh({ manual: true })`,后者重新开始有限重试预算,但仍遵守服务端 `Retry-After`。没有看板时,先用 `ws.watch(listener, { paths: [...] })` 指定范围;没有订阅范围时不会发起文件请求。
433
+
434
+ Web Component 发出 `cli-files-change`、`cli-files-watch`、`cli-board-context`,detail 与 React 事件一致;也可 `el.workspace(id).watch(...)`。通用 `cli-event` 会包含同类事件,不要重复处理两个入口。`watch-files="false"` 关闭自动订阅,`passive-context="false"` 关闭聊天自动背景。
435
+
436
+ 事件含 `jobId`、`changes[{kind,path,file?,previous?}]`、`revision?`、`mode`、`coverage`、`complete`;创建/修改/删除分别为 created/modified/deleted,初始快照不冒充全部新建。多个组件与业务监听共享短请求调度,最后取消时释放;每个订阅者的显式路径独立释放。
437
+
438
+ 0.6.7 的文件同步由事件触发,使用会结束的短 HTTP;没有文件 SSE、WebSocket、长轮询或周期轮询。每轮聊天流结束(含失败、停止)、流重连后的快照、打开/手动刷新、页面恢复会触发一次检查;普通输出片段不触发。相邻事件合并,在途检查期间再结束一轮会补查一次。只检查当前看板已打开文件、当前目录可见行和宿主显式订阅范围;已预览的多个文件逐个核对修订并同步其已读范围,未变化文件不下载正文,未读文件不预取。隐藏页面停止文件检查。网络类错误最多退避重试3次,权限错误等待显式事件。旧返回值 `mode=poll, coverage=listed-files, complete=false` 仅为兼容的短 HTTP 传输标签,不代表定时轮询,也不是整个工作区的审计日志。无 UI 订阅可通过 `ws.watch(listener, { paths: ["reports/a.md"], dirs: ["reports"] })` 指定范围。目录更新仍按页拉取元数据;不批量取正文。外部独立修改若没有传入结束/更新事件,只能在重新打开、手动刷新或页面恢复时发现。聊天和远程浏览器现有连接保持不变。
439
+
440
+ 列表和聊天可消费中枢任务摘要/实时 task 的可选 `awaiting_input`:`true` 表示节点确认需要人工处理(含浏览器验证码和输入卡片),`false` 表示已确认不等待,`null` 或缺省表示没有有效观测。正在执行的回合也可能需要人工处理,此时显示“需要你处理”,不会把任务误标为完成或暂停。中枢升级后从已有节点对账取得此状态;旧中枢仍兼容原有提问状态。新建/接续的迟到响应与旧操作报错不会抢走用户已切换到的任务。
441
+
442
+ ### v0.6.4 接入加固
443
+
444
+ 多个看板按最近实际操作选择当前文件,历史/远程 metadata 同步不抢占活动看板。文件和浏览状态的业务事件使用独立副本;监听可区分永久错误、认证/限流和暂时断线,保留 WebSocket → SSE 恢复。客户端文件元信息跟踪上限为 100000;超限明确 `complete=false`,完整且未超限快照才恢复。
445
+
446
+ `workspace.readFile()` 对自定义后端也校验实际字节数并及时响应取消,后端仍应正确实现 AbortSignal 以释放内部资源。浏览器重连、接管交还和切换任务不等待或重放旧会话输入。当前看板为只读预览,文件编辑由 AI/服务端执行,再通过事件刷新和交付给业务方。
447
+
448
+ 当前本地候选版本为 `0.6.14`(改动页签轮末刷新与读取失败提示,尚未发布),已发布组件为 `0.6.13`(2026-11-09 已核验 npm),承接已发布的 `0.6.12`;各版本修复见随包 `CHANGELOG.md`,子代理功能见 [0.6.12 发布说明](../../docs/CLI_UI_0612_RELEASE_2026-11-06.md)。0.6.10 历史验收见 [版本验收](../../docs/CLI_UI_0610_VERSION_VALIDATION_2026-11-06.md),三项 Chat 修复见 [修复验收](../../docs/CLI_UI_069_CHAT_FIX_VALIDATION_2026-11-06.md),默认控件与执行配置历史验收见 [0.6.8 验收记录](../../docs/CLI_UI_068_DEFAULT_CONTROLS_VALIDATION_2026-11-06.md);原件上传历史验收见 `docs/CLI_UI_UNIFIED_UPLOAD_VALIDATION_2026-11-05.md`。本地打包不等于 npm 或生产服务已更新。
449
+
450
+ ### v0.6.5 文件和业务交付加固
451
+
452
+ 文件预览按 backend/任务/路径隔离;下载和预览读取检查实际字节数,取消 ZIP 不保存迟到结果。部分失败时 `zip()`/`downloadFolder()` 的可选 `failedListName` 返回实际报告名,避让同名用户文件。
453
+
454
+ 原生文件通知的可选 `FileEntry.changeToken` 用于重连对账,可识别同大小并恢复 mtime 的改写;需同步部署支持该字段的节点服务端。它不是不可变内容版本或审计日志。
455
+
456
+ 看板与自动背景一起更新后通知业务,回调内继续导航保持顺序;显式选区引用携带来源任务身份。浏览器复制引用固定复制时页面,取消拖动、控制权切换、弹窗错误和异步帧解码均保持正确状态。
457
+
458
+
459
+ ### 0.6.7 大文件原件与增量读取(需配套服务)
460
+
461
+ HTML/HTM、MD/Markdown 原件最多 **100 MiB(104857600字节)**,按1MiB分片上传、完成后核验SHA-256;保留原始类型、文件名和字节,不把整份文档放入聊天消息。中枢新对话先按账号/产品线/草稿暂存,在任务派发前认领并传到节点;其他现有支持类型原件上限10MiB,文件名不超过240个UTF-8字节。Word支持现有预览,Excel原件可下载或交给Agent读取,本次未增加表格预览器。
462
+
463
+ 看板「+ → 上传文件」仅对已有Agent工作区开放:上传至当前会话的「输入文件」,成功自动打开文件页签,只保存文件,不发送消息或启动AI。当前打开文件按既有看板背景规则随之后的消息带入,也可选区引用。Chat上传同样保存原件并打开页签,另保留待发送附件;点击发送后才要求AI先读附件。新对话Chat先显示原件暂存,首次发送时才落到节点。
464
+
465
+ 两处共享有界上传队列、实际接收字节进度、校验阶段、取消和失败重试。看板上传按当前任务定向,即使浏览的是来源任务目录也不会上传到来源任务;切任务或卸载取消未完成上传。刷新后未完成项需重新选择本地文件,浏览器不会持久保存File对象。关闭预览后,可从工作区「输入文件」重新打开,目录仍按200项分页,不预加载正文。
466
+
467
+ 输入文件沿用节点现有任务附件目录(Git工作树外),不复制第二份;看板、附件标记和Agent指向同一原件。执行模式只授予当前任务输入目录写权限,计划模式保持只读;已运行的Agent进程不会热修改隔离规则,下次正常启动轮次采用新规则。服务端更新后,旧图片/文本附件地址取消immutable缓存,避免同一路径编辑后仍显示旧版本。
468
+
469
+ 看板预览、手动刷新、重新打开和 `workspace.readText/readFile` 的HTML/Markdown读取使用同一套修订同步:先授权查询manifest,再读取本地没有的内容块。节点按内容分块(16–256KiB,平均目标64KiB),中间插入、删除或跨越中间修订都可复用相同块;批次最多1MiB、同步器并发最多3。每块校验,完整文件重组再校验总hash,失败保留上一份完整内容;401/403撤下旧预览。选区引用携带复制时的修订和来源路径。
470
+
471
+ 正文块保存在IndexedDB,默认256MiB预算并按LRU淘汰;无持久存储或配额不足时使用会话缓存。缓存已存在且文件未改时正文传输为0;首次打开、缓存被浏览器/预算清除或整文件重写,仍可能需要下载大部分或全部正文。每次仍先校验当前权限,缓存不代替鉴权。宿主明确注销时可用 `clearFileSyncNamespace(backend.fileSync, namespace)` 清除指定授权空间;任意宿主的登录生命周期不会由组件自行推断。
472
+
473
+ 缓存打开、读写或事务停滞最多等待5秒后降级到会话缓存,存储配额估算最多等待1秒;这些是单次操作的截止时间,不是轮询。取消预览无需等待缓存恢复。聊天轮末核对不会复用该事件之前已经发出的旧清单请求,迟到旧修订不回退最新缓存指针,必要时触发一次预览纠正;损坏的重复内容块修复后复用同一份校验结果,避免反复下载。
474
+
475
+ 大文件反复修订不依赖逐版补丁历史:拿到当前授权清单后,用本地已校验块直接重组最新修订;弱网漏过多轮、旧节点快照被淘汰后仍走相同流程。失败请求中已完整校验的块可以保留,半响应或校验失败的块不会进入缓存;未完成修订不替换上次完整内容。文件页签的刷新按钮及更多菜单中的“刷新内容”仅刷新该文件,重新核对最新清单,不唤醒其他文件;聊天轮末仍同步已打开且预览过的文件范围。
476
+
477
+ 并发采用有界队列:同一客户端传输的manifest和分块请求各最多3个,正文组装最多2个,后台预览同步串行;元数据核对每个backend最多4个,同一fetcher的上传及哈希串行。每个客户端队列最多128项、最多等待180秒,取消排队立即移除;已开始的工作直到真正结束才释放名额,避免宿主忽略取消时突破限制。排队不提前分配100MiB组装缓冲区。
478
+
479
+ 配套服务端也限制容量:中枢文件代理每进程最多32个活跃请求、每节点8个,最多128个排队、同节点64个;独立节点每进程8个活跃、64个排队。服务端排队超过5秒或队满返回429与`Retry-After`,二进制响应传输结束或取消后才释放名额。节点快照构建最多2个同时执行、在途与排队合计32个;同文件构建合并,缓存仍受1GiB和过期时间约束。中枢原件暂存每进程16个活跃、同身份4个,排队最多128个、同身份32个。
480
+
481
+ 暂时断网、408/429和明确可恢复的5xx采用有限次数、带随机抖动的退避,并尊重`Retry-After`;协议损坏、校验失败和权限错误不盲目重试。请求层与元数据检查层分别最多追加3次,整个链路可能包含两层预算;202构建/校验中的等待也有180秒上限。检查预算耗尽且没有新事件时不再安排检查;冷却期间或最后一次失败期间到达的新事件会保留并合并,在至少30秒冷却及服务端`Retry-After`结束后补查一次,仍有有限重试预算。单次事件可等待超过180秒的服务端冷却,不建立周期轮询,也不截短服务端冷却;超过JavaScript定时器安全上限的提示保留错误,等待后续真实事件。多文件中单个永久错误不阻断其他文件的暂时故障恢复,并遵守这些暂时错误中最长的冷却。队满、等待超时和恢复失败保留明确错误及人工重试入口。容量限制按进程/传输生效,不是分布式配额,也不承诺100份100MiB冷文件同时即时完成;首次下载、缓存淘汰和复杂HTML渲染仍受实际网络、磁盘与设备内存限制。
482
+
483
+ 目录首屏200项,滚动/点击才取下一页,显示行数最多80;`workspace.listPage(dir,{cursor,limit,signal})` 返回 `nextCursor`,适合业务列表。`list()` / `listAll()` / ZIP 是用户显式全量列目录操作,逐页读取并保留数量/内存限制,不会自动预取正文。游标失效会提示重读目录,不返回混合页面。
484
+
485
+ Markdown与HTML源码先读取512KiB,继续阅读按需扩大范围(单次增量至多8MiB),只下载新块。Markdown每片至多16Ki字符,并限制表格行、单元格和标记复杂度;一次事件循环只启用一片,离屏正文卸载,32片合为一组占位。表格续页保留表头,长代码分段显示且不高亮。小于4Ki且结构简单的Markdown和小于16Ki源码直接显示,不引入排版等待。片段解析异常只回退该片原文;超长行、复杂跨段脚注等会明确提示简化显示,普通引用式链接复用已加载文本定义。每次扩大范围仍需本地重组/校验当前已读前缀。
486
+
487
+ HTML默认直接原样预览,包括超过2MiB或大小未知的文件;通过同一增量协议完整重组当前修订。用户可在读取或排版期间主动切换“分段阅读”。分段阅读复用同一缓存与修订,先读512KiB;正文块边界可能使实际下载略大。静态流式解析每批8Ki字符、约8ms让出主线程,只保留目标页和最后页,每页最多64块/16Ki正文。保留标题、段落、代码、列表和表格文字,不执行原件脚本、不应用原件CSS、不自动加载图片;布局因此简化,选中引用仍携带原文件修订。翻页只重新整理本地已读前缀,需要后续内容才继续取缺块。嵌套超过32层、超长未完成标记(例如大于约64Ki的内嵌图片属性)等返回明确提示,可切源码或原样预览;不能将该受限阅读模式视为任意HTML的完整排版替代。
488
+
489
+ 文件读取和HTML整理均可暂停、继续,旧完整内容在网络失败时保留;读取与排版各自管理取消信号,翻页不会覆盖仍在下载的请求。原样HTML通过受限引用桥在DOM就绪及绘制机会后报告状态,排版12秒未收到就提示切换分段阅读;该提示不是周期轮询。iframe自身的极端排版可能阻塞所在浏览器渲染进程,此时提示也可能延迟,计时器不能强制中断它或保证主页面始终响应。
490
+
491
+ HTML完整原貌预览要重组完整文档;网络只取缺块,更新后在沙箱iframe中替换完整修订并尽量恢复滚动。任意100MiB复杂DOM在手机上的排版成本不能由增量传输消除,超大样本的桌面测试不等于所有手机设备的性能保证。HTML自身脚本继续被阻止,相对资源按原有鉴权文件链路加载。
492
+
493
+ **需要配套升级中枢和独立节点服务端的 `file-sync/*` 接口。** 内置适配器遇到旧服务端会明确报不支持,不静默改回重复全量拉取。自定义BoardBackend未提供fileSync时保留原来的有限读取兼容行为。本次没有发布npm、部署或数据库变更。
494
+
495
+ ### 0.6.12:CLI 子代理过程
496
+
497
+ 主 Chat 中点击 CLI 子代理卡片,在右侧打开该子代理的 Chat,查看公开消息、工具调用、结果与状态;支持实时更新和历史读取。关闭侧栏不停止任务,也不会清除主 Chat 草稿。窄屏使用模态侧栏。Center 业务子任务仍切换到各自会话,不使用该侧栏。
498
+
499
+ 需要配套 Center 子代理代理接口及 Operator 子代理记录接口。Claude、Codex、Grok 的可展示范围取决于实际协议,Pi 需要子代理扩展;未提供的过程不会伪造成完整记录。本轮未提供直接向子代理发送消息或重启的操作。
500
+
501
+ ### 0.6.14(本地候选):改动页签、排队操作、无轮询与输入区布局
502
+
503
+ 看板「改动」页签每轮结束自动重读,读取失败保留上次结果并说明原因;排队消息右侧为「立即发送」「编辑」「删除」;组件内没有无限轮询或无限重试(见上文「可靠性」)。输入框文字区仍默认两行、最多八行,收紧上下留白与工具栏高度后 PC 端整个输入框约 102px(原 118px),加号与发送按钮保留 44px 点击区。PC 端(视口 ≥ 761px)聊天正文与输入区两侧留白再翻一倍:64px,窄聊天栏(聊天列 ≤ 640px)48px;手机端保持 16 / 12px。
504
+
505
+ 节点模式「项目上下文」里删除项目改为两步:先向节点询问删除范围(`GET /api/projects/:id/delete-preview`),平台为某个大任务建的项目会列出将一并删除的目录(任务仓库、各子任务工作副本、附件),大任务的资料目录(`material_root`)注明不删;确认时带上看到的根目录(`DELETE /api/projects/:id`,请求体 `{ "delete_storage": "<根目录>" }`)。仍有任务在运行等原因删不掉时,目录和项目都保留并在弹窗里说明原因。用户自己的仓库与之前一样只删记录、不动磁盘;没有预览接口的旧节点按原来的方式删除。需要配套节点服务端。
506
+
507
+ ### 0.6.13:文件读取与看板修复
508
+
509
+ 浏览器缺少 `crypto.subtle` 时,主线程和 Worker 均使用 SHA-256 兼容实现,继续校验内容块与完整文件。单个文件的目录越权仅影响该文件;真正的登录失效或任务级 401/403 仍清理对应工作区。刷新当前文件显示忙碌状态,只读取该文件的最新修订,未变内容继续复用。
510
+
511
+ 配套 Operator 节点统一原件与增量接口的路径和授权:完整路径直接解析,相对路径按对应任务工作目录解析;读取明确授权的来源目录与附件,保留来源完整路径,并在授权撤回后重新校验缓存。资料下载脚本应写入当前任务工作区,例如 `inputs/materials/paths.json`;拼接完整路径不会扩大授权。这些节点服务端修复不随 npm 包自动部署。
512
+
513
+ 通用及节点看板移除内置收起按钮,保留宿主控制与手机返回方式;Operator 右侧分隔线按实际看板边界计算宽度,可左右拖动及双击还原。PC Web 聊天正文与输入区左右留白翻倍:常规 32px、桌面窄聊天栏 24px;手机间距保持原值。