@tea-agent/loop-agent 0.42.1 → 0.43.0-next.10
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 +63 -3
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +5 -20
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +44 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +202 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +78 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +86 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +38 -0
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +28 -0
- package/dist/worker/console/chat/pi-runtime/inspection.js +439 -0
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +153 -0
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +5 -0
- package/dist/worker/console/chat/pi-runtime.js +100 -1104
- package/dist/worker/console/chat/routes.js +32 -17
- package/dist/worker/console/chat/terminal-sessions.js +65 -1
- package/dist/worker/console/chat/terminal-tools.js +17 -5
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C9eI7nEo.js → abnfDiagram-N423BO3Z-D_fNbWc_.js} +1 -1
- package/dist/worker/console/static/assets/{arc-VJWsxWhB.js → arc-_HuqLW6m.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BGcyiMSo.js → architectureDiagram-T3A2C74G-Djy2qY1m.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-BScnFwyi.js → blockDiagram-VBNYF7ZC-bB-1TSyY.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bl9_BsLi.js → c4Diagram-5PPSVZJV-C5Hmghrz.js} +1 -1
- package/dist/worker/console/static/assets/channel-C0A6UEIa.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-F1uKxiUt.js → chunk-2GRJ4B5K-Duyl39oy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-rSRMynVu.js → chunk-2Q5K7J3B-x6mdnSyf.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DAsJm1WD.js → chunk-5RXB4S5H-WlBuFj7W.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DQIdVI0a.js → chunk-5VM5RSS4-CTF429HX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4A_8rJ-Q.js → chunk-6Q2QTUOP-Dwd6r-SF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-BHnT-vjh.js → chunk-GF5L2VYU-C-Br82D1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-Rq7QKBkn.js → chunk-JWPE2WC7-CuGp0DeE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BXh-AI2u.js → chunk-KBJHAD2P-CJhFX6Lh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BblcLy8m.js → chunk-RYQCIY6F-DpGOa_jT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-zHZmVnB6.js → chunk-XXDRQBXY-DXJaTynd.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DzguNppy.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DzguNppy.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-Cmwz0Wi4.js → cose-bilkent-JH36ORCC-CRBZWjE4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Dq76MHQU.js → cynefin-VYW2F7L2-BdXcxtlb.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-zPahV1fM.js → cynefinDiagram-MW4NZA55-07UKsOts.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-DQJ3xPc_.js → dagre-VZM6K2ZE-D38p9__S.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BwX7u6fv.js → diagram-7IWD3JNH-nSiBNNjH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CodnDCco.js → diagram-B4RE2ZJO-BFOTot5D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-gAjtWkxG.js → diagram-LBJQPF4R-DypKaZ6k.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CGCqz0cS.js → diagram-Q27KOJAE-B6yoOQeW.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-RHwYEywd.js → diagram-UB23O5K3-BHYS6viX.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-B6seb_p_.js → ebnfDiagram-BXEA7PRR-D2VD5Snj.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-CmwCKYe8.js → erDiagram-JOGREHBK-E98mWxN1.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BfNsaZ0V.js → flowDiagram-UKHOOZJN-CT1zxhqP.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-zmYgi_Z3.js → ganttDiagram-PKOTCBZU-6GcngNcy.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BDEzzSKF.js → gitGraphDiagram-DS77QQ5N-DCL6NvKe.js} +1 -1
- package/dist/worker/console/static/assets/{index-DgenUAfc.js → index-24jp3aBh.js} +77 -77
- package/dist/worker/console/static/assets/index-CY6y6I8M.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DeSz8l42.js → infoDiagram-6WML65LV-C0627e-9.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B1LV29aH.js → ishikawaDiagram-WSZJBQD7-C_u3P6Ik.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CCw4W2oU.js → journeyDiagram-NVQOT4AX-DPDPv9HL.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BozaMoRJ.js → kanban-definition-27J2QSJJ-D3OHdWiO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-G1JR40SX.js → linear-DI88zD7n.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CCgPF0oA.js → mermaid.core-p2l8IrWY.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CEqnCHnD.js → mindmap-definition-FAOFIHXS-B1bAX-oS.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DpmQPKuI.js → pegDiagram-VL7TDLO6-DwOnWxdD.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BP501AxR.js → pieDiagram-7S7Q4E2Y-xRkTXnJC.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-D1dBhl_A.js → quadrantDiagram-CIZ2JOQS-_9UC5Zio.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DgZDS5pY.js → railroadDiagram-AXF67PYL-CgEUirWJ.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BzB3pyIq.js → requirementDiagram-LRYGKXZP-Czbpwiyz.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B9ZprFFP.js → sankeyDiagram-W5VNT64P-_j7_KiqY.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CC2-_XrA.js → sequenceDiagram-SI44F4Z6-BNJa6Hyi.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UVtAUqgB.js → sizeCapture-X5ZJPWSS-DedpKtOc.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CWcS5JYG.js → stateDiagram-OKZ733FA-BBvthr7c.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BABVQnfM.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-C4p-dn06.js → swimlanes-SLNWSIFB-Ds7umgEM.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D0qsLaxR.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Dnz52OHX.js → timeline-definition-Z64GVDOM-CKa-p1nF.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CzTE0Nfu.js → vennDiagram-T6HMQDX7-LoUNR56o.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-NAR9cjQQ.js → wardleyDiagram-T6FBY63Y-Bqbl6ym7.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DVMk8413.js → xychartDiagram-ELKLHX3M-B8dUg4lf.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +10 -0
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +7 -0
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +13 -6
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +2 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
- package/dist/worker/observe/static/styles.css +7 -4
- package/dist/worker/observe/static/views/dag-inspector.js +5 -19
- package/package.json +1 -1
- package/dist/worker/console/static/assets/channel-B6sQYuOE.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +0 -1
- package/dist/worker/console/static/assets/index-aF4u-Y__.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,19 +2,79 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### 改进
|
|
6
|
+
|
|
7
|
+
- 前端 Plan 普通分片按本批完整需求材料打包,固定提示词、用户消息和工具定义不再挤占 24 KB 分片目标,也不再触发发送前的机械二分,减少小需求逐 AC 会话;实际模型容量检查与超限恢复继续生效。
|
|
8
|
+
|
|
9
|
+
- 前端 Plan 小任务合并规划不再受固定 48 KB 输入门槛限制,避免提示词、用户消息或工具定义变长导致额外分片;完整输入、其余任务规模条件及容量恢复机制保持不变。
|
|
10
|
+
- 节点侧栏修复执行分次下拉菜单被执行记录工具栏遮挡的问题,直接展示所选失败尝试的简要原因,并移除重复的左右切换箭头。
|
|
11
|
+
- Operator Chat 的 Pi Runtime 按职责拆出工具构造、运行态检视和 SDK 适配,并共用创建/恢复/分叉的会话准备流程;对外入口、错误类型和会话行为保持不变。
|
|
12
|
+
|
|
5
13
|
## [0.42.1] - 2026-09-09
|
|
6
14
|
|
|
7
15
|
### 新增
|
|
8
16
|
|
|
9
|
-
-
|
|
17
|
+
- Operator Chat 浏览器面板(DSH 风格):Console 右侧辅助栏新增「浏览器」tab——地址栏(自动补 https、仅 http/https、禁止 javascript/data/file 等危险协议与本机回环地址)、后退/前进/刷新导航栈、系统浏览器跳转;页面运行在无 same-origin、无 top-navigation 的沙箱 iframe 中(访问的页面拿不到 Console 的存储与 API),沙箱状态条支持单页面临时解锁(红色警示 + 一键恢复);对拒绝被嵌入的站点(X-Frame-Options / frame-ancestors)给出明确解释面板与「在系统浏览器打开 / 仍要加载」出口,探测端点仅返回响应头、仅限 http(s) 非回环、8 秒硬超时。
|
|
18
|
+
- Operator Chat 源代码管理(DSH 风格):模型获得 `scm` 工具(status/diff/stage/unstage/discard/commit/log/branch/push/pull,固定参数 git 无 shell、限时限量输出);Console 右侧辅助栏新增「源码」面板(DSH GitView 布局——分支下拉切换、已暂存/更改分组与全部暂存/取消暂存、单行提交框 Ctrl+Enter、内联分页历史带 refs 徽标与相对时间、点击文件看 diff、右键菜单放弃更改/复制路径);模型与面板共用同一工作区级 ScmService。
|
|
19
|
+
- Operator Chat 在具备 Aimax 凭据资格时,向对话进程注入 `$AIMAX_AUTH_TOKEN`(明文或 `sm4:v1:` 解密后的 ticket),供 skills 读取;文件缺失、无法解析或解密失败时不注入该变量。
|
|
20
|
+
- Operator Chat 终端管理(DSH 风格):模型获得 `terminal` 持久 PTY 工具(跨平台 shell 目录——Windows 提供 powershell/bash/cmd,macOS/Linux 提供 bash/zsh,pwsh 7 装了即用;cwd/环境变量/后台任务跨调用保留,完成标记协议 + 超时与截断保护,枚举与默认值按平台动态生成);Console 右侧辅助栏新增「终端」面板(xterm.js 渲染,实时查看/接管模型的 shell,按平台动态展示可创建的 shell,支持发送输入、Ctrl+C、终止并关闭);模型 shell 与用户面板共用同一会话注册表并开在工作区仓库根目录,命名区分来源。
|
|
21
|
+
- Windows 原生 `cmd.exe` 终端体验补全:仅 Windows 平台提供 `cmd` shell,使用经典 `C:\path>` 提示符、cmd 黑底浅灰配色与方块光标,并补回 ConPTY 丢失的 `Microsoft Windows [版本 …]` 启动横幅;bash 与 PowerShell 保持各自原生输出,不注入 Windows 横幅。
|
|
22
|
+
- Operator Chat 详情栏调整:「源码」tab 紧邻「文件」tab,便于在文件预览与源码管理之间快速切换。
|
|
10
23
|
|
|
11
24
|
### 改进
|
|
12
25
|
|
|
13
|
-
-
|
|
26
|
+
- 修复前端设计评审与实施评审遗漏 `complete_review_scope` 工具权限,导致模型无法完成分段确认、最终评审反复被拒绝的问题;补充真实 Pi SDK 工具激活与分段收口回归验证。
|
|
27
|
+
|
|
28
|
+
- 对话正文与 Human Gate、任务活动和产物卡片统一绑定会话所属工作区;修正旧式任务/运行链接,只有操作编号时查询真实记录再定位,工作区冲突或记录缺失时明确提示,避免误入其他工作区或总览。
|
|
29
|
+
|
|
30
|
+
- 节点侧栏在首次原始运行中隐藏「来源与影响」,派生运行保留该入口;打开运行中节点默认定位到「执行记录」,手动选择和明确的链接位置在刷新时保持。
|
|
31
|
+
|
|
32
|
+
- 修复前端契约可选字段未填写时,评审分片索引引用不存在字段、导致设计评审启动前失败的问题。分片生成跳过 undefined;读取端明确拒绝缺失字段、无效引用、重复或非法范围及未索引内容,保留合法空数组和 JSON 空值。
|
|
33
|
+
|
|
34
|
+
- Operator Chat 侧栏「添加工作区」图标改为与 DSH 一致的文件夹加号,不再用纯加号。
|
|
35
|
+
- Operator Chat 侧栏改为单一「新会话」;空白会话在输入框上方用芯片选择工作区和模式(工作流 / Pi),有历史后芯片收起。不再用落地页双卡或「新 Pi」按钮;换模式会复用或新建对应空白会话,不会改已有会话的模式。
|
|
36
|
+
- 修复模型续跑取消后带部分文字仍被标记成功的问题;前端 Plan 的取消、认证、限流及策略失败优先于容量恢复,精确格式错误保留原修复路径。完整性检查按需求建立单次索引,减少大计划重复扫描,同时拆分纯检查与恢复策略模块。
|
|
37
|
+
- 长会话中的输入框不再因历史消息和工具记录增多而逐键重绘整段会话,输入与自动增高在低性能电脑上更流畅。
|
|
38
|
+
- 优化 Operator Chat 长会话的流式渲染:完成回合、消息行、工具卡和用户输入锚点建立稳定的 memo 边界,流式文本更新不再重复渲染整段历史或重装锚点测量监听;缓存会在历史消息或工具实际更新时正确失效;无新增折叠回合时不再提交空状态及重复写入本地存储,工具参数与结果格式化按真实输入变化缓存。
|
|
39
|
+
- 对话页「过程折叠」对齐 DSH 细节:模型回复完成时直接进入折叠态,不再先展开再跳动;折叠摘要、过程内容和最终回答采用与 DSH 一致的间距,折叠内容仍保留在页面中,浏览器 Ctrl+F 可直接命中并自动展开;折叠行展开时不再多出底部空隙;箭头改为 16px、颜色更淡的固定图标(靠旋转而非换图标),并补齐系统「减少动态效果」偏好;汇总文案新增「N 个 subagent」,内置 Explore 子代理不再计入普通工具调用。
|
|
40
|
+
- 恢复前端 Plan 实时事实分页读取、逐需求 UX 遗漏检查与已完成范围复用;超限时补齐剩余验证目标,并接通 DAG 到 SDK 的原会话续跑。局部状态修改保留其他需求的关联,沿用耐久账本、显式替换和冻结分片边界。
|
|
41
|
+
|
|
42
|
+
- DAG 的 Pi SDK 节点遇到 `stopReason=length` 时,先在原会话内最多续跑 3 次,保留已完成写入和事实,成功恢复不消耗额外节点重试次数;共享原超时与上下文预算,已成功提交终态时不重复续跑,耗尽后才进入原有缩片或重试路径。
|
|
43
|
+
- 修复前端 Design Review 成功后可能清空原 Plan 账本的问题,撤回未接通合同校验和物化的修订工具;同时移除误加在实施后 Review 的修订指令。Review 节点内局部修订尚未实现,设计缺陷仍走原有拒绝与恢复流程。
|
|
44
|
+
- 前端 Plan 输出超限后按已提交事实续跑:带部分结果或网关诊断也会保留进度,自动缩小剩余需求;单个过大需求进一步按缺失事实逐项恢复,并在恢复耗尽时输出剩余范围。
|
|
45
|
+
- 大需求前端 Plan 增加可分页的实时事实读取,保留长标识和跨批验收绑定;UX 按有限需求范围执行,重试复用完整批次和全局命名表,逐需求检查遗漏。冻结交付文件超过单 writer 容量时在 Contract 后提前识别拆分需求;自动子任务执行链路尚未接通,此项不代表自动拆分已可用。
|
|
46
|
+
|
|
47
|
+
- 前端 DAG 修正否定约束导致的小任务误升级;Plan 在分片前统一 compact 判定并保留完整执行组。支持冻结 coverage 并发上限(1–4,默认 4),限流后减少后续并发且不增加重试。自动恢复子进程与 DAG 共用长跑超时,避免继承通用 120 秒上限而中途终止。
|
|
48
|
+
- 前端 DAG 保留完整需求并按作用域/执行组分批,工具提交改为先校验和耐久保存再回执;支持按组共享覆盖、逐端点/逐 finding 提交和缺项恢复。小任务走短流程,容量耗尽直接缩小剩余工作;模型窗口不再硬编码为 100K/16K。独立 AC、来源保真与 writer/verify 门禁保持有效。
|
|
49
|
+
- 观测台阅读与口径修补:失败节点的产出页把「错误输出」提到正文之前,未登记文件关系时不再展示空文件卡;检查器 Tab 条放不下时两端渐隐提示并保证选中 Tab 可见,每页顶部说明卡可收起。异常 Task 的分类与建议跟进显示中文业务术语(未知 token 原样透传并可悬停看原始值),资源池「合计」改为口径明确的「已建 Task」并附悬停说明,数据更新文案改为「数据更新于 … · 仅提示」。DAG 详情面包屑使用可读运行标题(缺失回退运行 ID、窄屏截断),「记录保存位置」移入「诊断信息」折叠区,页面刷新按钮与标题同行靠右。
|
|
50
|
+
- 观测台保留检查器 Tab 导航,日志分类收进下拉框,辅助操作改为带提示的图标;所有执行记录区域(检查器执行记录/产出、运行页最新输出)统一配备对话式「回到底部」悬浮按钮:内容可滚动且视口离开底部即出现,贴底或内容不足一屏时隐藏,点击回到底部;活跃运行同时恢复跟随并提示未读条数,终态运行同样可用。执行分次仅在相关页面固定显示,上翻暂停跟随,并记住分次阅读位置。补充日志搜索、错误过滤、图定位和完整脱敏文本下载。
|
|
51
|
+
- 实际上下文改为中文材料目录优先、技术信息折叠;修复历史轮次混读当前上下文和旧请求覆盖新尝试的问题。保留仅看错误与原始协议事件,原始协议使用直接可达的图标开关,移除单项更多菜单和日志 HTML 导出;交接包明确固定来源并支持刷新。
|
|
52
|
+
- 检查器隐藏无内容或明确不适用的入口:单次执行不显示尝试对比,Shell/静态节点不显示实际上下文,静态节点不显示执行记录;切换节点与新增尝试时自动更新,保留用户当前的比较选择。
|
|
53
|
+
- 资源池筛选覆盖任务标题和全部任务区域,返回列表及历史分页保留阅读状态;重跑草稿和影响预览可恢复,结果不确定时原样重试已确认的请求,避免重复创建运行。
|
|
54
|
+
- 执行记录默认跟随最新内容;上翻自动暂停并出现对话式「回到底部」按钮,点击回到底部;历史记录保留阅读位置,终态运行同样可用。
|
|
55
|
+
- DAG 节点检查器仅对最新且仍运行的执行默认跟随;右下角显示阅读状态、新记录提示和“回到最新”,上翻即暂停。
|
|
14
56
|
|
|
15
57
|
### 修复
|
|
16
58
|
|
|
17
|
-
-
|
|
59
|
+
- 悬停或打开会话「更多」菜单时,Pi / 工作流徽章与相对时间一并让出,避免被三个点按钮盖住。
|
|
60
|
+
- 修复点「新 Pi」后一发消息侧栏就变成「工作流」:全局会话列表和点进会话时漏了 `sessionMode`,缺省被当成工作流。
|
|
61
|
+
- 修复手动压缩上下文后,长会话会清空并重复绘制大量历史内容的问题;压缩结果现在会直接在当前消息流中更新,不再触发整段会话重载。
|
|
62
|
+
- 修复对话页存在运行活动时,收起状态的右侧辅助栏展开按钮覆盖「运行活动 N」及数字徽标的问题;中大尺寸布局现在保留 8px 操作间距,窄屏布局不变。
|
|
63
|
+
- 修复 Operator Chat 消息流的六处实际使用问题:
|
|
64
|
+
- **回复被吞**:断线/重连(含每 25 秒的 SSE 事件窗口轮转)后,保留事件里已经生成的正文会在事件流恢复时重新应用到对应回合,不再被静默丢弃;仅当该回合已有更新内容时才跳过,避免重复气泡。
|
|
65
|
+
- **用户输入消失或重复**:新消息发送后会在每一帧模型输出中持续显示,不再等回复结束才重新出现;服务端确认后,提交回滚(排队抢占、被拒绝、连接失败)不再删掉已确认的消息;确认帧到达时会原地合并临时消息与会话快照,不再重复显示同一条输入。
|
|
66
|
+
- **回复重复**:上下文溢出自动压缩并重新生成后,被打断的旧回复会被取代,不再与重新生成的回复同时显示。
|
|
67
|
+
- **只有思考没有正文**:模型回复以工具调用或思考结尾时,正文会作为回答显示,不再被折叠进「过程」区。
|
|
68
|
+
- **空气泡误删**:只包含思考或工具内容、正文为空的回合不再被清理掉。
|
|
69
|
+
- **停止语义**:被停止中断的工具调用显示「已停止」,不再显示为「出错」并强制展开、计入「N 次出错」。
|
|
70
|
+
|
|
71
|
+
- 任务成功与收尾只认可匹配当前契约版本和哈希的运行;更新需求后重新审查 gate,旧成功记录不再阻止新运行。需求独立审核接受 `revision: null` 表示无修订,保留拒绝结论和身份校验;相同角色、相同内容的 PRD 重复导入复用不可变引用,不再重复增加副本或无故推进契约版本。
|
|
72
|
+
|
|
73
|
+
- 前端 Plan 重试复用首次冻结的分片归属,避免重试提示词变长导致进度绑定不匹配或重复处理已完成需求;布局缺失、损坏或漂移时保留证据并阻断派发。风险路由仅忽略完整的范围排除声明,条件句和嵌套否定中的真实权限风险在生成期与运行期均保持升级。
|
|
74
|
+
- 执行记录搜索直接高亮命中文字,标识当前跳转记录;未搜索或无匹配时禁用上下箭头,清空搜索恢复原文。
|
|
75
|
+
|
|
76
|
+
- 恢复运行在导入节点缺少明确读取范围时阻断工作区漂移,避免复用受变更影响的旧结果;读取范围明确且不受影响时仍可按路径复用。
|
|
77
|
+
- 前端验证将子目录执行的 Vitest 报告统一为仓库相对路径,并对齐实际目录别名,避免已通过测试被误判为未执行;被筛除或失败的目标仍阻断。
|
|
18
78
|
|
|
19
79
|
## [0.42.0] - 2026-09-07
|
|
20
80
|
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-09-
|
|
3
|
+
"version": "0.43.0-next.10",
|
|
4
|
+
"gitSha": "a57ccda19e1372045d03071a01e7e6df1be2e942",
|
|
5
|
+
"builtAt": "2026-09-10T04:37:15.842Z"
|
|
6
6
|
}
|
|
@@ -4512,16 +4512,18 @@ function buildFrontendPlanWorkload(input) {
|
|
|
4512
4512
|
const workCost = (ids) => 1 + ids.reduce((total, id) => total + Math.max(1, (input.requirementCosts?.get(id) ?? 2) - 1), 0);
|
|
4513
4513
|
const policy = input.sessionOptions.frontendExecutionPolicy;
|
|
4514
4514
|
const targetBytes = policy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES;
|
|
4515
|
-
const userMessageBytes = Buffer.byteLength(input.sessionOptions.userMessage ?? "");
|
|
4516
4515
|
const buildWorkBatches = (ids) => {
|
|
4517
4516
|
const work = workGroups.flatMap((g, index) => {
|
|
4518
4517
|
const members = g.requirementIds.filter(id => ids.includes(id));
|
|
4519
4518
|
return members.length ? [{ id: `${index}:${g.id}`, requirementIds: members,
|
|
4520
4519
|
requirements: members.map(id => fullUnits.get(id) ?? { id }), estimatedCalls: workCost(members) }] : [];
|
|
4521
4520
|
});
|
|
4522
|
-
|
|
4521
|
+
// Pack complete work units, not the repeated request scaffold. Deducting
|
|
4522
|
+
// fixed context/tools can leave a one-byte budget and force one AC per
|
|
4523
|
+
// session without reducing that overhead. The SDK checks the actual
|
|
4524
|
+
// request against model capacity; capacity recovery splits unfinished work.
|
|
4523
4525
|
return packFrontendInputUnits(work, {
|
|
4524
|
-
targetBytes
|
|
4526
|
+
targetBytes,
|
|
4525
4527
|
maxUnits: policy?.maxScopeUnits ?? 4,
|
|
4526
4528
|
maxCost: FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS, cost: g => g.estimatedCalls,
|
|
4527
4529
|
}).map(batch => batch.flatMap(g => g.requirementIds));
|
|
@@ -4533,7 +4535,6 @@ function buildFrontendPlanWorkload(input) {
|
|
|
4533
4535
|
frozenInput, workGroups, buildWorkBatches,
|
|
4534
4536
|
compactEligible: input.requirementCosts !== undefined &&
|
|
4535
4537
|
workGroups.length <= FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS &&
|
|
4536
|
-
Buffer.byteLength(input.basePrompt) + userMessageBytes + Buffer.byteLength(JSON.stringify(input.allTools)) <= targetBytes * 2 &&
|
|
4537
4538
|
workGroups.reduce((total, group) => total + workCost(group.requirementIds), 0) <= FRONTEND_PLAN_SMALL_MAX_ESTIMATED_CALLS &&
|
|
4538
4539
|
countFrontendPlanTargetSurfaces(input.basePrompt) === 1,
|
|
4539
4540
|
};
|
|
@@ -4785,8 +4786,6 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4785
4786
|
const { workGroups, buildWorkBatches, compactEligible } = buildFrontendPlanWorkload({
|
|
4786
4787
|
basePrompt: input.basePrompt, requirementIds: allRequirementIds,
|
|
4787
4788
|
requirementCosts: input.requirementCosts, sessionOptions: input.sessionOptions,
|
|
4788
|
-
coverageTools: input.segmentCustomTools(coverageSegment.toolNames),
|
|
4789
|
-
allTools: input.segmentCustomTools(null),
|
|
4790
4789
|
});
|
|
4791
4790
|
// Small, single-surface requests do not benefit from six isolated Pi
|
|
4792
4791
|
// sessions. Keep the typed ledger as the authority, but let one local
|
|
@@ -4971,17 +4970,6 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4971
4970
|
"state-flow": "record_state_flow", "data-flow": "record_data_flow", "mock-api": "record_mock_api",
|
|
4972
4971
|
};
|
|
4973
4972
|
const customTools = input.segmentCustomTools(atomicFocus ? new Set([atomicTools[atomicFocus.kind]]) : session.toolNames);
|
|
4974
|
-
const scopeForPacking = session.coverageSlice ?? session.requirementSlice;
|
|
4975
|
-
const envelopeBytes = Buffer.byteLength(prompt) + Buffer.byteLength(input.sessionOptions.userMessage ?? "") + Buffer.byteLength(JSON.stringify(customTools));
|
|
4976
|
-
if (envelopeBytes > (input.sessionOptions.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES) && scopeForPacking && session.id !== "compact-local") {
|
|
4977
|
-
const groups = workGroups.map(g => g.requirementIds.filter(id => scopeForPacking.includes(id))).filter(g => g.length);
|
|
4978
|
-
if (groups.length > 1) {
|
|
4979
|
-
const half = Math.ceil(groups.length / 2);
|
|
4980
|
-
queue.splice(index, 1, ...[groups.slice(0, half).flat(), groups.slice(half).flat()].map(slice => ({ ...session, ...(session.coverageOnly ? { coverageSlice: slice } : { requirementSlice: slice }) })));
|
|
4981
|
-
invocationCount -= 1;
|
|
4982
|
-
continue;
|
|
4983
|
-
}
|
|
4984
|
-
}
|
|
4985
4973
|
const result = await observeFrontendSession({
|
|
4986
4974
|
...input.observation, phase: `plan/${session.id}`, scopeIds: session.requirementSlice ?? session.coverageSlice ?? allRequirementIds,
|
|
4987
4975
|
prompt, userMessage: input.sessionOptions.userMessage, customTools, committedCount: input.committedFactCount, durableCommittedCount: () => lastDurableCount,
|
|
@@ -6088,12 +6076,9 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
6088
6076
|
flushLedger: () => options.ledgerTools.flush(),
|
|
6089
6077
|
});
|
|
6090
6078
|
if (planRequirementIds.length > 1) {
|
|
6091
|
-
const coverageTools = FRONTEND_PLAN_SEGMENTS.find(segment => segment.id === "coverage").toolNames;
|
|
6092
6079
|
const workload = buildFrontendPlanWorkload({
|
|
6093
6080
|
basePrompt: input.prompt, requirementIds: planRequirementIds,
|
|
6094
6081
|
requirementCosts: planRequirementCosts, sessionOptions: piSessionOptions,
|
|
6095
|
-
allTools: planLedgerTools.customTools,
|
|
6096
|
-
coverageTools: planLedgerTools.customTools.filter(tool => isRecordObject(tool) && coverageTools.has(String(tool.name))),
|
|
6097
6082
|
});
|
|
6098
6083
|
let coverageBatches;
|
|
6099
6084
|
try {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-bound ask_user_question custom tool.
|
|
3
|
+
* ConsolePiRuntime supplies the sessionId and UserQuestionRegistry.
|
|
4
|
+
*/
|
|
5
|
+
const ASK_USER_QUESTION_DESCRIPTION = "Ask the user concise questions when a confirmation, choice, or missing detail is required before proceeding. " +
|
|
6
|
+
"The tool pauses until the user answers. Use it instead of writing questions as ordinary assistant text. " +
|
|
7
|
+
"Send one or more questions with stable ids; recommended options should be first and end with (Recommended).";
|
|
8
|
+
export async function buildAskUserQuestionTool(options) {
|
|
9
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
10
|
+
import("typebox"),
|
|
11
|
+
import("@earendil-works/pi-coding-agent"),
|
|
12
|
+
]);
|
|
13
|
+
return defineTool({
|
|
14
|
+
name: "ask_user_question",
|
|
15
|
+
label: "Ask user",
|
|
16
|
+
description: ASK_USER_QUESTION_DESCRIPTION,
|
|
17
|
+
promptSnippet: ASK_USER_QUESTION_DESCRIPTION,
|
|
18
|
+
parameters: Type.Object({
|
|
19
|
+
questions: Type.Array(Type.Object({
|
|
20
|
+
id: Type.String({ description: "Stable question id echoed in the answer." }),
|
|
21
|
+
question: Type.String({ description: "Specific user-facing question." }),
|
|
22
|
+
header: Type.Optional(Type.String({ description: "Optional short heading." })),
|
|
23
|
+
detail: Type.Optional(Type.String({ description: "Optional Markdown context shown below the question." })),
|
|
24
|
+
options: Type.Optional(Type.Array(Type.Object({
|
|
25
|
+
label: Type.String({ description: "Short user-facing option label." }),
|
|
26
|
+
description: Type.Optional(Type.String({ description: "One sentence explaining impact or tradeoff." })),
|
|
27
|
+
}, { additionalProperties: false }))),
|
|
28
|
+
multi_select: Type.Optional(Type.Boolean({ description: "Allow more than one option." })),
|
|
29
|
+
}, { additionalProperties: false }), { minItems: 1, maxItems: 20 }),
|
|
30
|
+
}, { additionalProperties: false }),
|
|
31
|
+
async execute(toolCallId, params, signal) {
|
|
32
|
+
const answer = await options.registry.ask({
|
|
33
|
+
sessionId: options.sessionId,
|
|
34
|
+
toolCallId,
|
|
35
|
+
questions: params.questions,
|
|
36
|
+
signal,
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: JSON.stringify(answer) }],
|
|
40
|
+
details: answer,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-less safe explore custom tools (safe-read / safe-grep / git-status /
|
|
3
|
+
* git-diff). Execution uses explore-tools.ts path guards and secret scrubbing.
|
|
4
|
+
* ConsolePiRuntime owns the sessionless cache.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Build the safe explore custom tools (roadmap M0-A / G16): safe-read,
|
|
8
|
+
* safe-grep, gitStatus, gitDiff. These replace the bare SDK bash/read/grep so
|
|
9
|
+
* the model can probe the repo WITHOUT write escapes (echo redirect, tee,
|
|
10
|
+
* sed -i, node -e) and WITHOUT reading sensitive files (env files, key/pem,
|
|
11
|
+
* auth.json, sessions dir) — and every returned byte is secret-scrubbed
|
|
12
|
+
* before reaching the model context.
|
|
13
|
+
*
|
|
14
|
+
* gitStatus/gitDiff use a FIXED argv (typed, no shell) so they cannot be
|
|
15
|
+
* turned into an arbitrary-shell vector.
|
|
16
|
+
*/
|
|
17
|
+
export async function buildExploreCustomTools(options) {
|
|
18
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
19
|
+
import("typebox"),
|
|
20
|
+
import("@earendil-works/pi-coding-agent"),
|
|
21
|
+
]);
|
|
22
|
+
const { execFileSync } = await import("node:child_process");
|
|
23
|
+
const repoRoot = options.repoRoot;
|
|
24
|
+
const { safeReadFile, safeGrep, scrubSecrets } = await import("../../explore-tools.js");
|
|
25
|
+
function safeGit(args) {
|
|
26
|
+
try {
|
|
27
|
+
const stdout = execFileSync("git", args, {
|
|
28
|
+
cwd: repoRoot,
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
// Fixed argv, no shell, bounded output.
|
|
31
|
+
maxBuffer: 512 * 1024,
|
|
32
|
+
timeout: 10_000,
|
|
33
|
+
windowsHide: true,
|
|
34
|
+
});
|
|
35
|
+
return { ok: true, stdout };
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
code: "GIT_FAILED",
|
|
41
|
+
message: e instanceof Error ? e.message : String(e),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const safeReadTool = defineTool({
|
|
46
|
+
name: "safe-read",
|
|
47
|
+
label: "safe-read",
|
|
48
|
+
description: "Read a repo-relative file with sensitive-file denylist (.env*/*.key/*.pem/.git/**/auth.json/sessions/**) and secret scrubbing. Absolute paths and .. are rejected.",
|
|
49
|
+
promptSnippet: "safe-read: read a repo file (safe; secrets scrubbed)",
|
|
50
|
+
parameters: Type.Object({
|
|
51
|
+
path: Type.String({ description: "repo-relative file path" }),
|
|
52
|
+
}, { additionalProperties: false }),
|
|
53
|
+
async execute(_toolCallId, params) {
|
|
54
|
+
const raw = String(params.path ?? "");
|
|
55
|
+
try {
|
|
56
|
+
const result = await safeReadFile(repoRoot, raw);
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
59
|
+
details: result,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
const err = e;
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: JSON.stringify({
|
|
69
|
+
error: err.message ?? String(e),
|
|
70
|
+
code: err.code ?? "READ_FAILED",
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
details: {
|
|
75
|
+
error: err.message ?? String(e),
|
|
76
|
+
code: err.code ?? "READ_FAILED",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const safeGrepTool = defineTool({
|
|
83
|
+
name: "safe-grep",
|
|
84
|
+
label: "safe-grep",
|
|
85
|
+
description: "Line search across the repo working tree with sensitive-file denylist and secret scrubbing. pattern is a JS regex source; optional glob filters paths.",
|
|
86
|
+
promptSnippet: "safe-grep: search repo files (safe; secrets scrubbed)",
|
|
87
|
+
parameters: Type.Object({
|
|
88
|
+
pattern: Type.String({ description: "regex source (no shell)" }),
|
|
89
|
+
caseInsensitive: Type.Optional(Type.Boolean()),
|
|
90
|
+
glob: Type.Optional(Type.String({ description: "optional glob filter e.g. *.ts" })),
|
|
91
|
+
}, { additionalProperties: false }),
|
|
92
|
+
async execute(_toolCallId, params) {
|
|
93
|
+
try {
|
|
94
|
+
const result = await safeGrep(repoRoot, String(params.pattern ?? ""), {
|
|
95
|
+
caseInsensitive: Boolean(params.caseInsensitive),
|
|
96
|
+
glob: params.glob ? String(params.glob) : undefined,
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
100
|
+
details: result,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (e) {
|
|
104
|
+
const err = e;
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: JSON.stringify({
|
|
110
|
+
error: err.message ?? String(e),
|
|
111
|
+
code: err.code ?? "GREP_FAILED",
|
|
112
|
+
}),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
details: {
|
|
116
|
+
error: err.message ?? String(e),
|
|
117
|
+
code: err.code ?? "GREP_FAILED",
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
const gitStatusTool = defineTool({
|
|
124
|
+
name: "git-status",
|
|
125
|
+
label: "git-status",
|
|
126
|
+
description: "Typed `git status --porcelain` (fixed argv, no shell). Returns scrubbed working-tree status.",
|
|
127
|
+
promptSnippet: "git-status: working tree status (read-only)",
|
|
128
|
+
parameters: Type.Object({}, { additionalProperties: false }),
|
|
129
|
+
async execute() {
|
|
130
|
+
const r = safeGit(["status", "--porcelain"]);
|
|
131
|
+
if (!r.ok) {
|
|
132
|
+
return {
|
|
133
|
+
content: [
|
|
134
|
+
{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: JSON.stringify({ error: r.message, code: r.code }),
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
details: { error: r.message, code: r.code },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const { scrubbed, redactions } = scrubSecrets(r.stdout);
|
|
143
|
+
const payload = { status: scrubbed, redactions };
|
|
144
|
+
return {
|
|
145
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
146
|
+
details: payload,
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const gitDiffTool = defineTool({
|
|
151
|
+
name: "git-diff",
|
|
152
|
+
label: "git-diff",
|
|
153
|
+
description: "Typed `git diff` (fixed argv: --stat or a single -- path; no shell). Returns scrubbed diff. path must be repo-relative and not sensitive.",
|
|
154
|
+
promptSnippet: "git-diff: diff (read-only, scrubbed)",
|
|
155
|
+
parameters: Type.Object({
|
|
156
|
+
path: Type.Optional(Type.String({
|
|
157
|
+
description: "optional repo-relative path to diff (default: whole tree --stat)",
|
|
158
|
+
})),
|
|
159
|
+
}, { additionalProperties: false }),
|
|
160
|
+
async execute(_toolCallId, params) {
|
|
161
|
+
let rawPath = params.path ? String(params.path) : undefined;
|
|
162
|
+
// If a path is supplied, validate it via the read-path guard so git diff
|
|
163
|
+
// cannot be aimed at sensitive files (.env* / auth.json / sessions).
|
|
164
|
+
if (rawPath) {
|
|
165
|
+
const { guardReadPath } = await import("../../explore-tools.js");
|
|
166
|
+
const g = await guardReadPath(repoRoot, rawPath);
|
|
167
|
+
if (!g.ok) {
|
|
168
|
+
return {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: JSON.stringify({ error: g.message, code: g.code }),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
details: { error: g.message, code: g.code },
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
rawPath = g.repoRelative;
|
|
179
|
+
}
|
|
180
|
+
const args = rawPath ? ["diff", "--", rawPath] : ["diff", "--stat"];
|
|
181
|
+
const r = safeGit(args);
|
|
182
|
+
if (!r.ok) {
|
|
183
|
+
return {
|
|
184
|
+
content: [
|
|
185
|
+
{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: JSON.stringify({ error: r.message, code: r.code }),
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
details: { error: r.message, code: r.code },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const { scrubbed, redactions } = scrubSecrets(r.stdout);
|
|
194
|
+
const payload = { diff: scrubbed, redactions };
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
197
|
+
details: payload,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
return [safeReadTool, safeGrepTool, gitStatusTool, gitDiffTool];
|
|
202
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-bound Goal tools (workflow-mode only).
|
|
3
|
+
* create_goal still requires a consumed model-create grant from the registry.
|
|
4
|
+
*/
|
|
5
|
+
const GET_GOAL_DESCRIPTION = "Read the current session's durable cross-turn Goal, including revision, status, round limit, and whether this Console process is authorized to continue it.";
|
|
6
|
+
const CREATE_GOAL_DESCRIPTION = "Create a durable cross-turn Goal only when the user explicitly asks to start or create a Goal. Never silently upgrade an ordinary request. A session may have only one unfinished Goal.";
|
|
7
|
+
const UPDATE_GOAL_DESCRIPTION = "Finish or block the current durable Goal using its exact goalId and revision. Set complete only when the objective is genuinely achieved and no required work remains. Set blocked only when safe progress cannot continue; include the blocker.";
|
|
8
|
+
export async function buildGoalTools(options) {
|
|
9
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
10
|
+
import("typebox"),
|
|
11
|
+
import("@earendil-works/pi-coding-agent"),
|
|
12
|
+
]);
|
|
13
|
+
const textResult = (value) => ({
|
|
14
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
15
|
+
details: value,
|
|
16
|
+
});
|
|
17
|
+
return [
|
|
18
|
+
defineTool({
|
|
19
|
+
name: "get_goal",
|
|
20
|
+
label: "Get goal",
|
|
21
|
+
description: GET_GOAL_DESCRIPTION,
|
|
22
|
+
promptSnippet: GET_GOAL_DESCRIPTION,
|
|
23
|
+
parameters: Type.Object({}, { additionalProperties: false }),
|
|
24
|
+
async execute() {
|
|
25
|
+
return textResult({ goal: options.registry.get(options.sessionId) });
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
defineTool({
|
|
29
|
+
name: "create_goal",
|
|
30
|
+
label: "Create goal",
|
|
31
|
+
description: CREATE_GOAL_DESCRIPTION,
|
|
32
|
+
promptSnippet: CREATE_GOAL_DESCRIPTION,
|
|
33
|
+
parameters: Type.Object({
|
|
34
|
+
objective: Type.String({
|
|
35
|
+
description: "Concrete final objective and completion standard explicitly requested by the user.",
|
|
36
|
+
}),
|
|
37
|
+
}, { additionalProperties: false }),
|
|
38
|
+
async execute(_toolCallId, params) {
|
|
39
|
+
if (!options.registry.consumeModelCreateGrant(options.sessionId)) {
|
|
40
|
+
return textResult({
|
|
41
|
+
ok: false,
|
|
42
|
+
code: "GOAL_EXPLICIT_REQUEST_REQUIRED",
|
|
43
|
+
message: "create_goal requires an explicit user request to create or start a Goal",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return textResult(options.registry.create({
|
|
47
|
+
sessionId: options.sessionId,
|
|
48
|
+
objective: params.objective,
|
|
49
|
+
}));
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
defineTool({
|
|
53
|
+
name: "update_goal",
|
|
54
|
+
label: "Update goal",
|
|
55
|
+
description: UPDATE_GOAL_DESCRIPTION,
|
|
56
|
+
promptSnippet: UPDATE_GOAL_DESCRIPTION,
|
|
57
|
+
parameters: Type.Object({
|
|
58
|
+
goalId: Type.String(),
|
|
59
|
+
revision: Type.Number({ minimum: 1 }),
|
|
60
|
+
status: Type.Union([
|
|
61
|
+
Type.Literal("complete"),
|
|
62
|
+
Type.Literal("blocked"),
|
|
63
|
+
]),
|
|
64
|
+
blocker: Type.Optional(Type.String()),
|
|
65
|
+
}, { additionalProperties: false }),
|
|
66
|
+
async execute(_toolCallId, params) {
|
|
67
|
+
return textResult(options.registry.update({
|
|
68
|
+
sessionId: options.sessionId,
|
|
69
|
+
goalId: params.goalId,
|
|
70
|
+
expectedRevision: params.revision,
|
|
71
|
+
phase: params.status,
|
|
72
|
+
blocker: params.blocker,
|
|
73
|
+
arm: false,
|
|
74
|
+
}));
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-less operator-action custom tools (workflow-mode business tools).
|
|
3
|
+
* Definitions and execute() live here; ConsolePiRuntime owns caching and
|
|
4
|
+
* composition with session-bound tools.
|
|
5
|
+
*/
|
|
6
|
+
import { authorizeChatSessionTool, DEFAULT_CONSOLE_CHAT_SESSION_MODE, } from "../../session-mode.js";
|
|
7
|
+
import { buildModelCallableToolSchemas } from "../../tool-adapter.js";
|
|
8
|
+
/**
|
|
9
|
+
* Build Pi custom ToolDefinition objects for every whitelisted operator action.
|
|
10
|
+
* Each tool's execute() routes into the operator action dispatcher (Gate 3).
|
|
11
|
+
* Requires the `typebox` + `defineTool` packages (both bundled with the SDK).
|
|
12
|
+
*/
|
|
13
|
+
export async function buildCustomOperatorTools(options) {
|
|
14
|
+
// typebox is a transitive dep of @earendil-works/pi-coding-agent; Node's
|
|
15
|
+
// resolver hoists to the SDK's nested copy. defineTool is re-exported by SDK.
|
|
16
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
17
|
+
import("typebox"),
|
|
18
|
+
import("@earendil-works/pi-coding-agent"),
|
|
19
|
+
]);
|
|
20
|
+
const schemas = buildModelCallableToolSchemas();
|
|
21
|
+
const ctx = options.actionContext;
|
|
22
|
+
return schemas.map((schema) => {
|
|
23
|
+
// TypeBox schema is built dynamically; cast through unknown to the builder
|
|
24
|
+
// since Static<TParams> is structural and cannot be statically inferred here.
|
|
25
|
+
const parameters = Type.Object(schema.inputParams.reduce((acc, param) => {
|
|
26
|
+
// Carry the registry's param description into the tool schema so
|
|
27
|
+
// the model sees parameter semantics, not just names/types.
|
|
28
|
+
const desc = param.description?.trim()
|
|
29
|
+
? { description: param.description }
|
|
30
|
+
: {};
|
|
31
|
+
let t;
|
|
32
|
+
switch (param.type) {
|
|
33
|
+
case "string":
|
|
34
|
+
t = Type.String(desc);
|
|
35
|
+
break;
|
|
36
|
+
case "boolean":
|
|
37
|
+
t = Type.Boolean(desc);
|
|
38
|
+
break;
|
|
39
|
+
case "number":
|
|
40
|
+
t = Type.Number(desc);
|
|
41
|
+
break;
|
|
42
|
+
case "array":
|
|
43
|
+
t = Type.Array(Type.String({}), Object.keys(desc).length ? desc : {});
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
t = Type.Object({}, { additionalProperties: true });
|
|
47
|
+
}
|
|
48
|
+
acc[param.name] = param.required ? t : Type.Optional(t);
|
|
49
|
+
return acc;
|
|
50
|
+
}, {}), { additionalProperties: false });
|
|
51
|
+
return defineTool({
|
|
52
|
+
name: schema.toolId,
|
|
53
|
+
label: schema.toolId,
|
|
54
|
+
description: `${schema.description} (kind=${schema.kind}; cli: ${schema.cli})`,
|
|
55
|
+
promptSnippet: schema.description,
|
|
56
|
+
parameters: parameters,
|
|
57
|
+
async execute(toolCallId, params) {
|
|
58
|
+
const decision = authorizeChatSessionTool(schema.toolId, options.sessionMode ?? DEFAULT_CONSOLE_CHAT_SESSION_MODE);
|
|
59
|
+
if (!decision.ok) {
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: "text",
|
|
64
|
+
text: JSON.stringify({
|
|
65
|
+
error: decision.message,
|
|
66
|
+
code: decision.code,
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
details: { error: decision.message, code: decision.code },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Defer require to avoid circular import at module load.
|
|
74
|
+
const { dispatchChatToolCall } = await import("../../session-store.js");
|
|
75
|
+
const result = await dispatchChatToolCall(ctx, decision.toolId, params, toolCallId, options.sessionMode);
|
|
76
|
+
const payload = result.ok
|
|
77
|
+
? result.result
|
|
78
|
+
: { error: result.message, code: result.errorCode };
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
81
|
+
details: payload,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-bound todo_write custom tool.
|
|
3
|
+
* Description is mode-aware; ConsolePiRuntime owns when to register it.
|
|
4
|
+
*/
|
|
5
|
+
import { PI_TODO_WRITE_DESCRIPTION, } from "../../session-mode.js";
|
|
6
|
+
const TODO_WRITE_DESCRIPTION = "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (no partial updates, no per-item edits). Before starting multi-step work, add one todo per concrete step. Keep at least one todo `in_progress` while work remains. Mark a todo `completed` immediately when it is done (do not batch completions) and resend the full list as progress or DAG state advances. Skip the list for simple Q&A, read-only lookups, and single-step tasks. Use it for multi-step implementation and whenever you prepare, run, or supervise a DAG — call `todo_write` before entering the DAG tool chain. Phrase DAG todos as user-facing phases users can understand, not copies of internal DAG nodes. Statuses: `pending` / `in_progress` / `completed`.";
|
|
7
|
+
export async function buildTodoWriteTool(options) {
|
|
8
|
+
const [{ Type }, { defineTool }] = await Promise.all([
|
|
9
|
+
import("typebox"),
|
|
10
|
+
import("@earendil-works/pi-coding-agent"),
|
|
11
|
+
]);
|
|
12
|
+
const description = options.sessionMode === "pi"
|
|
13
|
+
? PI_TODO_WRITE_DESCRIPTION
|
|
14
|
+
: TODO_WRITE_DESCRIPTION;
|
|
15
|
+
return defineTool({
|
|
16
|
+
name: "todo_write",
|
|
17
|
+
label: "Update todo list",
|
|
18
|
+
description,
|
|
19
|
+
promptSnippet: description,
|
|
20
|
+
parameters: Type.Object({
|
|
21
|
+
todos: Type.Array(Type.Object({
|
|
22
|
+
content: Type.String({ description: "Short imperative task description." }),
|
|
23
|
+
status: Type.Union([
|
|
24
|
+
Type.Literal("pending"),
|
|
25
|
+
Type.Literal("in_progress"),
|
|
26
|
+
Type.Literal("completed"),
|
|
27
|
+
]),
|
|
28
|
+
}, { additionalProperties: false })),
|
|
29
|
+
}, { additionalProperties: false }),
|
|
30
|
+
async execute(toolCallId, params) {
|
|
31
|
+
const result = options.registry.write(options.sessionId, toolCallId, params.todos);
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
34
|
+
details: result,
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|