fix: keep supervisor reviews running until completion
Remove elapsed execution-budget pauses from extraction and navigation so long reviews no longer require repeated manual continuation. Remove the pause-duration setting and update activity copy and design documentation. Preserve explicit pause, saved batches, model timeouts and concurrency limits. Validation: recorded 13 focused tests, typecheck and targeted ESLint passed; staged diff check passed. Tests were not rerun for this commit. Release note: 监督回顾现在持续分批处理至完成,不再每隔固定时长自动暂停;仍可主动暂停并从已保存进度继续。
This commit is contained in:
+1
-1
@@ -722,7 +722,7 @@ GoodBuddy 是可调整窗口大小的桌面应用。响应式设计优先保证
|
||||
- 状态卡片使用统一状态令牌,不只依赖颜色。
|
||||
- 活动记录先显示运行状态、时间、范围及实际阶段,恢复操作紧邻进度。原始错误默认折叠,展开后在有界面板内滚动并可选择复制;配置归入次要详情。任务层级只展示可查询的记录,缺失阶段明确标注,不能用剩余来源为零推断发布成功。具体阶段与分页规则见[监督者活动设计](docs/features/conversation-supervision/ui-design.md#活动)。
|
||||
- 自动监督的当前状态下显示计划列表,仅保留列表的一个创建按钮;无计划时使用列表的未配置说明,不叠加第二个空态或创建入口。创建与编辑复用同一份表单及共享 Modal 焦点、背景隔离和遮罩样式;未保存关闭须确认,保存失败保留草稿,编辑暂停计划不恢复运行。“执行记录”进入按该计划 ID 筛选的活动页,活动页不重复显示心跳审计列表。
|
||||
- 设置只展示模型超时与并发、分页、分批和执行预算,不含计划、报告、审计或嵌套页签,不显示整页全局徽标或独立刷新行。
|
||||
- 设置只展示模型超时与并发、分页、分批和单次响应容量,不含计划、报告、审计或嵌套页签,不显示整页全局徽标或独立刷新行。
|
||||
- 监督者的设置、帮助、空态及错误文案不得向终端用户展示内部路线图、交付进度或设计与实现缺项;只说明当前行为、适用范围和可执行的下一步。不得因此夸大为提供商全局限流或完整语义识别;真实的运行失败、超时、暂停、来源失效及恢复操作必须保留。
|
||||
- 智能心跳菜单入口是完整配置的权威位置;任务中心和设置中心不得复制同一 CRUD 表单。
|
||||
- 分区记忆协同与唤起条件尚未实现,不得显示占位入口或平行的记忆类型。
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
| [实施进度](./progress.md) | 已验证实现、验证证据和当前边界 |
|
||||
| [故事线图谱设计](../story-graph/prd.md) | 图谱对象、环绕时间轴与实体交互 |
|
||||
|
||||
生产入口已接通完整来源分页、叶子持久化、预算暂停、活动页继续及批次详情;设置可调页大小、批次正文/消息上限、软预算、两个模型超时和共享并发。实际合同及限制见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界)。真实 DeepSeek 验证已覆盖一个完整的 27 条消息会话;较早的 129 条消息实验仍未完成。两次实验共 5 次付费请求,详情见进度。原始 AbortError 原因尚未由日志确定。
|
||||
生产入口已接通完整来源分页、叶子持久化、持续处理至完成、活动页主动暂停/继续及批次详情;设置可调页大小、批次正文/消息上限、单次响应容量、两个模型超时和共享并发。实际合同及限制见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界)。真实 DeepSeek 验证已覆盖一个完整的 27 条消息会话;较早的 129 条消息实验仍未完成。两次实验共 5 次付费请求,详情见进度。原始 AbortError 原因尚未由日志确定。
|
||||
|
||||
实施状态以代码和[实施进度](./progress.md)为准。逻辑待定项见[逻辑设计](./logic-design.md),架构边界见[技术设计](./technical-design.md)。现有心跳行为见[智能心跳](../smart-heartbeat/README.md),原型范围见[Story Graph 演示](../story-graph/README.md)。
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
|
||||
## 已验证
|
||||
|
||||
### 2026-09-26 移除回顾自动时间预算暂停
|
||||
|
||||
对应 FR-S4、FR-S6、FR-S10。生产服务移除提取循环和导航合并中的整次执行期限判断,持续处理至完成;已保存运行中的 `executionSeconds` 继续兼容读取,但不再控制暂停。设置页移除该控件,中英文帮助和活动配置改为持续处理说明。单次模型超时、共享并发、逐批保存、主动暂停及失败续跑保持原路径,具体合同见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界)。主动暂停仍中断在途请求,仅保留此前已保存批次。
|
||||
|
||||
`npx vitest run src/main/assistant/supervision-review.test.ts src/renderer/src/SupervisionReviewSettings.test.tsx`:13 项通过。受控时钟超过保存的执行预算后,三个叶子及两个导航合并仍完成并发布;另验证主动暂停后复用成功批次和原分块配置、中英文设置不再出现整理时长控件。测试使用隔离 SQLite 和可控摘要器,不代表真实模型长时间运行实测。
|
||||
|
||||
`npm run typecheck`、六个修改 TS/TSX 文件的定向 ESLint 和 `git diff --check` 通过。未运行全量测试,真实模型调用 0 次。核对 Main 的生产工厂接线,监督使用不携带项目的 `resolveRequestRuntime({ workMode: 'ask' })`;本次仅改变桌面回顾编排,不修改 Agent、远端 Runtime 或桌面到 Agent 协议。未提交或推送。
|
||||
|
||||
|
||||
### 2026-09-24 长摘要与内容区对齐
|
||||
|
||||
针对全局七天回顾的 `summary max 2000` 报错,代码确认导航输出 schema 的 `max(2000)`、服务层对叶子及导航的二次 2000 字符检查,以及模型提示词中的同值要求。另发现监督旧摘要通过 SQL `substr` 和 JavaScript `slice` 截断。以上监督路径限制已移除;生成内容和事实集合的合同见[生产调度合同](./review-scheduling-design.md#已接入的调度与存储)。未读取本次 16:03 失败的原始模型响应,不能据此声明该运行的具体失败节点。
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
日期:2026-09-24。状态:生产分块、持久化续跑、设置及活动入口已接线;27 条消息的完整会话通过真实模型验证,较早的 129 条消息实验仍未完成。当前实现以第 0 节为准,第 1 节保留改造前基线,第 2 至 11 节保留目标设计,第 12 节为早期隔离原型证据。
|
||||
|
||||
本文针对全局七天 `supervision:run` 出现 `AbortError` 的反馈,设计完整分页、证据分块、模型任务调度和逐层汇总。用户已明确要求预算耗尽或失败后保留成功批次及位置,重启后可继续,不重复计算成功叶子。生产接线复用 `SupervisorService`、现有数据库及 `review_checkpoints`;不另建定时调度器。schema 47 的实现与目标差异见第 0 节。
|
||||
本文针对全局七天 `supervision:run` 出现 `AbortError` 的反馈,设计完整分页、证据分块、模型任务调度和逐层汇总。用户已明确要求主动暂停或失败后保留成功批次及位置,重启后可继续,不重复计算成功叶子。生产接线复用 `SupervisorService`、现有数据库及 `review_checkpoints`;不另建定时调度器。schema 47 的实现与目标差异见第 0 节。
|
||||
|
||||
产品范围仍由[监督者产品设计](./supervisor-prd.md)定义;本设计对应 FR-S4、FR-S6、FR-S10 及 US-S12、US-S26、US-S27。既有规则见[逻辑设计](./logic-design.md#自动增量与手动重看),实现架构见[技术设计](./technical-design.md),已验证事实见[实施进度](./progress.md)。第 0 节以当前代码为准,其余目标须逐项核对实现状态;大范围回顾仍需独立性能验证。
|
||||
|
||||
2026-09-26 修订:回顾持续分页、提取和汇总至完成,不再按整次运行的时间预算自动暂停。下文历史草案中的执行软预算、整次执行硬期限及按时间预算续跑方案已撤销;单次模型超时、并发限制、用户暂停和失败续跑仍保留。
|
||||
|
||||
## 0. 生产接线与剩余边界
|
||||
|
||||
Main 的手动 IPC 和心跳完成回调均使用 `createProductionSupervisorService`,该工厂连接 `SupervisorService`、`SupervisionReviewStore`、共享模型池及实际 ask Runtime。生产路径不再调用旧的 20 会话 × 20 消息 collector,也不使用旧服务分支的 100 条 / 48,000 字符裁剪。旧的有界服务注入方式仍供原有测试和实验脚本使用;心跳报告本身继续使用原报告预算。
|
||||
@@ -16,7 +18,7 @@ schema 47 增加运行配置、来源清单、成功批次、导航节点四张
|
||||
|
||||
派发按项目及项目内会话轮转,每个会话同一轮至多一个批次,会话内按 sequence/source 排序。批次跨数据库页累计;长消息未装入部分保留待处理。每片最多 8,000 UTF-16 单元、每批最多 50 片,均为封块条件,不是范围上限。位置采用零基、左闭右开的 Unicode 码点;消息 locator 保存 messageId、conversationId、projectId、role、sequence、source、revision、start、end、length。知识引用另保留原 locator 及所属消息,任务使用当前状态和区间内创建/完成时间。
|
||||
|
||||
成功叶子的输出、原文片段和连续 offset 在同一事务提交。失败或软预算到期保留此前批次。继续操作使用同一 run ID、时间范围和分块配置;重启后可继续。手动运行不读写自动 checkpoint。自动运行先恢复同 scope 的未完成 run;仅在全部来源连续覆盖、导航完成、最终发布事务成功时写入 `stage=supervisor` 的 checkpoint。空范围无需模型。
|
||||
成功叶子的输出、原文片段和连续 offset 在同一事务提交。失败或用户主动暂停保留此前批次。继续操作使用同一 run ID、时间范围和分块配置;重启后可继续。手动运行不读写自动 checkpoint。自动运行先恢复同 scope 的未完成 run;仅在全部来源连续覆盖、导航完成、最终发布事务成功时写入 `stage=supervisor` 的 checkpoint。空范围无需模型。
|
||||
|
||||
叶子输出仍使用现有事件、实体、变化和关系契约,并校验引用与候选身份。会话、项目、scope 使用二叉递归合并导航摘要,单子透传,总共 N 个叶子至多 N-1 次合并。已成功导航节点按孩子 ID 保存,发布失败后可复用。叶子、导航及最终结果的生成文本不设字段字符上限,事实数组不设独立数量上限;导航节点仍不得生成替代叶子的事件或实体。发布事务逐页读取叶子,保存既有历史/图谱结构,保持历史定位、来源及用户确认状态;完整叶子同时通过独立批次接口分页读取。
|
||||
|
||||
@@ -24,7 +26,7 @@ schema 47 增加运行配置、来源清单、成功批次、导航节点四张
|
||||
|
||||
导航使用独立的严格输出 schema,要求 `summary`、`changeDigest`、`openItems`;四个事实数组允许省略或为空,非空数组、错误类型和额外字段仍拒绝。验证通过后才在内部补空数组,以复用现有导航存储形状。叶子继续要求四个数组齐全,不将缺字段当作成功;来源、事件实体、变化实体和关系端点均校验。继续时还会核对已保存叶子的引用,导航不覆盖其独立事实。
|
||||
|
||||
`supervision:pause` 将父 signal 传入排队及在途模型;取消后的输出不提交。`supervision:resume` 恢复已保存 run;`supervision:batches` 默认每页 10 批、最多 20 批,返回完整叶子及来源。活动返回已保存批次、码点数、剩余来源、当前阶段、排队或执行中的模型任务数及已保存配置。软预算到期会等待已派发批次各自结束,随后显示 `paused`;完成判定不依赖摘要措辞。
|
||||
`supervision:pause` 将父 signal 传入排队及在途模型;取消后的输出不提交。`supervision:resume` 恢复已保存 run;`supervision:batches` 默认每页 10 批、最多 20 批,返回完整叶子及来源。活动返回已保存批次、码点数、剩余来源、当前阶段、排队或执行中的模型任务数及已保存配置。提取和导航汇总持续运行至完成,不按累计运行时长暂停;用户暂停会中断在途请求,仅保留已保存批次。完成判定不依赖摘要措辞。
|
||||
|
||||
当前阶段以 `phase` 写入既有 `supervision_review_runs.state_json`,取值为 `collecting / extracting / summarizing / saving`;无新表或迁移。它记录服务最后进入的阶段,失败和重启后保留;完成仍由 `supervision_runs.status` 的发布事务决定。活动的 `navigationNodes` 从已保存导航表计数,`inFlight` 仍来自进程内计数。旧运行没有 phase 时不回填推测值。清单和最终发布为同步事务,短阶段不保证被轮询看到,清单事务失败也不会留下部分阶段记录。批次接口只有成功记录,没有逐个失败/在途任务或项目、会话聚合状态;界面仅按分页返回的真实归属组织详情。
|
||||
|
||||
@@ -35,12 +37,12 @@ schema 47 增加运行配置、来源清单、成功批次、导航节点四张
|
||||
| `supervisionReview.pageSize` | 50 | 1..200 | 每次来源元数据页条数;不改变最终集合或批次边界 |
|
||||
| `supervisionReview.batchCharacters` | 8000 | 1000..16000 | 每批新正文 UTF-16 单元,不含提示、候选及 JSON |
|
||||
| `supervisionReview.batchMessages` | 20 | 1..50 | 每批不同消息数;任务等无消息 ID 的来源单独计数 |
|
||||
| `supervisionReview.executionSeconds` | 300 | 30..3600 | 每次执行软预算,继续后重新计时;到期停止新增工作 |
|
||||
| `supervisionReview.executionSeconds` | 300 | 30..3600 | 旧设置兼容字段,执行时忽略,设置页不再展示;恢复旧 run 同样不按此值暂停 |
|
||||
| `supervisorOrganizeTimeoutSeconds` | 240 | 30..600 | 创建回顾时冻结,节点获槽并解析 Runtime 后计时 |
|
||||
| `heartbeatReportTimeoutSeconds` | 240 | 30..600 | 维持报告发起时冻结及既有租约规则 |
|
||||
| `supervisorModelConcurrency` | 1 | 1..4 | 回顾保存接纳并发;共享池上限仍随应用设置实时变化,实际执行取较小值 |
|
||||
|
||||
应用仍默认关闭。新参数由共享 Zod 合同、ApplicationSettingsStore、App 设置回调和 Renderer 表单保存;没有放置尚未生效的高级字段。
|
||||
应用仍默认关闭。除上述旧兼容字段外,参数由共享 Zod 合同、ApplicationSettingsStore、App 设置回调和 Renderer 表单保存;没有放置尚未生效的高级字段。
|
||||
|
||||
### 未完成与实测限制
|
||||
|
||||
@@ -221,7 +223,7 @@ run(request):
|
||||
admit bounded leaf tasks, round-robin by project and conversation
|
||||
run admitted leaves in a shared worker pool
|
||||
validate and persist each successful batch with facts, provenance and resume positions
|
||||
if budget expires, a task fails or cancellation arrives: stop; retain batches; mark incomplete
|
||||
if a task fails or cancellation arrives: stop; retain batches; mark incomplete
|
||||
continue paging all project/conversation/message/source groups until exhausted
|
||||
reduce within conversation, then project, then requested scope
|
||||
pass through singleton groups; reduce oversized groups in bounded rounds
|
||||
@@ -231,7 +233,7 @@ run(request):
|
||||
return completed only after publication; otherwise return resumable incomplete status
|
||||
```
|
||||
|
||||
采集与叶子执行可交错,但入队和在飞任务有界,不使用对全部来源的 `Promise.all`。每批完成后持久化,再接纳下一批;时间不足就暂停。首次派发前用有界探测判断是否直通;穷尽范围后确实只有一个叶子时直接生成最终输出,通过校验后只调用一次模型。因预算只接纳一个叶子时仍是未完成 run。空范围及自动 `no_change` 必须在全部相关目录/来源页检查完毕后判定,不调用模型、不把预算未读误作无变化。
|
||||
采集与叶子执行可交错,但入队和在飞任务有界,不使用对全部来源的 `Promise.all`。每批完成后持久化,再接纳下一批,持续至全部来源及汇总完成。首次派发前用有界探测判断是否直通;穷尽范围后确实只有一个叶子时直接生成最终输出,通过校验后只调用一次模型。因预算只接纳一个叶子时仍是未完成 run。空范围及自动 `no_change` 必须在全部相关目录/来源页检查完毕后判定,不调用模型、不把预算未读误作无变化。
|
||||
|
||||
叶子有多个时输出受限摘要卡及结构化事实,Main 保存卡片 token 到原文片段的映射。会话只有一个卡片时透传,项目只有一个会话摘要时透传,全局只有一个项目摘要时透传。不得为了层级外观额外调用模型。边界必须标清项目和会话,跨项目汇总仍使用原请求 scope 的候选实体集。
|
||||
|
||||
@@ -283,7 +285,7 @@ run(request):
|
||||
|
||||
建议扩展 `coverage_json`:是否读完冻结范围、已处理新字符和片段数、覆盖项目/会话数、是否仍有来源、停止原因及恢复引用。精确片段继续用 locator,恢复记录关联批次输出,不仅保存一个总计数。总数未知时显示“仍有未读内容”,不用动态发现数充当固定分母。结果读取合同需显式暴露 coverage;当前数据库查询了 coverage,但共享结果 schema 未返回它。
|
||||
|
||||
手动和自动均须跨重启继续同一未完成逻辑 run。Main 根据持久化 run ID 读取冻结范围、成功输出及位置,复查来源版本,恢复剩余页和未成功汇总;Renderer 不能自报已处理 offset。手动继续不污染自动位置;自动恢复先于创建同 scope 新 run。恢复后的单次执行重新获得执行预算,但不丢失既有进度。
|
||||
手动和自动均须跨重启继续同一未完成逻辑 run。Main 根据持久化 run ID 读取冻结范围、成功输出及位置,复查来源版本,恢复剩余页和未成功汇总;Renderer 不能自报已处理 offset。手动继续不污染自动位置;自动恢复先于创建同 scope 新 run。恢复后持续处理剩余工作,不按旧执行预算暂停,也不丢失既有进度。
|
||||
|
||||
持久化续跑及手动“继续”入口是本设计的验收要求,不能以建议缩小范围替代。恢复记录的保留期限、主动放弃后的清理规则和具体 schema 尚未确定;不得在 run 未完成时自动删除唯一成功输出或继续所需位置。显式放弃可以终止后续工作,但不能标为完整覆盖。
|
||||
|
||||
@@ -297,7 +299,7 @@ run(request):
|
||||
|
||||
若后续决定让心跳只触发监督、报告从同一结构化结果投影,必须先定义记忆建议和任务建议的共同输出契约、各自成功语义及旧报告展示,另行修改 FR-S4 和 US-S27;本轮不把该产品调整藏在调度重构里。也不使用多阶段事务将两个结果强行绑定。
|
||||
|
||||
报告先获共享槽位再领取租约,并在实际模型请求开始前刷新;报告完成或无变化提交后释放槽位,再执行下游监督。领取与续租条件见当前控制清单。未来整次执行预算不能套到报告租约,也不能在报告完成前等待整棵监督树。现有 tick 防重入不替代共享池上限;监督过长会延迟到期检查,拟通过有界执行和持久化续跑控制,不另建后台调度服务。
|
||||
报告先获共享槽位再领取租约,并在实际模型请求开始前刷新;报告完成或无变化提交后释放槽位,再执行下游监督。领取与续租条件见当前控制清单。未来整次执行预算不能套到报告租约,也不能在报告完成前等待整棵监督树。现有 tick 防重入不替代共享池上限;监督过长仍可能延迟到期检查;移除自动时间预算暂停不改变 tick 的串行等待关系,不另建后台调度服务。
|
||||
|
||||
## 10. 活动与结果展示
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ FR-S1:将现有智能心跳入口整合、升级为监督者,不增加另一
|
||||
|
||||
应用默认关闭,用户显式启用后才接受新的回顾工作;关闭时保留计划和历史。默认值、已保存选择、自动运行条件及在途运行边界由[应用启停与执行](./logic-design.md#应用启停与执行)统一定义。
|
||||
|
||||
FR-S2:应用包含“工作回顾 / 故事线图谱 / 自动监督 / 活动记录 / 设置”五个主视图。工作回顾提供统一监督结果阅读、历史选择和手动新回顾,结果显示自身范围与日期;自动监督集中展示计划状态、范围、周期及下次执行时间,以及运行概览、指标、趋势、自动监督报告、待处理记忆与行动建议和心跳审计,创建和编辑使用同一份 Modal 表单;活动记录展示统一实际执行、进度、详情及结果入口,支持按计划 ID 筛选;设置只管理超时、并发、分页、分批及执行预算,不嵌套次级菜单。计划保存失败保留草稿,关闭未保存修改须确认;编辑已暂停计划保持暂停,创建须显式保存并启用。原有运行记录及应用、存储标识保留。
|
||||
FR-S2:应用包含“工作回顾 / 故事线图谱 / 自动监督 / 活动记录 / 设置”五个主视图。工作回顾提供统一监督结果阅读、历史选择和手动新回顾,结果显示自身范围与日期;自动监督集中展示计划状态、范围、周期及下次执行时间,以及运行概览、指标、趋势、自动监督报告、待处理记忆与行动建议和心跳审计,创建和编辑使用同一份 Modal 表单;活动记录展示统一实际执行、进度、详情及结果入口,支持按计划 ID 筛选;设置只管理超时、并发、分页、分批及单次响应容量,不嵌套次级菜单。计划保存失败保留草稿,关闭未保存修改须确认;编辑已暂停计划保持暂停,创建须显式保存并启用。原有运行记录及应用、存储标识保留。
|
||||
|
||||
当前设置还支持分别调整报告与监督整理的模型超时,以及两者共用的监督并发数。并发设置不限制普通聊天,也不保证避免上游限流;排队不消耗模型阶段时限,降低并发不打断在途请求。取值及生效规则由[当前控制清单](./review-scheduling-design.md#当前超时与调度控制清单已实现)定义。
|
||||
|
||||
FR-S10:活动同时覆盖手动监督回顾、定时心跳及用户立即运行的心跳。每条记录显示触发方式、范围、开始与结束时间、运行状态、失败原因和已有结果入口。同一次心跳的报告和下游监督回顾合为一条活动,报告保存成功不能掩盖下游仍在运行、暂停或失败。活动读取不调用模型,关闭应用后不查询活动。新批次运行支持暂停及继续,保留成功批次;人工修改的时间序列审计尚未实现,历史缺失不得猜测补齐。
|
||||
|
||||
FR-S2:分页只控制数据库读取批量,不能限制最终处理范围。当前已开放来源页大小、每批正文/消息上限、执行软预算;这些配置在新建回顾时保存,继续沿用。当前字段和未来高级参数的边界见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界),交互见 [UI 设计](./ui-design.md#算法配置设置目标)。完整覆盖、来源校验与事实保留不作为可关闭选项。
|
||||
FR-S2:分页只控制数据库读取批量,不能限制最终处理范围。当前已开放来源页大小、每批正文/消息上限;这些配置在新建回顾时保存,继续沿用。单次响应容量按当前设置生效。回顾持续处理至完成,不按累计运行时长自动暂停。当前字段和未来高级参数的边界见[生产接线](./review-scheduling-design.md#0-生产接线与剩余边界),交互见 [UI 设计](./ui-design.md#算法配置设置目标)。完整覆盖、来源校验与事实保留不作为可关闭选项。
|
||||
|
||||
## 范围与触发
|
||||
|
||||
@@ -37,7 +37,7 @@ FR-S5:会话右侧在跟随模式下展示当前会话的监督反馈;固定
|
||||
|
||||
FR-S6:监督者可提出整理与修改建议;已有会话监督的观察边界继续生效。自动更新图谱不能静默覆盖用户修改的描述、已确认的关系或已有知识库正文。取消整理后保留上次结果;失败说明受影响范围并允许重试,部分结果不得显示为完整回顾。
|
||||
|
||||
FR-S4、FR-S6、FR-S10 的验收要求:完整分页处理冻结范围内的入选来源;叶子事实和来源完整保留,摘要用于导航。预算耗尽或失败后保存成功批次与位置,重启后继续,不重复计算仍有效的成功批次;未读完或汇总未成功不能标完成。该续跑要求已进入生产接线,当前实现、实测范围与剩余差异见[回顾分块调度](./review-scheduling-design.md#0-生产接线与剩余边界)。
|
||||
FR-S4、FR-S6、FR-S10 的验收要求:完整分页处理冻结范围内的入选来源;叶子事实和来源完整保留,摘要用于导航。用户主动暂停或失败后保留成功批次与位置,重启后继续,不重复计算仍有效的成功批次;未读完或汇总未成功不能标完成。该续跑要求已进入生产接线,当前实现、实测范围与剩余差异见[回顾分块调度](./review-scheduling-design.md#0-生产接线与剩余边界)。
|
||||
|
||||
## 与知识库的关系
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ schema 43 在结果上增加 `graph_snapshot_json`,保存当次实体名称、
|
||||
|
||||
`supervisorModelConcurrency` 为 1 至 4 的整数,默认 1。报告和监督整理共用 Main 内 `SupervisionModelPool` FIFO 池,普通聊天不入池;降低上限不终止在途请求。报告槽位从领取前持有至报告完成或无变化提交后,在下游监督开始前释放,避免并发 1 时相互等待。监督服务外层运行仍串行;该池不是完整分块调度器,也不提供跨聊天与监督的提供商全局并发、RPM、TPM 或 Retry-After 控制。
|
||||
|
||||
报告租约为 `max(300, reportTimeoutSeconds + 60)` 秒,300 是下限;每次到期领取最多一条。Runtime 解析后、模型请求开始前按冻结超时刷新租约,数据库要求运行 ID、owner、attempt、claimed 状态和未过期条件均匹配,否则拒绝启动模型。直连模型独立的 600 秒传输超时、网络重试、心跳重领及轮询不属于用户阶段时限。设置 UI 提供两个超时、共享并发及分页/分批/软预算字段。
|
||||
报告租约为 `max(300, reportTimeoutSeconds + 60)` 秒,300 是下限;每次到期领取最多一条。Runtime 解析后、模型请求开始前按冻结超时刷新租约,数据库要求运行 ID、owner、attempt、claimed 状态和未过期条件均匹配,否则拒绝启动模型。直连模型独立的 600 秒传输超时、网络重试、心跳重领及轮询不属于用户阶段时限。设置 UI 提供两个超时、共享并发及分页/分批/单次响应容量字段;整次回顾持续处理至完成,旧 `executionSeconds` 不再参与执行判断。
|
||||
|
||||
schema 47 的四张批次相关表使用外键随监督运行清理,未完成运行不新增自动过期删除规则。启动时原有 running 记录会标 failed,但已保存批次和配置保留,活动页仍可继续。发布逐页读取叶子并复用现有结果/来源/图谱写入,在同一事务更新完成状态和自动 checkpoint。
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
阶段下显示已保存批数、已读字符数、剩余来源和已保存导航合并数,运行中另显示排队或在途数。字符进度按码点统计,界面不展示 Unicode 术语。恢复操作先于详情:运行中可暂停,失败或暂停后沿用同一运行继续;已记录合并、发布阶段时分别显示“继续合并摘要”“重试发布”。成功叶子保留。来源失效时显示新建回顾提示,不提供无效继续。原始错误默认折叠,展开后的完整文本最多占 240px 高度,可滚动、选择复制和键盘聚焦;错误与恢复操作位于批次树之前。结束时间、覆盖区间、报告状态和已有摘要收进“运行详情与配置”,已保存配置在其中再次折叠。
|
||||
|
||||
“已保留事实与来源”展开后才查询既有批次接口,每页 10 批,按本页持久记录组成项目、会话、批次层级。项目使用可用名称,会话优先使用保存来源的标题;只有已保存批次标记“已保存”,不创建未落库的待执行、在途或失败叶子,也不把本页数量当作项目总量。批次摘要收起时最多两行,展开后保留完整摘要、事件、实体、变化、关系及原文片段。来源显示消息 ID、码点范围及版本;长原文局部滚动。原生 details/summary 保留键盘展开和可见焦点,轮询不主动展开用户收起的会话。加载、空批次和读取失败分开显示,失败可重试;换页失败不显示上一页批次,切换 run 后旧响应失效。暂停状态为“未完成,可继续”,手动回顾软预算暂停后在工作回顾页提示去活动继续,不显示完成通知。
|
||||
“已保留事实与来源”展开后才查询既有批次接口,每页 10 批,按本页持久记录组成项目、会话、批次层级。项目使用可用名称,会话优先使用保存来源的标题;只有已保存批次标记“已保存”,不创建未落库的待执行、在途或失败叶子,也不把本页数量当作项目总量。批次摘要收起时最多两行,展开后保留完整摘要、事件、实体、变化、关系及原文片段。来源显示消息 ID、码点范围及版本;长原文局部滚动。原生 details/summary 保留键盘展开和可见焦点,轮询不主动展开用户收起的会话。加载、空批次和读取失败分开显示,失败可重试;换页失败不显示上一页批次,切换 run 后旧响应失效。暂停状态为“未完成,可继续”,手动回顾主动暂停后在工作回顾页提示去活动继续,不显示完成通知。
|
||||
|
||||
## 自动监督设置
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
并发说明明确报告和监督整理共用上限,排队不计入模型超时;普通聊天不受此设置限制,但仍与监督竞争上游额度。表单不宣称提供商全局并发、RPM、TPM 或 Retry-After 限流已实现。
|
||||
|
||||
模型超时不包含数据收集与保存,直连模型另有独立 600 秒传输超时。“回顾算法”表单提供“每次读取来源条数”“每批文本量”“每批消息数”和“单次整理时长”,显示默认值及合法范围,使用“保存回顾算法”独立提交。文本量内部仍按 UTF-16 单元计算,界面不要求用户理解编码单位。保存经 App 的现有设置回调进入共享设置存储,加载/保存/锁定时禁用,失败保留草稿。取值以[当前设置合同](./review-scheduling-design.md#当前设置合同)为准。帮助说明执行时间达到软预算后,正在处理的批次完成并保存,再暂停,可从活动记录继续;已保存配置用“同时处理任务数”解释并发,说明实际并发还受当前监督设置限制。
|
||||
模型超时不包含数据收集与保存,直连模型另有独立 600 秒传输超时。“回顾算法”表单提供“每次读取来源条数”“每批文本量”“每批消息数”和“单次响应容量”,显示默认值及合法范围,使用“保存回顾算法”独立提交。文本量内部仍按 UTF-16 单元计算,界面不要求用户理解编码单位。保存经 App 的现有设置回调进入共享设置存储,加载/保存/锁定时禁用,失败保留草稿。取值以[当前设置合同](./review-scheduling-design.md#当前设置合同)为准。帮助说明回顾持续处理至完成,每批保存进度,可从活动记录主动暂停并继续;已保存配置用“同时处理任务数”解释并发,说明实际并发还受当前监督设置限制。
|
||||
|
||||
对应 US-S22 至 US-S24。“自动监督”先显示计划列表,点击创建或编辑才打开 Modal;复用原表单,不保留页面内副本。表单展示名称、项目范围、每日或每周频率、指定时间、回顾窗口(1 至 720 小时)和历史保留(1 至 365 天);每周显示星期选择。时区常显,新建使用本机时区,编辑保留计划原时区。列表上方帮助说明选择每日或每周时间、保存并启用后自动运行。
|
||||
|
||||
@@ -52,9 +52,9 @@ Modal 按[共享表单 Modal 规则](../../../UI-DESIGN.md#615-表单-modal)复
|
||||
|
||||
以下高级分组为目标交互;已接通的四项算法控件以上节为准。其余参数引用[设置配置清单](./review-scheduling-design.md#4-设置配置清单),不在 UI 文档另设数值,不增加一级页面。
|
||||
|
||||
普通配置常显消息每页条数、监督并发、报告/整理模型超时和单次执行软预算。高级配置按“来源与分块”“上下文与输出”“汇总与内存”“超时与续跑”“上游额度”分组。消息页字段的常显帮助为:“每次从数据库读取的消息数;会持续读完所选范围,不限制回顾总消息数,也不决定每次发给模型的消息数。”模型批次消息数、片段数和新增字符数分别标注,避免统称“批量大小”。
|
||||
普通配置常显消息每页条数、监督并发、报告/整理模型超时和单次响应容量。高级配置按“来源与分块”“上下文与输出”“汇总与内存”“超时与续跑”“上游额度”分组。消息页字段的常显帮助为:“每次从数据库读取的消息数;会持续读完所选范围,不限制回顾总消息数,也不决定每次发给模型的消息数。”模型批次消息数、片段数和新增字符数分别标注,避免统称“批量大小”。
|
||||
|
||||
字段显示单位、已确定的默认值和合法范围;补充帮助说明调大后的内存、等待或模型用量影响。允许继承的节点超时显示“继承监督整理值”及解析后的秒数;依赖模型容量的输入/输出预算显示所用模型、容量及 token 是否为估算。跨字段校验覆盖输入与输出预留之和、扇入可容纳性、软预算与硬期限顺序、退避起始与上限;错误附在字段并关联可访问说明,提交时聚焦首个错误,Main 仍作权威校验。
|
||||
字段显示单位、已确定的默认值和合法范围;补充帮助说明调大后的内存、等待或模型用量影响。允许继承的节点超时显示“继承监督整理值”及解析后的秒数;依赖模型容量的输入/输出预算显示所用模型、容量及 token 是否为估算。跨字段校验覆盖输入与输出预留之和、扇入可容纳性、退避起始与上限;错误附在字段并关联可访问说明,提交时聚焦首个错误,Main 仍作权威校验。
|
||||
|
||||
规划中的“保存配置”持久化后只用于新建运行,常显“已开始和待继续的回顾沿用启动时配置”。“恢复本组默认值”只修改该组草稿,经保存才生效,不开始回顾、不修改已有计划;取消编辑恢复已保存值。运行详情展示本次实际配置,继续操作沿用它;想采用新算法配置时使用“重新回顾”,说明会重新计算。当前已实现字段仍按上文的阶段读取和共享池生效方式工作,整次 run 冻结接线完成后再统一调整提示。
|
||||
|
||||
|
||||
@@ -86,18 +86,31 @@ it('reopens saved batches after failure, resumes exact Unicode offsets and only
|
||||
expect(f.summarize).toHaveBeenCalledTimes(calls)
|
||||
})
|
||||
|
||||
it('soft budget pauses without checkpointing and freezes configuration across continuation', async () => {
|
||||
it('continues extraction and navigation beyond the saved execution budget', async () => {
|
||||
const f = await fixture([['x'.repeat(2500)]], { concurrency: 1, executionSeconds: 30 })
|
||||
const clock = vi.spyOn(Date, 'now').mockReturnValue(time)
|
||||
f.summarize.mockImplementationOnce(async () => { clock.mockReturnValue(time + 31000); return empty })
|
||||
const result = await f.service().run(request)
|
||||
expect(result.status).toBe('completed')
|
||||
expect(result.coverage).toMatchObject({ batches: 3, remainingSources: 0, complete: true })
|
||||
expect(f.summarize).toHaveBeenCalledTimes(5)
|
||||
expect(f.db.supervisionReviewStore().batches(result.runId!, 100).flatMap(row => row.evidence).map(row => row.content.length)).toEqual([1000, 1000, 500])
|
||||
clock.mockRestore()
|
||||
})
|
||||
|
||||
it('still pauses on user request and resumes saved batches without changing batch settings', async () => {
|
||||
const f = await fixture([['x'.repeat(2500)]], { concurrency: 1 })
|
||||
const service = f.service()
|
||||
f.summarize.mockImplementationOnce(async () => empty).mockImplementationOnce(async () => {
|
||||
service.pause(f.db.listSupervisionActivity()[0]!.id)
|
||||
return empty
|
||||
})
|
||||
const result = await service.run(request)
|
||||
expect(result.status).toBe('paused')
|
||||
expect(result.coverage).toMatchObject({ batches: 1, remainingSources: 1, complete: false })
|
||||
f.config.batchCharacters = 16000
|
||||
const resumed = await f.service().resume(result.runId!)
|
||||
expect(resumed.status).toBe('completed')
|
||||
expect((await f.service().resume(result.runId!)).status).toBe('completed')
|
||||
expect(f.db.supervisionReviewStore().batches(result.runId!, 100).flatMap(row => row.evidence).map(row => row.content.length)).toEqual([1000, 1000, 500])
|
||||
clock.mockRestore()
|
||||
})
|
||||
|
||||
it('rejects changed source versions and rolls back offsets when batch persistence fails', async () => {
|
||||
|
||||
@@ -264,10 +264,9 @@ export class SupervisorService {
|
||||
this.controllers.set(runId, controller)
|
||||
const activity = { inFlight: 0 }
|
||||
this.activity.set(runId, activity)
|
||||
const deadline = Date.now() + config.executionSeconds * 1000
|
||||
const empty: SupervisionSummaryOutput = { summary: 'No new evidence', changeDigest: '', openItems: [], events: [], entities: [], entityChanges: [], relations: [] }
|
||||
const paused = () => {
|
||||
db.pause(runId, controller.signal.aborted ? 'Review paused by user' : 'Execution budget reached; continue to review remaining sources')
|
||||
db.pause(runId, 'Review paused by user')
|
||||
return { runId, request, evidence: [], output: empty, status: 'paused' as const, coverage: db.progress(runId) }
|
||||
}
|
||||
try {
|
||||
@@ -300,7 +299,6 @@ export class SupervisorService {
|
||||
if (controller.signal.aborted) return paused()
|
||||
const groups = db.groups(runId, config.concurrency)
|
||||
if (!groups.length) break
|
||||
if (Date.now() >= deadline) return paused()
|
||||
const results = await Promise.allSettled(groups.map(async group => {
|
||||
controller.signal.throwIfAborted()
|
||||
const evidence = db.chunk(runId, group.projectId, group.conversationId, config)
|
||||
@@ -324,7 +322,6 @@ export class SupervisorService {
|
||||
const id = createHash('sha256').update(JSON.stringify([left.id, right.id])).digest('hex')
|
||||
const cached = db.navigation(runId, id)
|
||||
if (cached) return { id, output: cached }
|
||||
if (Date.now() >= deadline) throw new Error('REVIEW_SOFT_BUDGET')
|
||||
const evidence = [left, right].map(card => ({ id: card.id, sourceType: 'note' as const, sourceId: card.id,
|
||||
title: 'Retained review navigation', content: card.output.summary, occurredAt: request.timeRange.to }))
|
||||
const output = await summarize(evidence, true)
|
||||
@@ -378,7 +375,7 @@ export class SupervisorService {
|
||||
await this.store.save(result)
|
||||
return result
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted || (error instanceof Error && error.message === 'REVIEW_SOFT_BUDGET')) return paused()
|
||||
if (controller.signal.aborted) return paused()
|
||||
this.store.fail?.(runId, error instanceof Error ? error.message : 'Review failed')
|
||||
throw error
|
||||
} finally { this.controllers.delete(runId); this.activity.delete(runId) }
|
||||
|
||||
@@ -11,6 +11,8 @@ it.each(['zh-CN', 'en-US'] as const)('shows actionable supervisor help without d
|
||||
render(<SupervisionReviewSettings />)
|
||||
const copy = i18nResources[locale].heartbeat
|
||||
expect(screen.getByText(copy.reviewSettings.pauseHelp)).toBeVisible()
|
||||
expect(screen.queryByLabelText(new RegExp(copy.reviewSettings.executionSeconds))).not.toBeInTheDocument()
|
||||
expect(copy.reviewSettings.pauseHelp).toContain(locale === 'zh-CN' ? '持续处理至完成' : 'continuously until complete')
|
||||
expect(i18n.exists('reviewSettings.pauseHelp', { ns: 'heartbeat', lng: locale })).toBe(true)
|
||||
expect(JSON.stringify(copy)).not.toMatch(/RPM|TPM|Retry-After|尚未实现|not implemented|没有按时间保存的操作审计|chronological audit log|暂不支持按分钟|Minute intervals are not supported/)
|
||||
expect(copy.timeouts.concurrencyHelp).toContain(locale === 'zh-CN' ? '仅限制监督请求' : 'This limits supervision only')
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ApplicationSettings, ApplicationSettingsUpdate } from '../../share
|
||||
import { defaultSupervisionReviewSettings, supervisionReviewSettingsSchema } from '../../shared/supervision-review-contracts'
|
||||
|
||||
const fields = [
|
||||
['pageSize', 1, 200], ['batchCharacters', 1000, 16000], ['batchMessages', 1, 50], ['executionSeconds', 30, 3600], ['responseKiB', 100, 16384]
|
||||
['pageSize', 1, 200], ['batchCharacters', 1000, 16000], ['batchMessages', 1, 50], ['responseKiB', 100, 16384]
|
||||
] as const
|
||||
|
||||
export function SupervisionReviewSettings({ settings, disabled, onSave }: {
|
||||
|
||||
@@ -9,7 +9,7 @@ export const heartbeat = {
|
||||
help: 'Page size controls database reads, not total coverage. Batch limits split work without dropping remaining text. These settings and the organize timeout are frozen for each new review; continuing uses its saved configuration. Response capacity uses the current setting for each request; increase it before continuing a failed review.',
|
||||
pageSize: 'Sources read at a time (default 50)', batchCharacters: 'Text size per batch (default 8000)',
|
||||
batchMessages: 'Messages per batch (default 20)', executionSeconds: 'Time before pausing, seconds (default 300)',
|
||||
pauseHelp: 'When the execution soft budget is reached, active batches finish and save before pausing. Continue from Activity records.',
|
||||
pauseHelp: 'Reviews run continuously until complete and save each batch. Pause and continue from Activity records.',
|
||||
invalid: 'Enter whole numbers within the displayed ranges.',
|
||||
pause: 'Pause review', resume: 'Continue saved review', facts: 'Retained facts and sources',
|
||||
pausedHint: 'Review is incomplete. Successful batches are saved. Open Activity to inspect progress and continue.',
|
||||
@@ -19,7 +19,7 @@ export const heartbeat = {
|
||||
previousPage: 'Previous page', nextPage: 'Next page',
|
||||
extracting: 'Reviewing source batches', summarizing: 'Building navigation summaries', inFlight: '{{count}} queued or running model tasks',
|
||||
configuration: 'Saved review configuration',
|
||||
configurationValues: 'Read {{pageSize}} sources at a time; batch size {{batchCharacters}}, up to {{batchMessages}} messages; pause after {{executionSeconds}}s; model timeout {{timeoutSeconds}}s; up to {{concurrency}} simultaneous tasks. Tasks also share the current supervision concurrency limit.',
|
||||
configurationValues: 'Read {{pageSize}} sources at a time; batch size {{batchCharacters}}, up to {{batchMessages}} messages; run continuously until complete; model timeout {{timeoutSeconds}}s; up to {{concurrency}} simultaneous tasks. Tasks also share the current supervision concurrency limit.',
|
||||
progress: '{{batches}} saved batches · {{characters}} characters reviewed · {{remaining}} sources remaining',
|
||||
identityError: 'The model returned an invalid entity reference. This batch was not saved; previously saved batches remain available.',
|
||||
runError: 'This run failed. Review the error details before continuing; saved batches remain available.',
|
||||
|
||||
@@ -6,7 +6,7 @@ export const heartbeat = {
|
||||
help: '每页条数只控制数据库读取量,不限制回顾总量。分批上限用于拆分工作,剩余正文会继续处理。这些设置及整理超时在新回顾开始时固定;继续回顾沿用已保存的配置。响应容量按每次请求的当前设置生效,调高后可继续失败的回顾。',
|
||||
pageSize: '每次读取来源条数(默认 50)', batchCharacters: '每批文本量(默认 8000)',
|
||||
batchMessages: '每批消息数(默认 20)', executionSeconds: '单次整理时长,秒(默认 300)',
|
||||
pauseHelp: '执行时间达到软预算后,正在处理的批次完成并保存,再暂停;可从活动记录继续。',
|
||||
pauseHelp: '回顾持续处理至完成,每批保存进度;可从活动记录主动暂停并继续。',
|
||||
invalid: '请输入所示范围内的整数。',
|
||||
pause: '暂停回顾', resume: '继续已保存回顾', facts: '已保留事实与来源',
|
||||
pausedHint: '本次回顾尚未完成,成功批次已保存。请到活动页查看进度并继续。',
|
||||
@@ -16,7 +16,7 @@ export const heartbeat = {
|
||||
previousPage: '上一页', nextPage: '下一页',
|
||||
extracting: '正在整理来源批次', summarizing: '正在生成导航摘要', inFlight: '{{count}} 个模型任务排队或执行中',
|
||||
configuration: '本次回顾的已保存配置',
|
||||
configurationValues: '每次读取 {{pageSize}} 个来源;每批文本量 {{batchCharacters}},最多 {{batchMessages}} 条消息;整理 {{executionSeconds}} 秒后暂停;模型超时 {{timeoutSeconds}} 秒;最多同时处理 {{concurrency}} 个任务。实际并发还受当前监督模型并发设置限制。',
|
||||
configurationValues: '每次读取 {{pageSize}} 个来源;每批文本量 {{batchCharacters}},最多 {{batchMessages}} 条消息;持续处理至完成;模型超时 {{timeoutSeconds}} 秒;最多同时处理 {{concurrency}} 个任务。实际并发还受当前监督模型并发设置限制。',
|
||||
progress: '已保存 {{batches}} 批 · 已读 {{characters}} 个字符 · 剩余 {{remaining}} 个来源',
|
||||
identityError: '模型返回的实体引用无效,本批未保存,之前成功的批次仍保留。',
|
||||
runError: '本次运行失败,请查看错误详情后继续。已保存的批次仍保留。',
|
||||
|
||||
Reference in New Issue
Block a user