@tea-agent/loop-agent 0.44.0-next.4 → 0.44.0-next.6
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 +29 -1
- package/dist/build-stamp.json +3 -3
- package/dist/executors/pi-sdk-executor.js +7 -7
- package/dist/executors/shell-write-guard.js +26 -3
- package/dist/infrastructure/console/operation-store.js +46 -14
- package/dist/shared/file-identity.js +23 -0
- package/dist/shared/git-workspace-status.js +480 -0
- package/dist/shared/path-safety.js +2 -2
- package/dist/shared/wizard-local-telemetry.js +2 -1
- package/dist/worker/console/chat/context-insights.js +723 -0
- package/dist/worker/console/chat/pi-runtime.js +217 -18
- package/dist/worker/console/chat/repo-browser.js +52 -4
- package/dist/worker/console/chat/routes.js +150 -58
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-B_lmJjOm.js → abnfDiagram-N423BO3Z-Brn2sPzL.js} +1 -1
- package/dist/worker/console/static/assets/{arc-pymM4Qeh.js → arc-BIMNTG0W.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-Bw9EPAhi.js → architectureDiagram-T3A2C74G--WtcgR_A.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C7_oXJp2.js → blockDiagram-VBNYF7ZC-BT_Ztog1.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-LnLWDbh8.js → c4Diagram-5PPSVZJV-C5hbVr-l.js} +1 -1
- package/dist/worker/console/static/assets/channel-DNWB4hpR.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BBiu3sCW.js → chunk-2GRJ4B5K-BKKnyR9Z.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DiXqDQml.js → chunk-2Q5K7J3B-BHs6biqA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-lmNq9nYk.js → chunk-5RXB4S5H-BdSFy_vP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-boBu74dU.js → chunk-5VM5RSS4-BpJIdTKV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CjxqoJLQ.js → chunk-6Q2QTUOP-C_iWJhkT.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-DL0vQ9MG.js → chunk-GF5L2VYU-BTbmx3-k.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DGPqE-O0.js → chunk-JWPE2WC7-D4Ee6DmP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-v4BbT_r2.js → chunk-KBJHAD2P-B487SORR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-Ci0ynvyL.js → chunk-RYQCIY6F-BKYHjNrr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CGsbJoTV.js → chunk-XXDRQBXY-DrTdoYZG.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-COhFgJji.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-COhFgJji.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CzoRDEI7.js → cose-bilkent-JH36ORCC-CttUY_Jo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BOwFwbX4.js → cynefin-VYW2F7L2-B4Rm5gnR.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2Cfg451l.js → cynefinDiagram-MW4NZA55-ByxHBq_S.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-DbT0D6Y5.js → dagre-VZM6K2ZE-DGrfNFA0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dfd_JoMs.js → diagram-7IWD3JNH-KjrUPquv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DaGm82kh.js → diagram-B4RE2ZJO-VS4mlm75.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DMoI8Ibc.js → diagram-LBJQPF4R-BLXYmFTM.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BtIFA3aY.js → diagram-Q27KOJAE-C6xoFAqH.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-D_2X1DTZ.js → diagram-UB23O5K3-C478dCe7.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-yFW5ZYlZ.js → ebnfDiagram-BXEA7PRR-DHEpW5zN.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DBXh5Svj.js → erDiagram-JOGREHBK-CA5GKfo9.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-C4ycm8KM.js → flowDiagram-UKHOOZJN-zysi0Fl-.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-qr-_Pekr.js → ganttDiagram-PKOTCBZU-DGzcKA8Y.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DzOSLv_A.js → gitGraphDiagram-DS77QQ5N-CHsuIG1w.js} +1 -1
- package/dist/worker/console/static/assets/index-CJh6pVLm.js +482 -0
- package/dist/worker/console/static/assets/index-C_kvKmFM.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-C7Hl8D7l.js → infoDiagram-6WML65LV-BexZHPCp.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-fBxVlr6t.js → ishikawaDiagram-WSZJBQD7-Gkx8q-DB.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CoiJEVCl.js → journeyDiagram-NVQOT4AX-XDW3qrJ7.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-TJXpxNuV.js → kanban-definition-27J2QSJJ-DJ07eEaN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CjQWz1pG.js → linear-DL-zeqOn.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DY9_apIE.js → mermaid.core-C0HdXtOf.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-HB0Id8gM.js → mindmap-definition-FAOFIHXS-CqRX0lrt.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-0OSWgr6v.js → pegDiagram-VL7TDLO6-BVM9JZ2H.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Cei1GGC2.js → pieDiagram-7S7Q4E2Y-BA3ozdKk.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CDy0Adfu.js → quadrantDiagram-CIZ2JOQS-B-0_p9_4.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwqzUWwN.js → railroadDiagram-AXF67PYL-DHRzPWFc.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-Bif2IBRL.js → requirementDiagram-LRYGKXZP-9BEC_PcW.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-CYIoNMLg.js → sankeyDiagram-W5VNT64P-DMxeW4rN.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BDS7Y0aQ.js → sequenceDiagram-SI44F4Z6-DixP6LIU.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-lZic-_Bp.js → sizeCapture-X5ZJPWSS-B_EifJBo.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-WCVlCqSd.js → stateDiagram-OKZ733FA-BwikwWbX.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-S2ZHMo1c.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-pGccxTw_.js → swimlanes-SLNWSIFB-C162l9o1.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BZm4ByfV.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-D29RlrQA.js → timeline-definition-Z64GVDOM-mpe_u1i4.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-01fEM1NU.js → vennDiagram-T6HMQDX7-DyUk54HX.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C4GeB6dq.js → wardleyDiagram-T6FBY63Y-HYyMkzfm.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BXHrXHps.js → xychartDiagram-ELKLHX3M-DRoh7skR.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +4 -2
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +8 -1
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/dist/worker/observe/node-context-insights.js +479 -0
- package/docs/README.md +1 -1
- package/docs/templates/backend-test-dag.json +1 -0
- package/package.json +4 -4
- package/dist/worker/console/static/assets/channel-Ym13M3Lb.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeFR1wGo.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeFR1wGo.js +0 -1
- package/dist/worker/console/static/assets/index-PFjB6g6l.js +0 -482
- package/dist/worker/console/static/assets/index-ZtyB6Ol-.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-cj5UUB6m.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-C_Qp6bnh.js +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
- 修复 main 分支的 next 发布链在类型检查阶段内存溢出失败的问题:完整源码与测试的类型图已超过 Node 默认约 2 GiB 堆上限,类型检查进程因此中止(exit 134),导致 `next` 发布无法完成。此前该上限只在部分验证入口生效,其余入口直接运行类型检查就会因默认堆不足而失败;现把上限固化到类型检查命令本身,各验证入口与本地开发统一生效,不再依赖各入口自行声明。
|
|
6
|
+
|
|
7
|
+
- 修复 Operator Chat 冷启动卡在检查面板的问题:模型列表和 runtime-snapshot 改为读工作区目录,不再为了展示技能或系统提示去打开 Pi 历史 JSONL;发消息、切换模型、reload 仍会激活会话。会话栏也不再等 Pi 重开,工作区列表未返回前不再把侧栏滤空。
|
|
8
|
+
|
|
9
|
+
- 修复 Operator Chat 在工作区尚未选中时抢先请求仓库树和能力目录、浏览器 Network 出现 400 的问题;等 `workspaceId` 就绪后再请求,不再把启动竞态显示成功能异常。
|
|
10
|
+
|
|
11
|
+
- 修复 Console 成功更新后历史接棒记录仍占用操作位、导致工作区初始化持续被拒绝的问题;通用「需要核对」提示现在引导先核实实际执行结果,不再误导为重新更新程序。
|
|
12
|
+
|
|
13
|
+
- Console 更新前保留当前实例身份,修复重启断线后首次响应已来自新进程时仍等待超时、无法自动刷新的问题;关闭重开设置保留该身份,所有刷新等待分支均反馈超时。
|
|
14
|
+
- 修复观测页「执行历史」把仍在重试的节点误报为「最终失败」的问题:只要节点最后一个已记录的尝试失败,就会显示「N 次 · 最终失败」,即使该节点仍在运行、重试已经排队,会让人误判运行已结束。现在节点处于运行/已启动/暂停状态时改为「N 次 · 最近一次失败,仍在重试」,真正到达终态才显示「最终失败」;修复轮次的多轮标签同样处理。
|
|
15
|
+
|
|
16
|
+
- 修复 Console「更新程序」被一条卡住的旧操作永久堵死的问题:停在「等待核对」的操作记录已被启动对账跳过、自身无法再推进,却又被当成「仍在运行」,导致之后每次更新都提示「另一个 Console 操作正在进行」。现将其明确归为已结束、不再占用操作位,并在恢复页保留可见;同时补全「已安装成功但接棒失败」记录的自动收敛,不再永久停留。
|
|
17
|
+
|
|
18
|
+
- Console 操作文案改为面向操作者的说明并给出下一步:等待超时不再伪装成「需要核对」,改为提示操作可能仍在后台运行;「源内容已变更」「前端拆分状态被并发修改」等原本直接展示英文原文的错误,改为说明原因与具体处理步骤;恢复页按钮与提示去掉 reconcile / supersede / orphan / Human Gate 等术语。
|
|
19
|
+
|
|
20
|
+
- 修复后端测试 DAG 模板遗漏 `upstream-unavailable` 重试类别的问题:内置模板的 Markdown 计划节点缺少该类别,与运行时生成器不一致,导致照模板配置的项目少一类重试;现已补齐,并把模板漂移检查扩展到重试策略字段,避免同类不一致再次漏过。
|
|
21
|
+
|
|
22
|
+
- Operator Chat 右侧辅助栏改为三个表面:仓库、会话、工作台。点上下文环进入「会话 / 上下文」,文件和终端不再与会话检查挤在同一条标签上;工作台用内侧图标轨切换终端、浏览器和数据库。
|
|
23
|
+
|
|
24
|
+
- Operator Chat 侧栏折叠后改为 56px 图标轨:可直接点新建会话、搜索、添加工作区和设置,不再留下空槽。
|
|
25
|
+
- Operator Chat 的 `@` 文件面板改为按当前目录一层浏览:空 `@` 不再倒出整库路径,点文件默认隐藏,Tab 进入目录、Enter 选定,插入仍是 `@path` 文本。文件行改为左对齐菜单,不再被全局按钮样式居中成胶囊;目录右侧常驻进入箭头,浮层圆角与投影贴近 DSH 菜单。
|
|
26
|
+
- Pi SDK 运行时依赖升级:`@earendil-works/pi-ai` 与 `@earendil-works/pi-coding-agent` 从精确 pin `0.83.0` 升到 `0.84.4`(顺带修复其携带的 `undici` 高危漏洞与 `brace-expansion` DoS 告警)。适配 0.84 破坏性变更:`getApiKeyAndHeaders` 返回的 headers 现可含 `null` 删除标记、`setRuntimeApiKey` 第三参改为认证取消选项;扩展加载 fixture 改为 ESM(pi-ai 子路径导出不再提供 CJS 条件)。SDK 契约探针文案、发布包契约测试与自举 canary 同步对齐 0.84.4。
|
|
27
|
+
|
|
28
|
+
- 修复 Windows 上 pi 节点零提交失败的读写守卫误报:libuv 在 win32 的 `fstat` 返回卷序列号而 `lstat` 恒为 0,跨 API 比较必然失败并误报“文件在打开期间被替换”;现新建共享身份比较 helper,win32 跳过跨 API `dev` 比较但保留 `ino` 与其余全部防护,Console 仓库预览同类比较一并修复(用户 0.42.1/0.43.0 两次实测上报)。
|
|
29
|
+
- 修复研发工作流对 Git submodule 的误拦截:父仓库 `git status` 只能看到 submodule 目录级条目,子仓库内合法写入会被误判 `empty-diff`(脏基线)或越界(净基线),导致 verify/review/closeout 全部跳过;现在状态采集会展开到已初始化子仓库的文件级事实(含嵌套 submodule、中文/带引号路径解码、未初始化与采集失败时如实报错不吞证据),合法子仓库写入正常进入验证链,真实越界仍按文件精确拦截,无 submodule 仓库行为字节级不变。
|
|
30
|
+
|
|
5
31
|
- Console 新增低饱和的「暮色蓝」深色与「雾光白」浅色主题,保留默认暖纸色;柔和背景、轻量选中态与独立按钮配色减少视觉刺激。顶部仅显示带「主题」悬停提示的紧凑明暗图标按钮,点击后展开「默认 / 深色 / 浅色」,选完自动收起,支持点击外部或 Escape 关闭;初始使用默认主题;即时切换对话、操作与观测界面,刷新后记住选择,浏览器存储不可用时仍可在当前页面切换。
|
|
6
32
|
- Operator Chat 消息流后续对齐:折叠摘要现在统计 retry/step/command/unknown 等 durable 过程节点,Trajectory 与 HTML 导出保留 DAG progress,并使用稳定过程身份避免直播插入导致 Thinking/工具行重挂载。
|
|
7
33
|
- Operator Chat 统一主时间线、Trajectory 与导出使用同一 DSH flow projection:Thinking、工具、重试、命令和完成态节点按事件序交错,保留 completed-turn 折叠;request/unknown 详情改为有界结构化摘要,不再回显裸节点 JSON。
|
|
8
34
|
- Operator Chat 的消息流对齐 DSH Conversation Node:模型步骤、重试、终止错误、输出上限、每次请求上下文、嵌套工具、`/sdd-bingo` 命令回执和未知事件均以稳定节点留痕;同一模型步骤的多次重试聚合为一条紧凑记录,Provider 终态错误改为轻量行且不再暴露原始响应;流式文本按动画帧合并发布,刷新和断线恢复保持同一身份与顺序,轨迹页面和导出同步展示。
|
|
9
|
-
|
|
35
|
+
- Operator Chat 在后台断线重连后不再丢消息:检测到中断会自动补齐缺口内容,也不会把正常的历史回放误判成丢事件而反复重连。
|
|
36
|
+
- Operator Chat 新增独立「上下文洞察」面板:提供当前上下文构成、按步/按轮趋势与累计/增量视图、上下文浏览器(系统提示、工具 Schema、消息、注入、工具结果、技能)、上下文事件筛选、文件活动筛选排序,以及 token/cache/cost/timing 统计;`/context` 与详情栏「洞察」共用同一数据面。
|
|
37
|
+
- 「上下文洞察」新增 token 构成饼图(conic-gradient 渲染)、剪枝/压缩明细(区分 prune 与 compact 事件,展示前/后/回收 tokens、移除项数、原因);趋势柱 hover/click 实时联动上下文浏览器。
|
|
10
38
|
- Operator Chat 的恢复快照现在只会合并属于目标会话的乐观提交;切换会话后,旧会话尚未确认的输入不会再因延迟重连恢复而显示到新会话中。
|
|
11
39
|
- `/sdd-bingo` 成功提示现在展示上报的 Skill 与 Session ID;轨迹顶部会出现当前会话的常驻 SDD 身份栏,可一键复制、首次请求后自动更新;该上报以独立“操作”事件留痕,刷新或导出后仍在,不再伪装成工具调用。
|
|
12
40
|
- Operator Chat 发送消息后的空白光标改为 Codex 式“正在思考”,真实思考、工具过程或最终回答出现后自然接替;并修复了状态偶发显示在用户输入上方的问题。
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.44.0-next.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-09-
|
|
3
|
+
"version": "0.44.0-next.6",
|
|
4
|
+
"gitSha": "11c9ac87f792e61b1bc31b1ef2aeacb48a6334d4",
|
|
5
|
+
"builtAt": "2026-09-16T11:49:07.558Z"
|
|
6
6
|
}
|
|
@@ -40,21 +40,21 @@ export async function installPiProviderRequestBudget(session, reserve, onFailure
|
|
|
40
40
|
const agent = session.agent;
|
|
41
41
|
const original = agent?.streamFunction;
|
|
42
42
|
if (!agent || typeof original !== "function")
|
|
43
|
-
throw Error("FRONTEND_PROVIDER_BUDGET_UNSUPPORTED: SDK actual request stream unavailable");
|
|
43
|
+
throw new Error("FRONTEND_PROVIDER_BUDGET_UNSUPPORTED: SDK actual request stream unavailable");
|
|
44
44
|
const { createAssistantMessageEventStream } = await import("@earendil-works/pi-ai");
|
|
45
45
|
agent.streamFunction = async (...args) => {
|
|
46
46
|
try {
|
|
47
47
|
if (args[2]?.signal?.aborted)
|
|
48
|
-
throw Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
48
|
+
throw new Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
49
49
|
if (capacity) {
|
|
50
50
|
const measurement = measureFrontendRequestEnvelope(args[1], args[0], capacity.policy);
|
|
51
51
|
capacity.observe(measurement);
|
|
52
52
|
if (measurement.exceedsEstimate)
|
|
53
|
-
throw Error(`FRONTEND_CONTEXT_ESTIMATE_EXCEEDED: estimated=${measurement.estimatedTokens} available=${measurement.availableInputTokens}; estimator=${measurement.estimatorVersion}; registry capability is not a measured gateway limit`);
|
|
53
|
+
throw new Error(`FRONTEND_CONTEXT_ESTIMATE_EXCEEDED: estimated=${measurement.estimatedTokens} available=${measurement.availableInputTokens}; estimator=${measurement.estimatorVersion}; registry capability is not a measured gateway limit`);
|
|
54
54
|
}
|
|
55
55
|
await reserve();
|
|
56
56
|
if (args[2]?.signal?.aborted)
|
|
57
|
-
throw Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
57
|
+
throw new Error("FRONTEND_PROVIDER_REQUEST_ABORTED");
|
|
58
58
|
}
|
|
59
59
|
catch (error) {
|
|
60
60
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -124,7 +124,7 @@ export function setPiSdkImportOverrideForTests(fn) {
|
|
|
124
124
|
export function setPiSdkModuleOverrideForTests(fn) {
|
|
125
125
|
sdkModuleOverrideForTests = fn;
|
|
126
126
|
}
|
|
127
|
-
/** Check whether the Pi SDK runtime dependency satisfies the 0.
|
|
127
|
+
/** Check whether the Pi SDK runtime dependency satisfies the 0.84.4 runtime contract. */
|
|
128
128
|
export async function checkPiSdkAvailability(_repoRoot) {
|
|
129
129
|
if (sdkSessionFactoryOverride) {
|
|
130
130
|
return { ok: true, detail: "pi SDK session factory override active" };
|
|
@@ -140,10 +140,10 @@ export async function checkPiSdkAvailability(_repoRoot) {
|
|
|
140
140
|
typeof ModelRuntime?.create !== "function") {
|
|
141
141
|
return {
|
|
142
142
|
ok: false,
|
|
143
|
-
detail: "pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.
|
|
143
|
+
detail: "pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.84.4 contract)",
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
return { ok: true, detail: "pi SDK 0.
|
|
146
|
+
return { ok: true, detail: "pi SDK 0.84.4 contract available" };
|
|
147
147
|
}
|
|
148
148
|
catch (error) {
|
|
149
149
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4,6 +4,7 @@ import { constants, createReadStream } from "node:fs";
|
|
|
4
4
|
import { access, lstat, readlink, unlink } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathMatchesPattern } from "../shared/git-progress.js";
|
|
7
|
+
import { expandWorkspaceStatus, hasGitmodulesFile, synthesizeExpandedPorcelain, } from "../shared/git-workspace-status.js";
|
|
7
8
|
import { ResilientGitCommandError, runResilientGitCommand, } from "../shared/resilient-git.js";
|
|
8
9
|
async function sha256File(filePath) {
|
|
9
10
|
const hash = createHash("sha256");
|
|
@@ -411,6 +412,7 @@ async function resolveGitExecutableCandidates(platform, env) {
|
|
|
411
412
|
return ["git"];
|
|
412
413
|
}
|
|
413
414
|
export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
415
|
+
let parentPorcelain;
|
|
414
416
|
try {
|
|
415
417
|
const result = await runResilientGitCommand({
|
|
416
418
|
cwd,
|
|
@@ -419,9 +421,12 @@ export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
|
419
421
|
attempts: options.attempts,
|
|
420
422
|
retryDelayMs: options.retryDelayMs,
|
|
421
423
|
});
|
|
422
|
-
if (result.code === 0)
|
|
423
|
-
|
|
424
|
-
|
|
424
|
+
if (result.code === 0) {
|
|
425
|
+
parentPorcelain = result.stdout;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
throw new Error(result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`);
|
|
429
|
+
}
|
|
425
430
|
}
|
|
426
431
|
catch (error) {
|
|
427
432
|
if (error instanceof ResilientGitCommandError) {
|
|
@@ -436,6 +441,24 @@ export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
|
436
441
|
}
|
|
437
442
|
throw error;
|
|
438
443
|
}
|
|
444
|
+
// Submodule workspaces: a parent-only porcelain hides every file inside a
|
|
445
|
+
// registered submodule behind a single directory-level gitlink entry,
|
|
446
|
+
// which historically produced false empty-diff and false violations
|
|
447
|
+
// (plan 2026-09-15-development-workflow-git-submodule). Repositories
|
|
448
|
+
// without .gitmodules keep the parent bytes verbatim — byte-identical
|
|
449
|
+
// behavior for every non-submodule workspace.
|
|
450
|
+
if (!(await hasGitmodulesFile(cwd)))
|
|
451
|
+
return parentPorcelain;
|
|
452
|
+
const observation = await expandWorkspaceStatus({
|
|
453
|
+
rootCwd: cwd,
|
|
454
|
+
parentPorcelain,
|
|
455
|
+
hasGitmodulesFile: true,
|
|
456
|
+
});
|
|
457
|
+
if (!observation.complete) {
|
|
458
|
+
const failed = observation.repos.find((repo) => repo.state === "status-read-failed");
|
|
459
|
+
throw new Error(`workspace status expansion incomplete: submodule ${failed?.repoRelativePath ?? "unknown"} status read failed: ${failed?.errorDetail ?? "unknown error"}`);
|
|
460
|
+
}
|
|
461
|
+
return synthesizeExpandedPorcelain(observation.entries);
|
|
439
462
|
}
|
|
440
463
|
export async function readGitStatusPorcelainWithDependencies(cwd, options, dependencies) {
|
|
441
464
|
const candidates = await dependencies.resolveExecutableCandidates();
|
|
@@ -6,6 +6,24 @@ import { buildFrontendSplitAggregateCloseout, computeFrontendSplitDigest, finali
|
|
|
6
6
|
import { frontendCloseoutFactsSchema, frontendTestHandoffSchema, } from "../../workflows/dag/frontend-closeout.js";
|
|
7
7
|
import { listJsonFiles, readJsonIfExists, sha256Json, writeSecureJson, } from "./app-data.js";
|
|
8
8
|
export const TERMINAL_OPERATION_STATES = new Set(["succeeded", "failed", "timed-out", "restarting"]);
|
|
9
|
+
/**
|
|
10
|
+
* Operation states that hold an admission slot. needs-reconcile requires
|
|
11
|
+
* inspection but does not itself prove a live operation; callers still check
|
|
12
|
+
* DAG/Worker/chat activity separately. restarting is the terminal receipt of
|
|
13
|
+
* a successful Console handoff, retained across boots, not a live slot.
|
|
14
|
+
*/
|
|
15
|
+
export const OCCUPIED_OPERATION_STATES = new Set(["queued", "starting", "running"]);
|
|
16
|
+
/** True when the state can still make progress and therefore holds the slot. */
|
|
17
|
+
export function isOperationOccupied(state) {
|
|
18
|
+
return OCCUPIED_OPERATION_STATES.has(state);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* True when no further state transition will ever occur: a business terminal
|
|
22
|
+
* (succeeded/failed/timed-out/restarting) or a stranded needs-reconcile.
|
|
23
|
+
*/
|
|
24
|
+
export function isOperationSettled(state) {
|
|
25
|
+
return TERMINAL_OPERATION_STATES.has(state) || state === "needs-reconcile";
|
|
26
|
+
}
|
|
9
27
|
function operationFile(appData, operationId) {
|
|
10
28
|
return path.join(appData.operations, `${operationId}.json`);
|
|
11
29
|
}
|
|
@@ -359,14 +377,11 @@ export class OperationStore {
|
|
|
359
377
|
for (const op of await this.list()) {
|
|
360
378
|
if (TERMINAL_OPERATION_STATES.has(op.state))
|
|
361
379
|
continue;
|
|
362
|
-
// A consoleUpdateAndInit
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
// handoff target. Mark it succeeded instead of surfacing the
|
|
366
|
-
// generic needs-reconcile failure copy to the operator.
|
|
380
|
+
// A consoleUpdateAndInit that proved its handoff before the process
|
|
381
|
+
// died must not be reported as a stranded failure: the booting
|
|
382
|
+
// process IS the handoff target, so the update actually succeeded.
|
|
367
383
|
if (op.action === "consoleUpdateAndInit" &&
|
|
368
|
-
op
|
|
369
|
-
isProvenConsoleUpdateHandoff(op.result)) {
|
|
384
|
+
isProvenConsoleUpdateHandoff(op)) {
|
|
370
385
|
changed.push(await this.update(op.operationId, {
|
|
371
386
|
state: "succeeded",
|
|
372
387
|
errorCode: undefined,
|
|
@@ -398,17 +413,34 @@ export function isTerminalOperationState(state) {
|
|
|
398
413
|
return TERMINAL_OPERATION_STATES.has(state);
|
|
399
414
|
}
|
|
400
415
|
/**
|
|
401
|
-
* True when a consoleUpdateAndInit
|
|
402
|
-
* (install verified +
|
|
403
|
-
*
|
|
404
|
-
*
|
|
416
|
+
* True when a consoleUpdateAndInit record proves the handoff actually
|
|
417
|
+
* succeeded (install verified + new Console spawned), so boot reconcile can
|
|
418
|
+
* close it as succeeded even though the dying process never wrote its
|
|
419
|
+
* terminal state.
|
|
420
|
+
*
|
|
421
|
+
* The proof reads the maintenance projection rather than only `result.state`.
|
|
422
|
+
* The old check required `result.state === "restarting"`, but the executor
|
|
423
|
+
* writes `state` and `maintenance` together (`console-update-and-init.ts`),
|
|
424
|
+
* so any path that recorded the handoff proof together with a non-restarting
|
|
425
|
+
* `state` — most importantly the `needs-reconcile` record that
|
|
426
|
+
* `acceptConsoleUpdateAndInit` writes when the executor throws *after* a
|
|
427
|
+
* successful handoff — could never satisfy it. Such records were stranded
|
|
428
|
+
* forever: boot reconcile skipped them and they blocked later updates.
|
|
429
|
+
*
|
|
430
|
+
* `handoff.status === "requested"` is only ever set on the success path, and
|
|
431
|
+
* `package.status === "failed"` explicitly disqualifies a record, so this
|
|
432
|
+
* stays fail-closed for genuine failures.
|
|
405
433
|
*/
|
|
406
|
-
function isProvenConsoleUpdateHandoff(
|
|
434
|
+
export function isProvenConsoleUpdateHandoff(operation) {
|
|
435
|
+
if (!operation || operation.action !== "consoleUpdateAndInit")
|
|
436
|
+
return false;
|
|
437
|
+
const result = operation.result;
|
|
407
438
|
if (!result || typeof result !== "object")
|
|
408
439
|
return false;
|
|
409
440
|
const shape = result;
|
|
410
|
-
|
|
411
|
-
|
|
441
|
+
if (shape.maintenance?.handoff?.status !== "requested")
|
|
442
|
+
return false;
|
|
443
|
+
return shape.maintenance?.package?.status !== "failed";
|
|
412
444
|
}
|
|
413
445
|
export function clipPreview(text, max = 4096) {
|
|
414
446
|
if (text.length <= max)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two stat records as the same file identity.
|
|
3
|
+
*
|
|
4
|
+
* POSIX: `dev` and `ino` together identify the inode; both must match.
|
|
5
|
+
*
|
|
6
|
+
* win32: libuv reports `dev` inconsistently across stat APIs — `fstat()`
|
|
7
|
+
* (from an open handle) returns the volume serial number while `lstat()`
|
|
8
|
+
* (from a path) returns 0. A cross-API comparison therefore always fails on
|
|
9
|
+
* Windows even when the path still refers to the same file, breaking every
|
|
10
|
+
* guard that rechecks identity after opening (user-reported on 0.42.1 and
|
|
11
|
+
* 0.43.0, Node 23.x). On win32 the `dev` field is skipped and `ino` plus the
|
|
12
|
+
* caller-side type/link/realpath checks remain the identity boundary.
|
|
13
|
+
*
|
|
14
|
+
* When both stats come from the same API the comparison is exact on every
|
|
15
|
+
* platform; the win32 relaxation only affects cross-API rechecks.
|
|
16
|
+
*/
|
|
17
|
+
export function sameFileIdentity(opened, current, platform = process.platform) {
|
|
18
|
+
if (current.ino !== opened.ino)
|
|
19
|
+
return false;
|
|
20
|
+
if (platform !== "win32" && current.dev !== opened.dev)
|
|
21
|
+
return false;
|
|
22
|
+
return true;
|
|
23
|
+
}
|