@tea-agent/loop-agent 0.40.0-next.12 → 0.40.0-next.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.
- package/CHANGELOG.md +38 -0
- package/README.md +2 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +9 -5
- package/dist/cli/program.js +38 -3
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/shared/operator/capabilities.js +9 -1
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +82 -11
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +140 -4
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/routes.js +482 -29
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-store.js +27 -18
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/turn-process.js +32 -3
- package/dist/worker/console/operator-actions.js +16 -2
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +100 -0
- package/dist/worker/console/routes.js +221 -6
- package/dist/worker/console/server.js +172 -42
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C_hkwt2J.js → abnfDiagram-N423BO3Z-B0gRLXJs.js} +1 -1
- package/dist/worker/console/static/assets/{arc-s2Q6r96B.js → arc-DldY0Hsk.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CtgsrwU1.js → architectureDiagram-T3A2C74G-4xHhMRM9.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DZ-A7EiJ.js → blockDiagram-VBNYF7ZC-DPym7n0M.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D2qv0Nyo.js → c4Diagram-5PPSVZJV-cSFzY5yS.js} +1 -1
- package/dist/worker/console/static/assets/channel-4xBA5kwn.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D-UYpSaW.js → chunk-2GRJ4B5K-w-64RMuc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DAS3jrrt.js → chunk-2Q5K7J3B-Bvue--jh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CSbuRcGB.js → chunk-5RXB4S5H-DxqkQnCh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BDH1ZO40.js → chunk-5VM5RSS4-C83gOKLK.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dc2cqQIb.js → chunk-6Q2QTUOP-C_V6tKAV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CkZuemIW.js → chunk-GF5L2VYU-CPvZQuL9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-URwjQnte.js → chunk-JWPE2WC7-w31Z_ATM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-04RHylJK.js → chunk-KBJHAD2P-DermQC9f.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CvQX56Hh.js → chunk-RYQCIY6F-CmwaYcnB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-dUF7qura.js → chunk-XXDRQBXY-DLwZqUBf.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CkefOueI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CkefOueI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-COwbfxJE.js → cose-bilkent-JH36ORCC-Dzh7bT73.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CaBEJekQ.js → cynefin-VYW2F7L2-CfOcBHtp.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-COWUBdet.js → cynefinDiagram-MW4NZA55-D0EbCVDZ.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BLxVqTn4.js → dagre-VZM6K2ZE-MfOFA6PZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-WL0yKfEQ.js → diagram-7IWD3JNH-BOa5-9PN.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DaVkKCle.js → diagram-B4RE2ZJO-B6zvJ-aL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DdTgnRGf.js → diagram-LBJQPF4R-BKwr5bRF.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-PG2Axo6A.js → diagram-Q27KOJAE-PLI5QLpX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-6vuQzlzI.js → diagram-UB23O5K3-BgYYME3o.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BpXeBz-g.js → ebnfDiagram-BXEA7PRR-D-SpjXk2.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-PxtUnaa1.js → erDiagram-JOGREHBK--JHpEOka.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B--Qgvs1.js → flowDiagram-UKHOOZJN-CbkQgWUz.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BIfQCgtx.js → ganttDiagram-PKOTCBZU-CTZMAMVE.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BRaX_E4j.js → gitGraphDiagram-DS77QQ5N-BaUGSDig.js} +1 -1
- package/dist/worker/console/static/assets/index-C4mO0i6P.js +437 -0
- package/dist/worker/console/static/assets/index-C86Y55Or.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Cd93r4Ab.js → infoDiagram-6WML65LV-DA3WbWjf.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C4c80iHt.js → ishikawaDiagram-WSZJBQD7-DbhI82U4.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Ch5ajf3s.js → journeyDiagram-NVQOT4AX-CJKA3OBG.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DbpyW1Ut.js → kanban-definition-27J2QSJJ-CF6dIVRa.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BHVjBnTW.js → linear-D4dEtOUL.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DO70kJ34.js → mermaid.core-ClhMic6O.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-_P44Zpvs.js → mindmap-definition-FAOFIHXS-NfRin7S2.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bgi9FmEd.js → pegDiagram-VL7TDLO6-DlBtBwW-.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-C9InDfJn.js → pieDiagram-7S7Q4E2Y-DdIkNHZO.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DtKrUouv.js → quadrantDiagram-CIZ2JOQS-YD8T98WX.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-zgAD_6Yo.js → railroadDiagram-AXF67PYL-CS4e5zGK.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQNO6kj4.js → requirementDiagram-LRYGKXZP-BpbWoL9r.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-knKu111P.js → sankeyDiagram-W5VNT64P-DvWy-Mc9.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-D6tshiQ6.js → sequenceDiagram-SI44F4Z6-DmoXH5pJ.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BMIWJcir.js → sizeCapture-X5ZJPWSS-BY89V1SW.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BP9wRDqc.js → stateDiagram-OKZ733FA-DaK2Iuqr.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DLc5WD-z.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DEkM5Dhe.js → swimlanes-SLNWSIFB-VVcozat-.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DhjxpI7d.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ByBKsL35.js → timeline-definition-Z64GVDOM-D4l1E4Mu.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-waFbNCUy.js → vennDiagram-T6HMQDX7-CJmewqcq.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-1ljiY0eZ.js → wardleyDiagram-T6FBY63Y-CsCCYorj.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B4ZLHr_L.js → xychartDiagram-ELKLHX3M-CwjILi0N.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +24 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +8 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/workspace-context.js +28 -3
- package/dist/worker/console/workspace-registry.js +333 -60
- package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +6 -2
- package/dist/worker/observe/static/styles.css +78 -25
- package/dist/worker/observe/static/views/dag-inspector.js +2 -1
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/workflows/dag/init-hybrid.js +3 -3
- package/docs/templates/backend-test-dag.json +3 -3
- package/docs/templates/init-managed-agents.md +1 -1
- package/package.json +1 -1
- package/skills/loop-agent/references/command-reference.md +15 -1
- package/dist/worker/console/static/assets/channel-BluaMT3-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-H-ZpSafr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-H-ZpSafr.js +0 -1
- package/dist/worker/console/static/assets/index-Crrgl8Mx.css +0 -1
- package/dist/worker/console/static/assets/index-vPsp-idE.js +0 -437
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-ClEhytQG.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPg5IMuu.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
- Operator Chat 输入框下方的「调用记录」入口现在会常驻显示;没有记录时显示为 0,发送消息或刷新运行状态时不再突然消失。
|
|
6
|
+
- 初始化选择模型时现在能识别公司内部 `wizard-local` 的 AICoder 型号梯度:`aicoder-default`、`aicoder-plus`、`aicoder-pro`、`aicoder-max` 从左到右能力逐级增强;它会作为选择 LOW/MED/HIGH 的参考,但不会忽略成本和项目需求机械分档。
|
|
7
|
+
- 修复 Operator Chat 左侧栏工作区名称过长时遮挡悬浮「更多」「新建对话」按钮的问题:工作区行改为 flex 行布局,名称过长时单行省略,操作按钮改为行尾占位显示,悬浮背景整行连续无断层。
|
|
5
8
|
- 修复 Console 轨迹视图引用未声明 CSS 自定义属性导致仓库发布门禁失败的问题,补齐品牌色、警告色与字体别名并保持现有视觉回退值。
|
|
6
9
|
|
|
7
10
|
### 重点更新
|
|
8
11
|
|
|
12
|
+
- Console 设置页新增「Pi 插件」区块:内置一份经 npm 校验的插件目录(6303 个,仅展示 5562 个可安装、含中文说明),支持搜索、每页条数(10/20/50/100)、分页、一键安装/卸载(调用 `pi install/remove`,全局 `~/.pi/agent`),安装/卸载带进度过渡与结果提示。
|
|
13
|
+
- CLI 命令 `--help` 现在直接显示真实 usage(此前 raw-args 命令只在解析出错时才抛 usage):`task advance` / `task status` / `task source prepare` 与 `dag resume|doctor|report|reconcile-run|rerun|rerun-task|reject|approve|status` 的 `--help` 顶部即列出完整参数契约,`task advance` 并附 `--verify "label:command"` 示例;`init` 提示补全局 `--repo-root <path>`(置于子命令前)约定。
|
|
14
|
+
- `task source prepare`(确定性任务源投影,managed 需求.md/执行约束.md)从死代码转正:此前 `TASK_SUBCOMMANDS` 缺少 `source` 导致命令不可达,现 `task source prepare <task-id> --from-text/--from-file/--from-stdin/--from-draft --dry-run|--apply` 可直接调用。
|
|
9
15
|
- Console 对话页「轨迹」视图响应式优化:轨迹列宽跟随面板实际宽度收缩(`.oc-trajectory` 加 `width:100%`、`.oc-trajectory-turn/.step` 加 `min-width:0` 修 flex 收缩链),窄窗口(≤720px)自动隐藏耗时列,避免工具行被固定项挤爆。
|
|
10
16
|
- Console 对话页新增「轨迹」视图(详情侧栏 tab):仿 DSH ui-trajectory,用本仓库 turnGroups 数据把对话按「用户 → 过程 → AI 回复」渲染成统一单元格列表,每行带 kind 标签 + 图标(用户/ AI / 思考 / 工具 / 活动 / 错误 / 压缩);用户与 AI 用标签颜色 + 图标区分,AI 中间文本也标注;思考、工具、以及超长用户/AI 消息默认单行截断、可展开/收起看全文(含工具参数与结果、状态与耗时)。支持一键「导出 HTML」自包含文件(长内容用 `<details>` 折叠,短内容内联)。主会话思考块标签由英文 Think 改为「思考」。
|
|
11
17
|
- DAG 检查器节点「执行过程」的「导出 HTML」改为复用页面真实渲染与样式,导出的独立 HTML 排版与页面一致;Pi 会话节点导出会带上原始协议事件,左上角提供「展开/隐藏原始协议事件」按钮(默认隐藏,每项仍可单独展开),shell/静态节点导出同样与页面一致。
|
|
12
18
|
- backend-test 的 pytest repair 现在继续使用 run-owned plan,不再搜索测试目录 README;correspondence finding 会直接指向 docstring、参数 ID、符号、脚本或 payload 的修复位置,collection/scenario repair 与 generated-local Python 写边界保持一致,并在单次 Pi 会话达到 24 轮或 80K 最大 usage snapshot 前受控返回 partial failure。
|
|
19
|
+
- Operator Chat 新增持续目标模式:可以用 `/goal` 或输入框上方的目标条创建跨多个回合推进的目标,并随时暂停、继续、编辑或清除;刷新后仍能看到目标,但重启 Console 后不会未经确认自行续跑。
|
|
13
20
|
- Operator Chat 现在可以在同一个页面管理多个工作区;每个会话始终使用创建它的工作区,切换对话时地址栏保持简洁稳定。
|
|
14
21
|
- 对话消息流整体调整为更紧凑、清晰的布局,思考过程、工具执行、阶段性结果和最终回答会按真实发生顺序展示。
|
|
15
22
|
- 上下文过大时会自动压缩并继续原请求,消息流会依次展示触发原因、压缩进度和压缩结果;手动压缩也使用同一套展示方式。
|
|
@@ -18,6 +25,7 @@
|
|
|
18
25
|
|
|
19
26
|
### 新增
|
|
20
27
|
|
|
28
|
+
- 模型可以读取、创建和更新当前会话的持续目标;长任务会在安全轮数内自动续行,优先处理用户排队消息,并在 DAG 或其他运行结束后再继续。上下文压缩后,当前目标也会自动回到模型上下文中。
|
|
21
29
|
- 右侧辅助栏统一承载文件、上下文、调用记录、运行活动和当前 DAG;点击消息流中的运行状态即可在同一侧栏查看运行、门禁与产物,并可自由调整侧栏宽度。
|
|
22
30
|
- 新增任务清单与结构化提问卡片,让长任务的进度和需要用户选择的内容更容易理解。
|
|
23
31
|
- 「对话 / 操作 / 观测」使用统一导航;操作和观测页面可以切换工作区查看对应数据。
|
|
@@ -29,14 +37,23 @@
|
|
|
29
37
|
- `@agent-context/sift@0.0.1-alpha.6` 作为可选依赖接入 Console 与 DAG:默认开启,`HARNESS_CONSOLE_SIFT` / `HARNESS_DAG_SIFT` 设为 `0`、`false` 或 `off` 即可关闭。Anthropic 缺省 prompt cache 形态保持 no-op,避免破坏缓存。
|
|
30
38
|
- 后端测试的计划与生成阶段只读取任务明确绑定的需求、环境报告和本轮产物,减少无关仓库内容干扰结果,并让后续步骤继续使用同一份已确认计划。
|
|
31
39
|
- Operator Chat 的任务清单工具描述补齐多步骤与 DAG 使用纪律:开始前按具体步骤规划、保持进行中约束、完成后立即更新完整列表,并用面向用户的阶段呈现 DAG 进度,简单任务自动跳过。
|
|
40
|
+
- 右侧文件工作台打开 `.html` 或 `.htm` 文件时默认直接预览:沙箱会完整加载页面及同项目的相对样式、图片和字体,同时阻止脚本及外部网络访问;大体积源码不再直接报错,而是展示前 512 KiB 并清晰提示内容已截断。
|
|
41
|
+
- 排队消息改为由输入框上方的队列面板统一展示;正常排队、撤回和删除不再反复弹出提示,当前会话也不再显示重复的“待发送”,其他会话只保留低调的排队数量。
|
|
32
42
|
- 正式发布会自动同步更新版本日志和文档站摘要;发布说明优先生成简洁自然的中文,生成服务不可用时也能安全完成发布。
|
|
33
43
|
- 输入框整合模型与推理等级选择,并只展示有真实数据的轮次、耗时、速度和缓存统计。
|
|
34
44
|
- 工作区、会话列表、文件标签和侧栏交互更加紧凑自然,折叠、切换和页面过渡更加流畅。
|
|
45
|
+
- 左侧多个工作区可以独立展开;展开工作区不再切换当前作用域,跨工作区打开会话时也不会闪出错误的空列表。
|
|
46
|
+
- 多个 Console 同时运行时会安全合并工作区增删,并把变更实时同步到已打开的页面,避免旧缓存覆盖新工作区。
|
|
35
47
|
- 设置页改用自然中文展示当前工作区的模型状态,减少内部状态和重复信息。
|
|
36
48
|
- DAG 检查器简化节点说明、执行记录和提示词重启面板,长内容更容易阅读和定位。
|
|
37
49
|
|
|
38
50
|
### 修复
|
|
39
51
|
|
|
52
|
+
- 修复 Console「更新并初始化」被观测顶栏挡住、看起来像从 DAG 详情消失的问题:入口改到操作 → 设置,观测与对话页不再挂全局悬浮按钮。
|
|
53
|
+
- 修复从恢复打开全屏观测时,长 DAG run id 把「返回 恢复」顶进居中模式开关底下、按钮显示不全的问题;上下文 chip 改到顶栏下方独立一行,过长 id 省略、按钮不收缩。
|
|
54
|
+
- 修复 Console 内嵌观测后样式泄漏到对话/操作、观测顶栏丢失新鲜度与 Pi 状态、从恢复打开观测看不到「返回 恢复」、以及检查器盖住一级导航的问题;对话里的观测链接改为壳内地址。
|
|
55
|
+
- 修复 Console 内嵌观测丢失「提示词重启」入口、Remixicon 图标和产品深链的问题:托管观测从 `/inspect/` 响应解析 scoped token,不把该凭据写入 Console HTML。
|
|
56
|
+
- 修复 backend-test 的 Scenario Partition 虽声明 Domain/slot kinds、却未把展开后的精确 `TP-SP-*` 清单交给 Markdown writer,导致 Case 只生成普通别名 TP 并被 N6 全量判缺的问题;Plan 现在把每个 exact slot 逐字写入绑定 Coverage Matrix Rule,Markdown生成与同步节点返回前执行 exact-set 审计,普通别名和 `SINGLE`/`MULTIPLE` 不再替代 partition slots。
|
|
40
57
|
- 修复 backend-test 的 Scenario Partition 计划表只提示 `each-value`/`omitted`、却由下游确定性门禁强制要求 `not-in-set` 的合同漂移;计划、Markdown Case 与 pytest 现在统一明确唯一补集槽位,避免 PI 因表格指令遗漏 `TP-<Partition ID>-NOT-IN-SET` 后触发 `MISSING_COMPLEMENT_SLOT`。
|
|
41
58
|
- 修复文档审计扫描本地 `.scratch/` 临时证据与打包目录、导致仓库 preflight 和发布前验证被非治理文件阻断的问题。
|
|
42
59
|
- 修复 backend-test 把 Markdown 明确声明的 enum-invalid、wrong-type、null、empty 和精确非法 custom literal 当作 payload enum 契约漂移、错误消耗唯一一次 pytest repair 的问题;豁免现在按 pytest parameter ID、目标字段和值逐 TP 绑定,positive/nominal drift 与 path 缺陷仍严格阻断。
|
|
@@ -47,10 +64,31 @@
|
|
|
47
64
|
- 修复 backend-test 对 `resource_client.call(..., payload=...)` 一类本地 transport 的 payload AST 假阴性;readiness 新增固定分类/Case 排除汇总和 pytest 启动事实,payload repair finding 同时携带 expected/observed/missing path 清单。
|
|
48
65
|
- 修复后端测试在 payload、fixture 与 Markdown→pytest 对应关系同时异常时只修复其中一类问题的问题;pytest 侧缺陷现在通过结构化 `repairFindings` 合并进入唯一一次有界 repair,Markdown Test Point 未分类或重复分类则在生成阶段定向重试并禁止下游猜修。
|
|
49
66
|
- 修复重命名会话时输入框没有回显当前标题的问题。
|
|
67
|
+
- 修复自动压缩提示长期停在输入框上方、恢复输出跑到压缩前面,以及回复文字在工具出现时突然跳位的问题;消息流会按顺序保留模型提供商的原始错误、压缩过程和恢复结果,刷新后也保持一致。
|
|
68
|
+
- 工作区列表支持像 DSH Web 一样上下拖动调整顺序;新的顺序会自动保存,刷新页面或重新启动看板后仍然保持。
|
|
69
|
+
- 修复多工作区下已有会话只显示在对话正文、却没有出现在左侧列表的问题;会话会稳定归属到创建它的工作区,旧会话也会自动恢复到列表中。
|
|
70
|
+
- 会话列表改为独立的全局目录:启动时一次恢复历史归属,之后随会话变化实时更新;移除工作区不会删除或隐藏其中的会话,它们会保留在“未归类”中并可继续打开。
|
|
50
71
|
- 修复停止回复后输入区恢复迟缓、排队消息偶发丢失,以及切换会话后旧状态残留的问题。
|
|
51
72
|
- 修复宽表格、文件预览、滚动条和窄屏布局中的多处显示异常。
|
|
52
73
|
- 修复部分 Windows 环境读取运行状态时的偶发失败,并提高前后端任务验证的可靠性。
|
|
53
74
|
|
|
75
|
+
## [0.39.3] - 2026-08-28
|
|
76
|
+
|
|
77
|
+
### 新增
|
|
78
|
+
|
|
79
|
+
- 前端实现支持来源归属运行时注入,使用宽松校验,不符合格式也不会阻断现有流程,方便后续接入完整的来源追溯。
|
|
80
|
+
|
|
81
|
+
## [0.39.2] - 2026-08-28
|
|
82
|
+
|
|
83
|
+
### 重点更新
|
|
84
|
+
|
|
85
|
+
- DAG 检查器与提示词重启面板重做:节点备注列改为节点用途简介不再复读节点 ID,默认隐藏协议事件、需要时可一键展开;提示词重启只读面板撑满剩余高度,内容在卡片内滚动,避免长提示词被压缩或被全局滚动条逼出视区。
|
|
86
|
+
|
|
87
|
+
### 修复
|
|
88
|
+
|
|
89
|
+
- 为 `views/dag.js` 补充缺失类型声明,修复 next 发布通道的 typecheck 阻断。
|
|
90
|
+
- 记录 release/0.39 上 observe 面板改动的回移植入 0.39.2,方便追溯修复来源。
|
|
91
|
+
|
|
54
92
|
## [0.39.1] - 2026-08-25
|
|
55
93
|
|
|
56
94
|
### 重点更新
|
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ loop-agent init doctor --repo-root .
|
|
|
54
54
|
loop-agent inspect --repo-root .
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
`init model-catalog` 会通过 Pi runtime 与 OpenCode 的非 verbose 列表发现本机可用模型,只输出白名单模型元数据、Pi setup 状态和 provider 凭据就绪状态,不输出原始配置或凭据。已有 LOW/MED/HIGH 只要仍存在于 Pi
|
|
57
|
+
`init model-catalog` 会通过 Pi runtime 与 OpenCode 的非 verbose 列表发现本机可用模型,只输出白名单模型元数据、Pi setup 状态和 provider 凭据就绪状态,不输出原始配置或凭据。已有 LOW/MED/HIGH 只要仍存在于 Pi 就保持不变;只有已移除或缺失的档位才由模型结合项目、能力、成本和上下文窗口重选。对于 `wizard-local` provider,目录会识别 `aicoder-default < aicoder-plus < aicoder-pro < aicoder-max` 的家族内相对能力顺序,但不会把它机械绑定为固定三档。如果 Pi 没有模型定义,初始化模型会先向用户补齐 provider/model、API/base URL、上下文窗口、最大输出等无法推断的信息并协助配置;如果只是凭据缺失,则保留 harness 选择并修复认证。初始化会保留已有用户内容,并补充 `AGENTS.md`、`harness.json`、`ai_workspace/loop-agent/`、`.agents/skills/`、`.harness/` 和验证脚本等治理入口。
|
|
58
58
|
|
|
59
59
|
控制器升级后,旧项目可能仍使用过期的 repo-local skills、模板、managed blocks 和治理说明。普通安全仓库命令结束时会自动检测目标项目是否需要对齐:非 TTY 只向 stderr 输出提示且不写入目标;TTY 且无 human decisions、无活跃 DAG/Worker 时,可经明确 `y/yes` 同意后应用 deterministic safe actions;也可手动运行统一入口:
|
|
60
60
|
|
|
@@ -126,7 +126,7 @@ agent-worker console doctor --repo .
|
|
|
126
126
|
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
裸 `agent-worker console`
|
|
129
|
+
裸 `agent-worker console` 不在终端询问升级。操作 → 设置提供非阻塞的「更新并初始化」入口,默认选择 npm `latest` 稳定版,也可由操作者显式选择 `next` 预发布版;更新前会先检查活跃运行和全局安装来源。`console doctor`、帮助和版本命令均不触发更新检查。
|
|
130
130
|
|
|
131
131
|
访问 Console <http://127.0.0.1:8790/>;**Operator Chat Official** 是 Console 内的主引导交互,默认打开「对话」工作区。构建时设置 `VITE_OPERATOR_CHAT_DEFAULT_LANDING=tasks` 可改回 Tasks;URL hash(`#/tasks`)与 localStorage 仍优先于默认值。顶部「检视」以及 `/inspect/` 提供只读 Dashboard、DAG、时间线与 artifacts;`loop-agent …` / `agent-worker …` CLI direct path 仍是受治理 fallback。当前状态为 **code ready, dogfood pending/unmet**,不宣称已替代 openCode 或其他外部主会话。
|
|
132
132
|
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.40.0-next.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.40.0-next.14",
|
|
4
|
+
"gitSha": "4a8185d1e0c0203f2d68c11f5c779b8130145252",
|
|
5
|
+
"builtAt": "2026-08-30T06:48:39.207Z"
|
|
6
6
|
}
|
|
@@ -11,6 +11,7 @@ import { runInspectNext } from "../commands/inspect-next.js";
|
|
|
11
11
|
import { runInstructions } from "../commands/instructions.js";
|
|
12
12
|
import { runTaskAdvance } from "../commands/task-advance.js";
|
|
13
13
|
import { runTaskStatus } from "../commands/task-status.js";
|
|
14
|
+
import { runTaskSourcePrepare } from "../commands/task-source-prepare.js";
|
|
14
15
|
import { runOperator } from "../commands/operator.js";
|
|
15
16
|
import { runPlanList } from "../commands/plan-list.js";
|
|
16
17
|
import { runPlanCheck, runPlanComplete, runPlanCreate, } from "../commands/plan.js";
|
|
@@ -100,7 +101,6 @@ const EVAL_SUBCOMMANDS = [
|
|
|
100
101
|
"outer-loop",
|
|
101
102
|
"ignition",
|
|
102
103
|
];
|
|
103
|
-
const CLOSEOUT_SUBCOMMANDS = ["task"];
|
|
104
104
|
const PLAN_SUBCOMMANDS = ["list", "create", "complete", "check"];
|
|
105
105
|
const SPINE_SUBCOMMANDS = ["audit"];
|
|
106
106
|
const HANDOFF_SUBCOMMANDS = ["check", "coverage"];
|
|
@@ -139,7 +139,7 @@ const DAG_SUBCOMMANDS = [
|
|
|
139
139
|
"final-verification",
|
|
140
140
|
"decision",
|
|
141
141
|
];
|
|
142
|
-
const TASK_SUBCOMMANDS = ["advance", "status"];
|
|
142
|
+
const TASK_SUBCOMMANDS = ["advance", "status", "source"];
|
|
143
143
|
const OPERATOR_SUBCOMMANDS = ["capabilities"];
|
|
144
144
|
const WORKFLOW_SUBCOMMANDS = [
|
|
145
145
|
"list",
|
|
@@ -215,7 +215,7 @@ export const COMMAND_DEFINITIONS = [
|
|
|
215
215
|
name: "init",
|
|
216
216
|
adapter: "none",
|
|
217
217
|
tier: "primary",
|
|
218
|
-
intent: "Initialize a target repository with loop-agent harness capabilities.",
|
|
218
|
+
intent: "Initialize a target repository with loop-agent harness capabilities. Run INSIDE the target repo, or pass the global --repo-root <path> BEFORE this subcommand (e.g. `loop-agent --repo-root . init --profile full --merge`).",
|
|
219
219
|
usage: "init [instructions|model-catalog|doctor|check-update|update|reconcile|upgrade] [--profile full|minimal] [--merge] [--model provider/model | --low-model provider/model --med-model provider/model --high-model provider/model] [--json|--markdown] [--bootstrap-surface|--apply-safe] [--client-recovery=auto|project|user|off] [--run-id <id> --status|--continue|--report]",
|
|
220
220
|
subcommands: [...INIT_SUBCOMMANDS],
|
|
221
221
|
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
@@ -248,8 +248,8 @@ export const COMMAND_DEFINITIONS = [
|
|
|
248
248
|
name: "task",
|
|
249
249
|
adapter: "required",
|
|
250
250
|
tier: "primary",
|
|
251
|
-
intent: "Task lifecycle: advance (mutation)
|
|
252
|
-
usage: "task <advance|status> ...",
|
|
251
|
+
intent: "Task lifecycle: advance (mutation), status (read), source prepare (deterministic source files).",
|
|
252
|
+
usage: "task <advance|status|source> ...",
|
|
253
253
|
subcommands: [...TASK_SUBCOMMANDS],
|
|
254
254
|
handler: async ({ repoRoot, subcommand, rest }) => {
|
|
255
255
|
if (subcommand === "advance") {
|
|
@@ -260,6 +260,10 @@ export const COMMAND_DEFINITIONS = [
|
|
|
260
260
|
await runTaskStatus(repoRoot, rest);
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
|
+
if (subcommand === "source") {
|
|
264
|
+
await runTaskSourcePrepare(repoRoot, rest);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
263
267
|
throw new Error(formatSubcommandUsageError("task", TASK_SUBCOMMANDS));
|
|
264
268
|
},
|
|
265
269
|
},
|
package/dist/cli/program.js
CHANGED
|
@@ -38,8 +38,9 @@ import { runInstructions } from "../commands/instructions.js";
|
|
|
38
38
|
import { runKnowledge } from "../commands/knowledge.js";
|
|
39
39
|
import { runLoop } from "../commands/loop.js";
|
|
40
40
|
import { runLoopBenchmark } from "../commands/loop-benchmark.js";
|
|
41
|
-
import { runTaskAdvance } from "../commands/task-advance.js";
|
|
42
|
-
import { runTaskStatus } from "../commands/task-status.js";
|
|
41
|
+
import { runTaskAdvance, TASK_ADVANCE_USAGE } from "../commands/task-advance.js";
|
|
42
|
+
import { runTaskStatus, TASK_STATUS_USAGE } from "../commands/task-status.js";
|
|
43
|
+
import { runTaskSourcePrepare, TASK_SOURCE_PREPARE_USAGE, } from "../commands/task-source-prepare.js";
|
|
43
44
|
import { runOperator } from "../commands/operator.js";
|
|
44
45
|
import { runPiReuseBenchmark } from "../commands/pi-reuse-benchmark.js";
|
|
45
46
|
import { parsePiPromptArgs, printPiPromptUsage, runPiPrompt, } from "../commands/pi-prompt.js";
|
|
@@ -124,7 +125,11 @@ async function runTaskCommand(repoRoot, subcommand, rest) {
|
|
|
124
125
|
await runTaskStatus(repoRoot, rest);
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
127
|
-
|
|
128
|
+
if (subcommand === "source") {
|
|
129
|
+
await runTaskSourcePrepare(repoRoot, rest);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
throw new Error("usage: task <advance|status|source> ...");
|
|
128
133
|
}
|
|
129
134
|
async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
130
135
|
switch (command) {
|
|
@@ -402,9 +407,38 @@ function createRawArgsCommand(name, description, defaultRepoRoot) {
|
|
|
402
407
|
function addRawSubcommands(parent, subcommands, defaultRepoRoot) {
|
|
403
408
|
for (const subcommand of subcommands) {
|
|
404
409
|
const command = createRawArgsCommand(subcommand, `${parent.name()} ${subcommand}`, defaultRepoRoot);
|
|
410
|
+
applyRawSubcommandUsage(command, `${parent.name()} ${subcommand}`);
|
|
405
411
|
parent.addCommand(command);
|
|
406
412
|
}
|
|
407
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* Well-known raw-args subcommands whose real USAGE would otherwise only appear
|
|
416
|
+
* after a parse error. `--help` shows this text at the top (beforeAll help text)
|
|
417
|
+
* so one-shot callers (Humans, Pi, Chat) can read the real contract up front.
|
|
418
|
+
* Keys are the full chain (`<parent> <subcommand>`); values must mirror the
|
|
419
|
+
* command module's own usage strings verbatim where one exists.
|
|
420
|
+
*/
|
|
421
|
+
const RAW_SUBCOMMAND_USAGE = {
|
|
422
|
+
"task advance": TASK_ADVANCE_USAGE,
|
|
423
|
+
"task status": TASK_STATUS_USAGE,
|
|
424
|
+
"task source prepare": TASK_SOURCE_PREPARE_USAGE,
|
|
425
|
+
"task source": "usage: task source prepare <task-id> [--from-text|--from-file|--from-stdin|--from-draft] [--dry-run|--apply]\nDeterministic task source projection (managed 需求.md / 执行约束.md). See `task source prepare --help` for the full flag surface.",
|
|
426
|
+
"dag resume": "usage: dag resume --run-id <id>",
|
|
427
|
+
"dag status": "usage: dag status --run-id <id>",
|
|
428
|
+
"dag approve": 'usage: dag approve --run-id <id> --option <A|B|...> [--notes "..."]',
|
|
429
|
+
"dag reject": 'usage: dag reject --run-id <id> --reason "..."',
|
|
430
|
+
"dag rerun": "usage: dag rerun --run-id <id> --from-node <node-id> --plan [--json] | dag rerun --run-id <id> --from-node <node-id> --plan-hash <hash> --request-id <key> --reason <text> [--json]",
|
|
431
|
+
"dag rerun-task": "usage: dag rerun-task --run-id <id> --reason <text> --request-id <key> [--profile auto|minimal|standard|reviewed|supervised] [--task-id <id>] [--json]",
|
|
432
|
+
"dag reconcile-run": "usage: dag reconcile-run --run-id <id> [--action supersede|abandon] [--reason <text>]",
|
|
433
|
+
"dag doctor": "usage: dag doctor [--run-id <id>]\nInspect health issues before approve/reject/resume; no --json (output is already structured JSON).",
|
|
434
|
+
"dag report": "usage: dag report [--run-id <id>] [--lifecycle active|paused|completed|all] [--json|--markdown] [--failed-only] [--latest] [--paused-latest]\nRead-only run report; --json gives structured facts (canonical: `dag report --run-id <id> --lifecycle completed --json`).",
|
|
435
|
+
};
|
|
436
|
+
function applyRawSubcommandUsage(command, chainKey) {
|
|
437
|
+
const usage = RAW_SUBCOMMAND_USAGE[chainKey];
|
|
438
|
+
if (!usage)
|
|
439
|
+
return;
|
|
440
|
+
command.addHelpText("beforeAll", usage);
|
|
441
|
+
}
|
|
408
442
|
function addStandaloneSubcommands(parent, subcommands, handler) {
|
|
409
443
|
for (const subcommand of subcommands) {
|
|
410
444
|
parent
|
|
@@ -640,6 +674,7 @@ export function buildLoopAgentProgram(options) {
|
|
|
640
674
|
const prepare = createRawArgsCommand("prepare", "Prepare deterministic task source files", options.defaultRepoRoot);
|
|
641
675
|
configureTaskSourcePrepare(prepare);
|
|
642
676
|
replaceActionWithOptionAwareDispatch(prepare, options.defaultRepoRoot);
|
|
677
|
+
applyRawSubcommandUsage(prepare, "task source prepare");
|
|
643
678
|
source.addCommand(prepare);
|
|
644
679
|
}
|
|
645
680
|
}
|
|
@@ -31,8 +31,28 @@ function sanitizeInputKinds(value) {
|
|
|
31
31
|
.sort();
|
|
32
32
|
return entries.length > 0 ? entries : undefined;
|
|
33
33
|
}
|
|
34
|
+
const AICODER_CAPABILITY_RANK = {
|
|
35
|
+
"aicoder-default": 1,
|
|
36
|
+
"aicoder-plus": 2,
|
|
37
|
+
"aicoder-pro": 3,
|
|
38
|
+
"aicoder-max": 4,
|
|
39
|
+
};
|
|
40
|
+
function relativeCapabilityForModelId(provider, modelId) {
|
|
41
|
+
if (provider !== "wizard-local")
|
|
42
|
+
return undefined;
|
|
43
|
+
const rank = AICODER_CAPABILITY_RANK[modelId];
|
|
44
|
+
if (!rank)
|
|
45
|
+
return undefined;
|
|
46
|
+
return {
|
|
47
|
+
family: "aicoder",
|
|
48
|
+
rank,
|
|
49
|
+
scale: 4,
|
|
50
|
+
order: "weakest-to-strongest",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
34
53
|
function createCatalogEntry(input) {
|
|
35
54
|
const piCompatible = input.source === "pi";
|
|
55
|
+
const relativeCapability = relativeCapabilityForModelId(input.provider, input.modelId);
|
|
36
56
|
return {
|
|
37
57
|
reference: `${input.provider}/${input.modelId}`,
|
|
38
58
|
provider: input.provider,
|
|
@@ -42,6 +62,7 @@ function createCatalogEntry(input) {
|
|
|
42
62
|
credentialReady: input.credentialReady,
|
|
43
63
|
piCompatible,
|
|
44
64
|
eligibleForHarness: piCompatible && input.credentialReady === true && input.tools !== false,
|
|
65
|
+
...(relativeCapability ? { relativeCapability } : {}),
|
|
45
66
|
...(compactRecord({
|
|
46
67
|
reasoning: input.reasoning,
|
|
47
68
|
tools: input.tools,
|
|
@@ -448,6 +469,7 @@ export async function discoverInitModelCatalog(options) {
|
|
|
448
469
|
rules: [
|
|
449
470
|
"Choose only models with eligibleForHarness=true; OpenCode-only entries are context, not proof of Pi executor compatibility.",
|
|
450
471
|
"Use target-project evidence plus model capabilities, cost, context window, and credential readiness; tiers may intentionally share a model.",
|
|
472
|
+
"When relativeCapability is present, compare only entries from the same family and treat larger rank values as stronger; it is a relative capability hint, not a mandatory LOW/MED/HIGH assignment.",
|
|
451
473
|
"Preserve an existing complete usable matrix unless it is unavailable or the user asked to reselect it.",
|
|
452
474
|
"Ask the user only for a material unresolved credential, cost, compliance, or availability decision.",
|
|
453
475
|
],
|
|
@@ -13,7 +13,7 @@ const USAGE = `usage:
|
|
|
13
13
|
[--feature-id <id>]
|
|
14
14
|
[--allowed-path <glob>]...
|
|
15
15
|
[--forbidden-path <glob>]...
|
|
16
|
-
[--verify <label:command>]...
|
|
16
|
+
[--verify <label:command>]... # e.g. --verify "typecheck:npx tsc --noEmit" (label BEFORE the colon)
|
|
17
17
|
[--verify-timeout <label:timeoutMs>]...
|
|
18
18
|
[--backend-test <json>]
|
|
19
19
|
[--accept-recommendations <digest>]
|
|
@@ -31,6 +31,7 @@ const USAGE = `usage:
|
|
|
31
31
|
[--progress-interval-ms <ms>]
|
|
32
32
|
[--dry-run]
|
|
33
33
|
[--json]`;
|
|
34
|
+
export { USAGE as TASK_ADVANCE_USAGE };
|
|
34
35
|
function pushList(target, value) {
|
|
35
36
|
const list = target ?? [];
|
|
36
37
|
list.push(value);
|
|
@@ -232,6 +233,26 @@ export function parseTaskAdvanceArgs(args) {
|
|
|
232
233
|
}
|
|
233
234
|
return { taskId, options };
|
|
234
235
|
}
|
|
236
|
+
const FOREGROUND_DAG_WARNING = [
|
|
237
|
+
"[task advance] --approve-gate will execute the whole DAG in the FOREGROUND:",
|
|
238
|
+
" this terminal must stay open until every node finishes (writer nodes can run 10-60+ min).",
|
|
239
|
+
" Killing it mid-run (timeout / Ctrl-C / closed terminal) leaves an orphaned run that",
|
|
240
|
+
" only `dag reconcile-run --action supersede|abandon` can close.",
|
|
241
|
+
" Prefer background holders instead:",
|
|
242
|
+
" - Console: prepareDagExecution -> runDag (operation-held)",
|
|
243
|
+
" - CLI: nohup loop-agent task advance <task-id> --approve-gate <token> ... &",
|
|
244
|
+
].join("\n");
|
|
245
|
+
/**
|
|
246
|
+
* Operator guardrail (dogfood 2026-08-29): warn a human sitting on a TTY that
|
|
247
|
+
* `--approve-gate` runs the full DAG in the foreground before the process
|
|
248
|
+
* parks on node execution. Piped/non-TTY invocations (Console operations,
|
|
249
|
+
* scripts, --json pipelines) stay silent so machine paths are unaffected.
|
|
250
|
+
*/
|
|
251
|
+
export function warnForegroundDagExecution(options, stream = process.stderr) {
|
|
252
|
+
if (options.quiet || stream.isTTY !== true)
|
|
253
|
+
return;
|
|
254
|
+
stream.write(`${FOREGROUND_DAG_WARNING}\n`);
|
|
255
|
+
}
|
|
235
256
|
function mapOutcome(result) {
|
|
236
257
|
if (result.blockers.length > 0) {
|
|
237
258
|
const code = result.blockers[0]?.code ?? "";
|
|
@@ -351,6 +372,7 @@ export async function runTaskAdvance(repoRoot, args) {
|
|
|
351
372
|
// ONLY for the approve-gate execution path and disposed on every exit;
|
|
352
373
|
// its timer starts only after onRunStart (double guard, no stray timer).
|
|
353
374
|
if (options.approveGate && !options.dryRun && !options.quiet) {
|
|
375
|
+
warnForegroundDagExecution(options);
|
|
354
376
|
progress = createRunDagProgressObserver({
|
|
355
377
|
intervalMs: options.progressIntervalMs,
|
|
356
378
|
});
|
|
@@ -32,6 +32,7 @@ const USAGE = `usage:
|
|
|
32
32
|
[--dry-run | --apply]
|
|
33
33
|
[--force-overwrite-source]
|
|
34
34
|
[--json]`;
|
|
35
|
+
export { USAGE as TASK_SOURCE_PREPARE_USAGE };
|
|
35
36
|
function pushList(target, value) {
|
|
36
37
|
const list = target ?? [];
|
|
37
38
|
list.push(value);
|
|
@@ -386,6 +387,10 @@ function printHuman(result) {
|
|
|
386
387
|
console.log(lines.join("\n"));
|
|
387
388
|
}
|
|
388
389
|
export async function runTaskSourcePrepare(repoRoot, args) {
|
|
390
|
+
// CLI chain routing (task -> source -> prepare) folds the leaf name into
|
|
391
|
+
// rest[0]; strip it so `loop-agent task source prepare <id> ...` works.
|
|
392
|
+
if (args[0] === "prepare")
|
|
393
|
+
args = args.slice(1);
|
|
389
394
|
if (args.includes("--help") || args.includes("-h")) {
|
|
390
395
|
console.log(USAGE);
|
|
391
396
|
process.exitCode = 0;
|
|
@@ -3,6 +3,7 @@ import { buildOperatorResult, operatorFailed, processExitCodeForOutcome, writeOp
|
|
|
3
3
|
const COMMAND = "task status";
|
|
4
4
|
const USAGE = `usage:
|
|
5
5
|
task status <task-id> [--json|--markdown] [--details]`;
|
|
6
|
+
export { USAGE as TASK_STATUS_USAGE };
|
|
6
7
|
export function parseTaskStatusArgs(args) {
|
|
7
8
|
const options = {};
|
|
8
9
|
const positional = [];
|
|
@@ -51,6 +51,8 @@ const OFFICIAL_ACTION_DESCRIPTIONS = {
|
|
|
51
51
|
workerSchedulerStatus: "Night-scheduler status overview (agent-worker). Use to check whether overnight jobs are healthy.",
|
|
52
52
|
workerSchedulerLedger: "Night-scheduler harvest ledger (agent-worker). Use to audit what overnight runs produced.",
|
|
53
53
|
workerSchedulerDoctor: "Diagnose night-scheduler health (agent-worker). Use when overnight jobs fail to fire or harvest.",
|
|
54
|
+
dagResume: "Unpause a run paused at a human decision checkpoint. Orphaned runs (dead runner, liveness=orphaned) are NOT continuable — do not resume them: use dagReconcileRun --action supersede to archive and then advance/rerun the task fresh.",
|
|
55
|
+
dagReconcileRun: "Reconcile an orphaned run (dead runner, liveness=orphaned). These are NOT continuable: supersede archives the run for a fresh same-task advance/rerun; abandon discards it. resume only unpauses human-decision checkpoints — never for orphaned runs.",
|
|
54
56
|
};
|
|
55
57
|
const officialAction = (action, cli, kind, coverage) => ({
|
|
56
58
|
action,
|
|
@@ -817,7 +819,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
817
819
|
"BINDING_DRIFT",
|
|
818
820
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
819
821
|
],
|
|
820
|
-
description: "
|
|
822
|
+
description: "Returns SYNCHRONOUSLY (not an operation): it immediately yields a session-bound single-use executionId + receipt, which you then pass to runDag to actually execute. It is a server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
|
|
821
823
|
inputParams: [
|
|
822
824
|
{
|
|
823
825
|
name: "taskId",
|
|
@@ -1067,6 +1069,12 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1067
1069
|
required: false,
|
|
1068
1070
|
description: "verification entries as label:command strings",
|
|
1069
1071
|
},
|
|
1072
|
+
{
|
|
1073
|
+
name: "approveGate",
|
|
1074
|
+
type: "string",
|
|
1075
|
+
required: false,
|
|
1076
|
+
description: "optional write-set gate approval token `<gate-id>:sha256:<64-hex>` exactly as reported by task status gate.approveCommand; forwards as --approve-gate and executes the approved DAG as this background operation (preferred over a foreground terminal)",
|
|
1077
|
+
},
|
|
1070
1078
|
],
|
|
1071
1079
|
modelCallable: "always",
|
|
1072
1080
|
humanConfirmation: "none",
|
|
@@ -98,6 +98,13 @@ function isFinalAnswerBlock(block) {
|
|
|
98
98
|
}
|
|
99
99
|
export function splitFinalAssistantBlocks(blocks, options = {}) {
|
|
100
100
|
const displayable = getDisplayableAssistantBlocks(blocks, options);
|
|
101
|
+
// A streaming Pi message is cumulative: a later update may append a toolCall
|
|
102
|
+
// after text that is already visible. Keep the whole live message in the
|
|
103
|
+
// chronological process subtree so that update appends the tool in place
|
|
104
|
+
// instead of reparenting the earlier text from answer -> process.
|
|
105
|
+
if (options.isStreaming) {
|
|
106
|
+
return { answerBlocks: [], processBlocks: displayable };
|
|
107
|
+
}
|
|
101
108
|
let lastProcessIndex = -1;
|
|
102
109
|
for (let i = displayable.length - 1; i >= 0; i--) {
|
|
103
110
|
if (!isFinalAnswerBlock(displayable[i])) {
|
|
@@ -136,7 +136,7 @@ export function projectOperationForChat(operation) {
|
|
|
136
136
|
state: operation.state,
|
|
137
137
|
createdAt: operation.createdAt,
|
|
138
138
|
updatedAt: operation.finishedAt ?? operation.startedAt ?? operation.createdAt,
|
|
139
|
-
inspectHref:
|
|
139
|
+
inspectHref: `/#/inspect?operationId=${encodeURIComponent(operation.operationId)}`,
|
|
140
140
|
...(taskId ? { taskId } : {}),
|
|
141
141
|
...(dagRunId ? { dagRunId } : {}),
|
|
142
142
|
...(featureId ? { featureId } : {}),
|
|
@@ -381,12 +381,26 @@ function dropOneByPriority(ring) {
|
|
|
381
381
|
const events = ring.events;
|
|
382
382
|
if (events.length === 0)
|
|
383
383
|
return undefined;
|
|
384
|
-
|
|
384
|
+
// Keep the newest Goal snapshot/tombstone as the session's durable Goal
|
|
385
|
+
// source of truth. Otherwise a long thread could forget an active Goal or
|
|
386
|
+
// resurrect a cleared one after ring eviction.
|
|
387
|
+
let protectedGoalIndex = -1;
|
|
388
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
389
|
+
if (events[index].kind === "goal_changed" ||
|
|
390
|
+
events[index].kind === "goal_cleared") {
|
|
391
|
+
protectedGoalIndex = index;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const textIndex = events.findIndex((event, index) => index !== protectedGoalIndex && event.kind === "message_update");
|
|
385
396
|
if (textIndex !== -1)
|
|
386
397
|
return events.splice(textIndex, 1)[0];
|
|
387
|
-
const structuralIndex = events.findIndex((event) => event.kind !== "message_end");
|
|
398
|
+
const structuralIndex = events.findIndex((event, index) => index !== protectedGoalIndex && event.kind !== "message_end");
|
|
388
399
|
if (structuralIndex !== -1)
|
|
389
400
|
return events.splice(structuralIndex, 1)[0];
|
|
401
|
+
const anyUnprotected = events.findIndex((_event, index) => index !== protectedGoalIndex);
|
|
402
|
+
if (anyUnprotected !== -1)
|
|
403
|
+
return events.splice(anyUnprotected, 1)[0];
|
|
390
404
|
return events.shift();
|
|
391
405
|
}
|
|
392
406
|
/**
|
|
@@ -430,6 +444,13 @@ const DEFAULT_MAX_EVENTS = 2000;
|
|
|
430
444
|
const DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
|
|
431
445
|
/** Same-turn `message_update` events inside this window coalesce into one persisted event. */
|
|
432
446
|
const DEFAULT_COALESCE_WINDOW_MS = 100;
|
|
447
|
+
/** B5-1: `message_update` appends persist at most once per this window (driven
|
|
448
|
+
* by the event `at` clock, same as coalescing — no timers involved). */
|
|
449
|
+
const DEFAULT_PERSIST_DEBOUNCE_MS = 250;
|
|
450
|
+
/** B5-1: hard cap on pending debounced `message_update` appends before an
|
|
451
|
+
* interim flush is forced, so a fast stream never widens the loss window
|
|
452
|
+
* unboundedly even if the `at` clock stalls. */
|
|
453
|
+
const DEFAULT_PERSIST_MAX_PENDING_UPDATES = 250;
|
|
433
454
|
/** Conservative per-event scale for the byte pre-check gate (UTF-8 multibyte ≤ 4 bytes/char × pretty-print expansion). */
|
|
434
455
|
const BYTE_GATE_SCALE = 12;
|
|
435
456
|
/** Fixed envelope slack (schema keys, turnCount, nextSeq, minSeq, turns) for the byte pre-check gate. */
|
|
@@ -632,6 +653,9 @@ export function createChatEventStore(options) {
|
|
|
632
653
|
const maxBytes = options?.maxBytesPerSession ?? DEFAULT_MAX_BYTES;
|
|
633
654
|
const coalesceWindowMs = options?.coalesceWindowMs ?? DEFAULT_COALESCE_WINDOW_MS;
|
|
634
655
|
const eventEpoch = options?.epoch?.trim() || LEGACY_EPOCH;
|
|
656
|
+
const persistWriter = options?.persistWriter ?? writeSecureJsonSync;
|
|
657
|
+
const persistDebounceMs = options?.persistDebounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS;
|
|
658
|
+
const persistMaxPendingUpdates = options?.persistMaxPendingUpdates ?? DEFAULT_PERSIST_MAX_PENDING_UPDATES;
|
|
635
659
|
const persistenceDir = options?.appData?.chatEvents
|
|
636
660
|
? path.resolve(options.appData.chatEvents)
|
|
637
661
|
: undefined;
|
|
@@ -642,6 +666,9 @@ export function createChatEventStore(options) {
|
|
|
642
666
|
const turnCounts = new Map();
|
|
643
667
|
/** Live subscribers per session. */
|
|
644
668
|
const subscribers = new Map();
|
|
669
|
+
/** B5-1: sessions whose latest `message_update` appends are still waiting
|
|
670
|
+
* for the next persist (debounced; structural events / turn changes flush). */
|
|
671
|
+
const pendingPersists = new Map();
|
|
645
672
|
function persistedPath(sessionId) {
|
|
646
673
|
return persistenceDir
|
|
647
674
|
? path.join(persistenceDir, `${safeSessionId(sessionId)}.json`)
|
|
@@ -709,7 +736,7 @@ export function createChatEventStore(options) {
|
|
|
709
736
|
// current limits if it was written by an older config or hand-edited.
|
|
710
737
|
enforceBounds(sessionId, ring);
|
|
711
738
|
if (orphaned)
|
|
712
|
-
|
|
739
|
+
persistNow(sessionId, ring);
|
|
713
740
|
return ring;
|
|
714
741
|
}
|
|
715
742
|
catch (error) {
|
|
@@ -763,11 +790,36 @@ export function createChatEventStore(options) {
|
|
|
763
790
|
ring.minSeq = ring.events[0].seq;
|
|
764
791
|
}
|
|
765
792
|
}
|
|
766
|
-
|
|
793
|
+
/** Persist the full ring synchronously (atomic tmp+rename) and clear any
|
|
794
|
+
* B5-1 pending-debounce marker for this session. */
|
|
795
|
+
function persistNow(sessionId, ring) {
|
|
767
796
|
const file = persistedPath(sessionId);
|
|
797
|
+
pendingPersists.delete(sessionId);
|
|
768
798
|
if (!file)
|
|
769
799
|
return;
|
|
770
|
-
|
|
800
|
+
persistWriter(file, ringPayload(sessionId, ring));
|
|
801
|
+
}
|
|
802
|
+
/** B5-1: defer the persist of a `message_update` append. The window is driven
|
|
803
|
+
* by the append `at` clock (mirroring `shouldCoalesce`) — a structural event,
|
|
804
|
+
* turn change, elapsed window or the pending-count cap each force a real
|
|
805
|
+
* flush, so a streaming turn no longer serializes the whole ring per token. */
|
|
806
|
+
function persistDebounced(sessionId, ring, atMs) {
|
|
807
|
+
const file = persistedPath(sessionId);
|
|
808
|
+
if (!file)
|
|
809
|
+
return;
|
|
810
|
+
const pending = pendingPersists.get(sessionId);
|
|
811
|
+
if (!pending) {
|
|
812
|
+
pendingPersists.set(sessionId, {
|
|
813
|
+
firstPendingAtMs: atMs,
|
|
814
|
+
pendingUpdates: 1,
|
|
815
|
+
});
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
pending.pendingUpdates += 1;
|
|
819
|
+
if (atMs - pending.firstPendingAtMs >= persistDebounceMs ||
|
|
820
|
+
pending.pendingUpdates >= persistMaxPendingUpdates) {
|
|
821
|
+
persistNow(sessionId, ring);
|
|
822
|
+
}
|
|
771
823
|
}
|
|
772
824
|
function ringFor(sessionId) {
|
|
773
825
|
let ring = rings.get(sessionId);
|
|
@@ -870,7 +922,7 @@ export function createChatEventStore(options) {
|
|
|
870
922
|
};
|
|
871
923
|
ring.turns.push(turn);
|
|
872
924
|
ring.approxBytes += byteHint(turn);
|
|
873
|
-
|
|
925
|
+
persistNow(sessionId, ring);
|
|
874
926
|
return { ok: true, turn: { ...turn }, created: true, idempotent: false };
|
|
875
927
|
},
|
|
876
928
|
setTurnState(sessionId, turnId, state, error) {
|
|
@@ -889,7 +941,7 @@ export function createChatEventStore(options) {
|
|
|
889
941
|
turn.error = error;
|
|
890
942
|
ring.approxBytes +=
|
|
891
943
|
(JSON.stringify(turn).length - before) * BYTE_GATE_SCALE;
|
|
892
|
-
|
|
944
|
+
persistNow(sessionId, ring);
|
|
893
945
|
return { ...turn };
|
|
894
946
|
},
|
|
895
947
|
markAbortRequested(sessionId, turnId, at) {
|
|
@@ -902,7 +954,7 @@ export function createChatEventStore(options) {
|
|
|
902
954
|
turn.abortRequestedAt = at ?? new Date().toISOString();
|
|
903
955
|
ring.approxBytes +=
|
|
904
956
|
(JSON.stringify(turn).length - before) * BYTE_GATE_SCALE;
|
|
905
|
-
|
|
957
|
+
persistNow(sessionId, ring);
|
|
906
958
|
return { ...turn };
|
|
907
959
|
},
|
|
908
960
|
findTurnByClientRequestId(sessionId, clientRequestId) {
|
|
@@ -962,7 +1014,23 @@ export function createChatEventStore(options) {
|
|
|
962
1014
|
ring.approxBytes += byteHint(live);
|
|
963
1015
|
}
|
|
964
1016
|
enforceBounds(sessionId, ring);
|
|
965
|
-
persist
|
|
1017
|
+
// B5-1: only high-frequency `message_update` appends defer the persist;
|
|
1018
|
+
// every other kind (message_end / tool_call / error / turn signals …)
|
|
1019
|
+
// still persists synchronously. Because persistNow writes the FULL
|
|
1020
|
+
// in-memory ring, one flush also lands any earlier pending updates in
|
|
1021
|
+
// order — no two-phase flush is needed.
|
|
1022
|
+
if (partial.kind === "message_update") {
|
|
1023
|
+
const atMs = partial.at ? Date.parse(partial.at) : Date.now();
|
|
1024
|
+
if (!Number.isNaN(atMs)) {
|
|
1025
|
+
persistDebounced(sessionId, ring, atMs);
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
persistNow(sessionId, ring);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
persistNow(sessionId, ring);
|
|
1033
|
+
}
|
|
966
1034
|
const subs = subscribers.get(sessionId);
|
|
967
1035
|
if (subs) {
|
|
968
1036
|
for (const fn of subs) {
|
|
@@ -1017,7 +1085,7 @@ export function createChatEventStore(options) {
|
|
|
1017
1085
|
const ring = ringFor(sessionId);
|
|
1018
1086
|
const next = (turnCounts.get(sessionId) ?? 0) + 1;
|
|
1019
1087
|
turnCounts.set(sessionId, next);
|
|
1020
|
-
|
|
1088
|
+
persistNow(sessionId, ring);
|
|
1021
1089
|
return next;
|
|
1022
1090
|
},
|
|
1023
1091
|
latestTurnId(sessionId) {
|
|
@@ -1043,6 +1111,9 @@ export function createChatEventStore(options) {
|
|
|
1043
1111
|
rings.delete(sessionId);
|
|
1044
1112
|
turnCounts.delete(sessionId);
|
|
1045
1113
|
subscribers.delete(sessionId);
|
|
1114
|
+
// B5-1: drop any debounced (not yet written) updates with the ring itself —
|
|
1115
|
+
// the file is being deleted, so flushing first would only resurrect it.
|
|
1116
|
+
pendingPersists.delete(sessionId);
|
|
1046
1117
|
const file = persistedPath(sessionId);
|
|
1047
1118
|
if (file)
|
|
1048
1119
|
rmSync(file, { force: true });
|