agim-cli 1.2.107 → 1.2.109
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 +60 -1
- package/dist/cli-ui/env-file.js +1 -1
- package/dist/cli-ui/env-file.js.map +1 -1
- package/dist/core/llm/agent-loop.d.ts +41 -0
- package/dist/core/llm/agent-loop.d.ts.map +1 -1
- package/dist/core/llm/agent-loop.js +231 -142
- package/dist/core/llm/agent-loop.js.map +1 -1
- package/dist/core/llm/mcp-client.d.ts +10 -0
- package/dist/core/llm/mcp-client.d.ts.map +1 -1
- package/dist/core/llm/mcp-client.js +35 -6
- package/dist/core/llm/mcp-client.js.map +1 -1
- package/dist/core/llm/mcp-registry.d.ts +4 -0
- package/dist/core/llm/mcp-registry.d.ts.map +1 -1
- package/dist/core/llm/mcp-registry.js +36 -10
- package/dist/core/llm/mcp-registry.js.map +1 -1
- package/dist/core/llm/openai-compat-provider.d.ts +28 -5
- package/dist/core/llm/openai-compat-provider.d.ts.map +1 -1
- package/dist/core/llm/openai-compat-provider.js +72 -12
- package/dist/core/llm/openai-compat-provider.js.map +1 -1
- package/dist/plugins/agents/native/index.d.ts.map +1 -1
- package/dist/plugins/agents/native/index.js +30 -0
- package/dist/plugins/agents/native/index.js.map +1 -1
- package/dist/web/public/assets/{a2a-DCgHD3C7.js → a2a-DjyqPnWh.js} +2 -2
- package/dist/web/public/assets/{a2a-DCgHD3C7.js.map → a2a-DjyqPnWh.js.map} +1 -1
- package/dist/web/public/assets/{activity-CqIdqoFx.js → activity-Dy3elxiH.js} +2 -2
- package/dist/web/public/assets/{activity-CqIdqoFx.js.map → activity-Dy3elxiH.js.map} +1 -1
- package/dist/web/public/assets/{admins-Cfm5YCFY.js → admins-p5qJnsCS.js} +2 -2
- package/dist/web/public/assets/{admins-Cfm5YCFY.js.map → admins-p5qJnsCS.js.map} +1 -1
- package/dist/web/public/assets/{agents-CXy1c3ml.js → agents-BK-LNF6u.js} +2 -2
- package/dist/web/public/assets/{agents-CXy1c3ml.js.map → agents-BK-LNF6u.js.map} +1 -1
- package/dist/web/public/assets/{approvals-DzfXTuHD.js → approvals-BR1f5zmP.js} +2 -2
- package/dist/web/public/assets/{approvals-DzfXTuHD.js.map → approvals-BR1f5zmP.js.map} +1 -1
- package/dist/web/public/assets/{asks-hv8KYkMv.js → asks-1MaTIfZO.js} +2 -2
- package/dist/web/public/assets/{asks-hv8KYkMv.js.map → asks-1MaTIfZO.js.map} +1 -1
- package/dist/web/public/assets/{audit-N2_Hb8ID.js → audit-jSgqqxdG.js} +2 -2
- package/dist/web/public/assets/{audit-N2_Hb8ID.js.map → audit-jSgqqxdG.js.map} +1 -1
- package/dist/web/public/assets/{bell-B2EC2Smm.js → bell-LeCqn72n.js} +2 -2
- package/dist/web/public/assets/{bell-B2EC2Smm.js.map → bell-LeCqn72n.js.map} +1 -1
- package/dist/web/public/assets/{bgjobs-Dnik8OzW.js → bgjobs-BPyKhOuq.js} +2 -2
- package/dist/web/public/assets/{bgjobs-Dnik8OzW.js.map → bgjobs-BPyKhOuq.js.map} +1 -1
- package/dist/web/public/assets/{brain-CBk_86fd.js → brain-CPuDSbdC.js} +2 -2
- package/dist/web/public/assets/{brain-CBk_86fd.js.map → brain-CPuDSbdC.js.map} +1 -1
- package/dist/web/public/assets/{briefcase-BqvjWo9V.js → briefcase-Ck3PTo9z.js} +2 -2
- package/dist/web/public/assets/{briefcase-BqvjWo9V.js.map → briefcase-Ck3PTo9z.js.map} +1 -1
- package/dist/web/public/assets/{chevron-right-JI5zm1vU.js → chevron-right-CNLUFBr6.js} +2 -2
- package/dist/web/public/assets/{chevron-right-JI5zm1vU.js.map → chevron-right-CNLUFBr6.js.map} +1 -1
- package/dist/web/public/assets/{circle-check-BoARt50L.js → circle-check-CsSqoqeq.js} +2 -2
- package/dist/web/public/assets/{circle-check-BoARt50L.js.map → circle-check-CsSqoqeq.js.map} +1 -1
- package/dist/web/public/assets/{circle-check-big-DElVlKvH.js → circle-check-big-NZvva0KN.js} +2 -2
- package/dist/web/public/assets/{circle-check-big-DElVlKvH.js.map → circle-check-big-NZvva0KN.js.map} +1 -1
- package/dist/web/public/assets/{circle-x-DO-2tGgt.js → circle-x-CFWz6gEd.js} +2 -2
- package/dist/web/public/assets/{circle-x-DO-2tGgt.js.map → circle-x-CFWz6gEd.js.map} +1 -1
- package/dist/web/public/assets/{confirm-dialog-WAkeu7Va.js → confirm-dialog-DgO--07o.js} +2 -2
- package/dist/web/public/assets/{confirm-dialog-WAkeu7Va.js.map → confirm-dialog-DgO--07o.js.map} +1 -1
- package/dist/web/public/assets/{data-table-BXUgaJFG.js → data-table-D-Zgmc4f.js} +2 -2
- package/dist/web/public/assets/{data-table-BXUgaJFG.js.map → data-table-D-Zgmc4f.js.map} +1 -1
- package/dist/web/public/assets/{dialog-C1_Gr4a-.js → dialog-CQgpA4K2.js} +2 -2
- package/dist/web/public/assets/{dialog-C1_Gr4a-.js.map → dialog-CQgpA4K2.js.map} +1 -1
- package/dist/web/public/assets/{download-DaQMiRdc.js → download-DKzhn49i.js} +2 -2
- package/dist/web/public/assets/{download-DaQMiRdc.js.map → download-DKzhn49i.js.map} +1 -1
- package/dist/web/public/assets/{email-DaE3P0Pi.js → email-j2wp12wu.js} +2 -2
- package/dist/web/public/assets/{email-DaE3P0Pi.js.map → email-j2wp12wu.js.map} +1 -1
- package/dist/web/public/assets/{empty-state--wTr0nEY.js → empty-state-C_hR--ro.js} +2 -2
- package/dist/web/public/assets/{empty-state--wTr0nEY.js.map → empty-state-C_hR--ro.js.map} +1 -1
- package/dist/web/public/assets/{external-link-BMsOzlAO.js → external-link-CoqEoAcO.js} +2 -2
- package/dist/web/public/assets/{external-link-BMsOzlAO.js.map → external-link-CoqEoAcO.js.map} +1 -1
- package/dist/web/public/assets/{eye-DyUOG2VV.js → eye-CXOBz4Cl.js} +2 -2
- package/dist/web/public/assets/{eye-DyUOG2VV.js.map → eye-CXOBz4Cl.js.map} +1 -1
- package/dist/web/public/assets/{facts-BsU_itZp.js → facts-CJDnig7y.js} +2 -2
- package/dist/web/public/assets/{facts-BsU_itZp.js.map → facts-CJDnig7y.js.map} +1 -1
- package/dist/web/public/assets/{goals-D5MmIEKE.js → goals-D89OCyJN.js} +2 -2
- package/dist/web/public/assets/{goals-D5MmIEKE.js.map → goals-D89OCyJN.js.map} +1 -1
- package/dist/web/public/assets/{health-BHHdheFW.js → health-BQwZeXAS.js} +2 -2
- package/dist/web/public/assets/{health-BHHdheFW.js.map → health-BQwZeXAS.js.map} +1 -1
- package/dist/web/public/assets/{heart-pulse-CHnf3dk9.js → heart-pulse-BzJZEKl7.js} +2 -2
- package/dist/web/public/assets/{heart-pulse-CHnf3dk9.js.map → heart-pulse-BzJZEKl7.js.map} +1 -1
- package/dist/web/public/assets/{heartbeat-wKQjksPH.js → heartbeat-D4kJCmkw.js} +2 -2
- package/dist/web/public/assets/{heartbeat-wKQjksPH.js.map → heartbeat-D4kJCmkw.js.map} +1 -1
- package/dist/web/public/assets/{hot-BF4yOa64.js → hot-R6aOoPMY.js} +2 -2
- package/dist/web/public/assets/{hot-BF4yOa64.js.map → hot-R6aOoPMY.js.map} +1 -1
- package/dist/web/public/assets/{index-CUlfCXYW.js → index-DVf2XlVZ.js} +34 -34
- package/dist/web/public/assets/index-DVf2XlVZ.js.map +1 -0
- package/dist/web/public/assets/{installed-ChSSxLpe.js → installed-DSJ7AiMe.js} +2 -2
- package/dist/web/public/assets/{installed-ChSSxLpe.js.map → installed-DSJ7AiMe.js.map} +1 -1
- package/dist/web/public/assets/{jobs-Bl_FJXMV.js → jobs-CdVDgq8m.js} +2 -2
- package/dist/web/public/assets/{jobs-Bl_FJXMV.js.map → jobs-CdVDgq8m.js.map} +1 -1
- package/dist/web/public/assets/{layout-rdDZbKKf.js → layout-BKjENLxQ.js} +2 -2
- package/dist/web/public/assets/{layout-rdDZbKKf.js.map → layout-BKjENLxQ.js.map} +1 -1
- package/dist/web/public/assets/{layout-CTgw-R_J.js → layout-BLKq93Z4.js} +2 -2
- package/dist/web/public/assets/{layout-CTgw-R_J.js.map → layout-BLKq93Z4.js.map} +1 -1
- package/dist/web/public/assets/{layout-B4VFa896.js → layout-BqilVxGS.js} +2 -2
- package/dist/web/public/assets/{layout-B4VFa896.js.map → layout-BqilVxGS.js.map} +1 -1
- package/dist/web/public/assets/{layout-BnTMi2Vm.js → layout-CvmElCgO.js} +2 -2
- package/dist/web/public/assets/{layout-BnTMi2Vm.js.map → layout-CvmElCgO.js.map} +1 -1
- package/dist/web/public/assets/{layout-DNcGVE9i.js → layout-Dj03GsQJ.js} +2 -2
- package/dist/web/public/assets/{layout-DNcGVE9i.js.map → layout-Dj03GsQJ.js.map} +1 -1
- package/dist/web/public/assets/{llm-2BhcOCUt.js → llm-C5Uob7Ci.js} +2 -2
- package/dist/web/public/assets/{llm-2BhcOCUt.js.map → llm-C5Uob7Ci.js.map} +1 -1
- package/dist/web/public/assets/{loader-circle-BeeODmoF.js → loader-circle-DdWIiO8L.js} +2 -2
- package/dist/web/public/assets/{loader-circle-BeeODmoF.js.map → loader-circle-DdWIiO8L.js.map} +1 -1
- package/dist/web/public/assets/{map-pin-Bib-61c8.js → map-pin-kO-wD4fU.js} +2 -2
- package/dist/web/public/assets/{map-pin-Bib-61c8.js.map → map-pin-kO-wD4fU.js.map} +1 -1
- package/dist/web/public/assets/{mcp-RhWfVYQA.js → mcp-DL5oUQgm.js} +2 -2
- package/dist/web/public/assets/{mcp-RhWfVYQA.js.map → mcp-DL5oUQgm.js.map} +1 -1
- package/dist/web/public/assets/{memos-D0aauhFt.js → memos-Ab9Rp5v_.js} +2 -2
- package/dist/web/public/assets/{memos-D0aauhFt.js.map → memos-Ab9Rp5v_.js.map} +1 -1
- package/dist/web/public/assets/{messengers-BWqF9z0b.js → messengers-D0a8jlLw.js} +2 -2
- package/dist/web/public/assets/{messengers-BWqF9z0b.js.map → messengers-D0a8jlLw.js.map} +1 -1
- package/dist/web/public/assets/{native-agent-Be7FW740.js → native-agent-Bnz7Inoh.js} +2 -2
- package/dist/web/public/assets/{native-agent-Be7FW740.js.map → native-agent-Bnz7Inoh.js.map} +1 -1
- package/dist/web/public/assets/{network-CGq_PEku.js → network-BrQvlSjl.js} +2 -2
- package/dist/web/public/assets/{network-CGq_PEku.js.map → network-BrQvlSjl.js.map} +1 -1
- package/dist/web/public/assets/{outbox-B8raDIpY.js → outbox-BNDPAlVy.js} +2 -2
- package/dist/web/public/assets/{outbox-B8raDIpY.js.map → outbox-BNDPAlVy.js.map} +1 -1
- package/dist/web/public/assets/{pagination-BaOx4wBN.js → pagination-CyoXKAm9.js} +2 -2
- package/dist/web/public/assets/{pagination-BaOx4wBN.js.map → pagination-CyoXKAm9.js.map} +1 -1
- package/dist/web/public/assets/{persona-DfJOKp-G.js → persona-Bxqqj1yV.js} +2 -2
- package/dist/web/public/assets/{persona-DfJOKp-G.js.map → persona-Bxqqj1yV.js.map} +1 -1
- package/dist/web/public/assets/{play-Bn15ljVu.js → play-B7ZN5aFX.js} +2 -2
- package/dist/web/public/assets/{play-Bn15ljVu.js.map → play-B7ZN5aFX.js.map} +1 -1
- package/dist/web/public/assets/{plus-ta7et8lU.js → plus-DzvDGrh0.js} +2 -2
- package/dist/web/public/assets/{plus-ta7et8lU.js.map → plus-DzvDGrh0.js.map} +1 -1
- package/dist/web/public/assets/{policy-BIPMjtiz.js → policy-CSl5ENVs.js} +2 -2
- package/dist/web/public/assets/{policy-BIPMjtiz.js.map → policy-CSl5ENVs.js.map} +1 -1
- package/dist/web/public/assets/{refresh-ccw-CweZfxgw.js → refresh-ccw-B8Fzl2uq.js} +2 -2
- package/dist/web/public/assets/{refresh-ccw-CweZfxgw.js.map → refresh-ccw-B8Fzl2uq.js.map} +1 -1
- package/dist/web/public/assets/{reminders-uWllha6u.js → reminders-CmLd8DLt.js} +2 -2
- package/dist/web/public/assets/{reminders-uWllha6u.js.map → reminders-CmLd8DLt.js.map} +1 -1
- package/dist/web/public/assets/{save-BsDR6-4Z.js → save-JKib3YIn.js} +2 -2
- package/dist/web/public/assets/{save-BsDR6-4Z.js.map → save-JKib3YIn.js.map} +1 -1
- package/dist/web/public/assets/{schedules-COWq8_0O.js → schedules-B-770HIa.js} +2 -2
- package/dist/web/public/assets/{schedules-COWq8_0O.js.map → schedules-B-770HIa.js.map} +1 -1
- package/dist/web/public/assets/{search-COTgXLCn.js → search-Bc7OS8FB.js} +2 -2
- package/dist/web/public/assets/{search-COTgXLCn.js.map → search-Bc7OS8FB.js.map} +1 -1
- package/dist/web/public/assets/{security-BCuwv4RN.js → security-CD8INSD5.js} +2 -2
- package/dist/web/public/assets/{security-BCuwv4RN.js.map → security-CD8INSD5.js.map} +1 -1
- package/dist/web/public/assets/{service-6x5gzuKT.js → service-CFB2QfK6.js} +2 -2
- package/dist/web/public/assets/{service-6x5gzuKT.js.map → service-CFB2QfK6.js.map} +1 -1
- package/dist/web/public/assets/{status-badge-CiY-R4cR.js → status-badge---MleYSt.js} +2 -2
- package/dist/web/public/assets/{status-badge-CiY-R4cR.js.map → status-badge---MleYSt.js.map} +1 -1
- package/dist/web/public/assets/{subtasks-QTIxglPc.js → subtasks-CzToK_65.js} +2 -2
- package/dist/web/public/assets/{subtasks-QTIxglPc.js.map → subtasks-CzToK_65.js.map} +1 -1
- package/dist/web/public/assets/{table-BQd1FahX.js → table-Dg3N5NYh.js} +2 -2
- package/dist/web/public/assets/{table-BQd1FahX.js.map → table-Dg3N5NYh.js.map} +1 -1
- package/dist/web/public/assets/{topn-D7mnFIN8.js → topn-CePuMziu.js} +2 -2
- package/dist/web/public/assets/{topn-D7mnFIN8.js.map → topn-CePuMziu.js.map} +1 -1
- package/dist/web/public/assets/{trash-2-CqpK78qO.js → trash-2-LmyUghia.js} +2 -2
- package/dist/web/public/assets/{trash-2-CqpK78qO.js.map → trash-2-LmyUghia.js.map} +1 -1
- package/dist/web/public/assets/{use-background-tasks-E1AqQHuk.js → use-background-tasks-CmVigPtZ.js} +2 -2
- package/dist/web/public/assets/{use-background-tasks-E1AqQHuk.js.map → use-background-tasks-CmVigPtZ.js.map} +1 -1
- package/dist/web/public/assets/{use-llm-admin-C5j8A81P.js → use-llm-admin-D8Go_PKM.js} +2 -2
- package/dist/web/public/assets/{use-llm-admin-C5j8A81P.js.map → use-llm-admin-D8Go_PKM.js.map} +1 -1
- package/dist/web/public/assets/{use-memory-e4psRiYj.js → use-memory-BDuQTDX0.js} +2 -2
- package/dist/web/public/assets/{use-memory-e4psRiYj.js.map → use-memory-BDuQTDX0.js.map} +1 -1
- package/dist/web/public/assets/{use-observability-xdJotn0L.js → use-observability-CiO9A5Z9.js} +2 -2
- package/dist/web/public/assets/{use-observability-xdJotn0L.js.map → use-observability-CiO9A5Z9.js.map} +1 -1
- package/dist/web/public/assets/{use-settings-DbvYjLo8.js → use-settings-fG28ShvX.js} +2 -2
- package/dist/web/public/assets/{use-settings-DbvYjLo8.js.map → use-settings-fG28ShvX.js.map} +1 -1
- package/dist/web/public/assets/{use-workspace-CXkiS0Ho.js → use-workspace-CE3JTeap.js} +2 -2
- package/dist/web/public/assets/{use-workspace-CXkiS0Ho.js.map → use-workspace-CE3JTeap.js.map} +1 -1
- package/dist/web/public/assets/{useQuery-CPWNEU8n.js → useQuery-BtY7wSLM.js} +2 -2
- package/dist/web/public/assets/{useQuery-CPWNEU8n.js.map → useQuery-BtY7wSLM.js.map} +1 -1
- package/dist/web/public/assets/{vector-B52Da4cl.js → vector-CakzHMP0.js} +2 -2
- package/dist/web/public/assets/{vector-B52Da4cl.js.map → vector-CakzHMP0.js.map} +1 -1
- package/dist/web/public/assets/{viewer-Fr6DcgPb.js → viewer-DWOOXMMI.js} +2 -2
- package/dist/web/public/assets/{viewer-Fr6DcgPb.js.map → viewer-DWOOXMMI.js.map} +1 -1
- package/dist/web/public/assets/{workspace-B9SHz-uT.js → workspace-BnlWyXR1.js} +2 -2
- package/dist/web/public/assets/{workspace-B9SHz-uT.js.map → workspace-BnlWyXR1.js.map} +1 -1
- package/dist/web/public/assets/{workspaces-DNPDEzPP.js → workspaces-DTXat9Xq.js} +2 -2
- package/dist/web/public/assets/{workspaces-DNPDEzPP.js.map → workspaces-DTXat9Xq.js.map} +1 -1
- package/dist/web/public/assets/{x-DqRovlI4.js → x-BVwIUHdL.js} +2 -2
- package/dist/web/public/assets/{x-DqRovlI4.js.map → x-BVwIUHdL.js.map} +1 -1
- package/dist/web/public/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/public/assets/index-CUlfCXYW.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,66 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
## [1.2.
|
|
7
|
+
## [1.2.109] - 2026-05-29
|
|
8
|
+
|
|
9
|
+
### Changed (native agent stability, batch P0b — gap ② closed)
|
|
10
|
+
|
|
11
|
+
- **agent-loop 支持工具调用并行(只读类按 caller 声明的白名单)。** 此前
|
|
12
|
+
`for (const call of result.toolCalls) { await dispatch(...) }` 强制把模型一次返回
|
|
13
|
+
的多个工具调用串行起来——5 个独立 `native_read_file` 也要排队跑,长 turn 时延被
|
|
14
|
+
线性放大。现在 `AgentLoopInput.parallelSafeTools?: ReadonlySet<string>` 让 caller
|
|
15
|
+
声明哪些工具名是 parallel-safe(无副作用、不共享可变状态、顺序无关);同一迭代
|
|
16
|
+
内声明安全的调用走 `Promise.allSettled` 并发,非声明的工具仍串行并在自身派发前
|
|
17
|
+
把累计 pending 全部 drain(serial barrier,让串行调用看到一个 settled world)。
|
|
18
|
+
- **history 与 toolCalls 的发射严格按原始 call 顺序**——采用 slot 数组在派发完成
|
|
19
|
+
后一次性按 index 提交,避免完成顺序与 tool_call_id 对齐失序(部分 OpenAI 兼容
|
|
20
|
+
provider 对此敏感)。
|
|
21
|
+
- **stuck-loop 和 goal-critic 检查相应移到批次发射之后**:仍正确触发(last-3
|
|
22
|
+
比对 + critic 新增 `lastCriticAtToolCalls` 节流,按"至少每 N 次工具调用一次"
|
|
23
|
+
的语义保持,不会因为一次 iteration 多个调用而误增触发频率)。
|
|
24
|
+
- **native 内置声明的 parallel-safe 集合**(`NATIVE_PARALLEL_SAFE_TOOLS`):
|
|
25
|
+
`native_echo` / `native_now` / `native_random_uuid` / `native_read_file` /
|
|
26
|
+
`native_list_dir` / `native_glob` / `native_grep` / `native_web_fetch` /
|
|
27
|
+
`native_web_search` / `mcp__imhub__{read_skill,list_skills,search_memos,
|
|
28
|
+
memory_list,memory_query}`。`native_exec` / `native_write_file` / 任何写类
|
|
29
|
+
memo / `push_message` / `ask_user` / `call_agent` / `long_task` /
|
|
30
|
+
`complete_goal` / 其他 MCP 均不在内(保持串行 + barrier)。
|
|
31
|
+
|
|
32
|
+
### Tests
|
|
33
|
+
|
|
34
|
+
4 个集成测试覆盖:并发耗时≈max(非 sum)、缺省 parallelSafeTools 完全串行(backwards-
|
|
35
|
+
compat)、完成顺序 ≠ 原始顺序时 history 仍按原始顺序、serial barrier 在 mixed 序列
|
|
36
|
+
正确 drain。整套 P0a+P0b 测试 45/45 全绿;typecheck 干净。
|
|
37
|
+
|
|
38
|
+
## [1.2.108] - 2026-05-29
|
|
39
|
+
|
|
40
|
+
### Changed (native agent stability, batch P0a — 3 of 7 gaps closed)
|
|
41
|
+
|
|
42
|
+
- **provider 重试现在尊重 `Retry-After` 头 + 指数退避 + jitter(默认 3 次)。**
|
|
43
|
+
之前是固定 250ms × attempt 线性 backoff、retry=1、不读 `Retry-After`,导致
|
|
44
|
+
DeepSeek / Moonshot 等 OpenAI-compat 提供方返回 429 时把服务器要求的几秒等待
|
|
45
|
+
完全无视,250ms 后再撞墙、跳到 fallback provider,吃 cold-start + 模型语义切换。
|
|
46
|
+
现在默认 4 次尝试(base 250ms、cap 30s、jitter ±20%),`Retry-After` 作为硬下限
|
|
47
|
+
(秒/HTTP-date 两种 RFC 7231 格式都支持)。`IMHUB_LLM_RETRY_MAX` 覆盖默认(clamp
|
|
48
|
+
[0,10])。导出 `parseRetryAfter` / `computeBackoffMs` 两个纯函数 + 13 个回归测试。
|
|
49
|
+
- **stuck-loop 早停升级为 `(name, argsKey, errorType)` 三元组。** v1.2.95 用的是
|
|
50
|
+
`(name, isError, preview[0..200])`:模型只要让结果文本带个时间戳/换行/计数器就
|
|
51
|
+
绕过、绕到 max-iter 才停。新版用 `stableStringify(arguments)`(递归排序键,确定性
|
|
52
|
+
哈希)+ `extractErrorType`(错误首行 ≤80 字符指纹)。同样的工具同样的参数同样的
|
|
53
|
+
失败 3 次 → 停。导出 `stableStringify` / `extractErrorType` / `isStuckLoop` +
|
|
54
|
+
16 个回归测试(含明确的"结果抖动不再隐藏死循环"对照)。
|
|
55
|
+
- **MCP tools 缓存加 TTL(默认 5 min)+ 重连改指数退避(base 5s, cap 60s,
|
|
56
|
+
成功重置)。** v1.2.96 的 cachedTools 永不刷新——上游 MCP server 加新工具/改
|
|
57
|
+
schema 必须重启 agim 才看得见;重连冷却也是固定 5s,永远 broken 的 server 被
|
|
58
|
+
5s 一次永久敲。现在:`McpClient.cachedTools` 带 `fetchedAt`,
|
|
59
|
+
`IMHUB_MCP_TOOLS_TTL_MS` 控制 TTL(0=不缓存),新增 `refresh()` 主动清缓存;
|
|
60
|
+
`mcp-registry.tryReconnect` 按 `state.reconnectFailures` 指数翻倍冷却,成功
|
|
61
|
+
立即 reset 为 0。导出 `computeReconnectCooldownMs` + 8 个回归测试。
|
|
62
|
+
|
|
63
|
+
### Tests
|
|
64
|
+
|
|
65
|
+
37 new unit tests across 3 files;41 个 P0a 相关测试全绿;typecheck 干净。
|
|
66
|
+
|
|
8
67
|
|
|
9
68
|
### CI
|
|
10
69
|
|
package/dist/cli-ui/env-file.js
CHANGED
|
@@ -198,9 +198,9 @@ export function updateEnvFile(updates) {
|
|
|
198
198
|
}
|
|
199
199
|
/** Wipe a single key. No-op if missing. */
|
|
200
200
|
export function unsetEnvKey(key) {
|
|
201
|
+
assertEnvWriteAllowed();
|
|
201
202
|
if (!existsSync(ENV_FILE))
|
|
202
203
|
return;
|
|
203
|
-
assertEnvWriteAllowed();
|
|
204
204
|
const current = readEnvFile();
|
|
205
205
|
if (!(key in current))
|
|
206
206
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../../src/cli-ui/env-file.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,+DAA+D;AAC/D,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAE1E,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACjH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB,QAAQ;IAC7D,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAC/B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM;QACpB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAA;IAC9B,IAAI,CAAC,SAAS;QAAE,OAAM;IAEtB,uEAAuE;IACvE,uEAAuE;IACvE,wEAAwE;IACxE,yEAAyE;IACzE,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QAAC,IAAI,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,IAAI,GAAG,MAAM,EAAE,CAAA;IAAC,CAAC;IAC/D,IAAI,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACzB,IAAI,CAAC;QAAC,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,iCAAiC,CAAC,CAAC;IAE3E,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAM;IAEtD,MAAM,IAAI,KAAK,CACb,iCAAiC,MAAM,iCAAiC;QACxE,6BAA6B,IAAI,kCAAkC;QACnE,uEAAuE;QACvE,oEAAoE;QACpE,uEAAuE;QACvE,8CAA8C,CAC/C,CAAA;AACH,CAAC;AAED;0EAC0E;AAC1E,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,MAAM,GAA2B,EAAE,CAAA;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC3C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAQ;YACjD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC/B,IAAI,EAAE,IAAI,CAAC;gBAAE,SAAQ;YACrB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YACvC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;QACnB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,yBAAyB,CAAC,CAAC;IACrC,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;iEAGiE;AACjE,MAAM,eAAe,GAAG;IACtB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;IACnB,oBAAoB;CACZ,CAAA;AAEV,MAAM,iBAAiB,GAAG,oCAAoC,CAAA;AAE9D;;;;;iFAKiF;AACjF,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAAE,OAAO,EAAE,CAAA;IAC7C,MAAM,MAAM,GAA2B,EAAE,CAAA;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAA;QACpD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC;gBAAE,SAAQ;YAChD,mEAAmE;YACnE,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAA;YACtD,gEAAgE;YAChE,8DAA8D;YAC9D,8DAA8D;YAC9D,iEAAiE;YACjE,qBAAqB;YACrB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;YAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;gBAC7E,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;gBAC3B,IAAI,EAAE,IAAI,CAAC;oBAAE,SAAQ;gBACrB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;gBAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;gBAC7B,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,6CAA6C,CAAC,CAAC;IACzD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;8EAQ8E;AAC9E,MAAM,UAAU,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAA;IACrC,MAAM,MAAM,GAA2B,EAAE,GAAG,QAAQ,EAAE,CAAA;IACtD,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,SAAQ;QACrC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAChE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAW,CAAA;YACpC,SAAQ;QACV,CAAC;QACD,IAAI,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAC1D,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;uBAGuB;AACvB,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,MAAM,IAAI,GAAG,WAAW,EAAE,CAAA;IAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACzC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAChF,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAA;IACjC,OAAO,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAA;AAC1D,CAAC;AAED;;+BAE+B;AAC/B,MAAM,UAAU,aAAa,CAAC,OAAkD;IAC9E,qBAAqB,EAAE,CAAA;IACvB,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;IAC7B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAA;;YAC/C,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACrB,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClE,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACtE,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAM;IACjC,
|
|
1
|
+
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../../src/cli-ui/env-file.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,+DAA+D;AAC/D,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAE1E,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACjH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEjD,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB,QAAQ;IAC7D,MAAM,SAAS,GACb,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAC/B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM;QACpB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAA;IAC9B,IAAI,CAAC,SAAS;QAAE,OAAM;IAEtB,uEAAuE;IACvE,uEAAuE;IACvE,wEAAwE;IACxE,yEAAyE;IACzE,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QAAC,IAAI,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC;QAAC,IAAI,GAAG,MAAM,EAAE,CAAA;IAAC,CAAC;IAC/D,IAAI,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACzB,IAAI,CAAC;QAAC,GAAG,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,iCAAiC,CAAC,CAAC;IAE3E,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAM;IAEtD,MAAM,IAAI,KAAK,CACb,iCAAiC,MAAM,iCAAiC;QACxE,6BAA6B,IAAI,kCAAkC;QACnE,uEAAuE;QACvE,oEAAoE;QACpE,uEAAuE;QACvE,8CAA8C,CAC/C,CAAA;AACH,CAAC;AAED;0EAC0E;AAC1E,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,MAAM,GAA2B,EAAE,CAAA;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAC3C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAQ;YACjD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;YAC/B,IAAI,EAAE,IAAI,CAAC;gBAAE,SAAQ;YACrB,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;YACvC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;QACnB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,yBAAyB,CAAC,CAAC;IACrC,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;iEAGiE;AACjE,MAAM,eAAe,GAAG;IACtB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;IACnB,oBAAoB;CACZ,CAAA;AAEV,MAAM,iBAAiB,GAAG,oCAAoC,CAAA;AAE9D;;;;;iFAKiF;AACjF,MAAM,UAAU,kBAAkB;IAChC,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAAE,OAAO,EAAE,CAAA;IAC7C,MAAM,MAAM,GAA2B,EAAE,CAAA;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAA;QACpD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC;gBAAE,SAAQ;YAChD,mEAAmE;YACnE,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAA;YACtD,gEAAgE;YAChE,8DAA8D;YAC9D,8DAA8D;YAC9D,iEAAiE;YACjE,qBAAqB;YACrB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;YAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;gBAC7E,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;gBAC3B,IAAI,EAAE,IAAI,CAAC;oBAAE,SAAQ;gBACrB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;gBAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;gBAC7B,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,6CAA6C,CAAC,CAAC;IACzD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;8EAQ8E;AAC9E,MAAM,UAAU,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAA;IACrC,MAAM,MAAM,GAA2B,EAAE,GAAG,QAAQ,EAAE,CAAA;IACtD,KAAK,MAAM,CAAC,IAAI,eAAe,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,SAAQ;QACrC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAChE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAW,CAAA;YACpC,SAAQ;QACV,CAAC;QACD,IAAI,OAAO,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAC1D,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;uBAGuB;AACvB,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,MAAM,IAAI,GAAG,WAAW,EAAE,CAAA;IAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAA;IACzC,IAAI,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAChF,MAAM,IAAI,GAAG,kBAAkB,EAAE,CAAA;IACjC,OAAO,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAA;AAC1D,CAAC;AAED;;+BAE+B;AAC/B,MAAM,UAAU,aAAa,CAAC,OAAkD;IAC9E,qBAAqB,EAAE,CAAA;IACvB,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;IAC7B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAA;;YAC/C,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACrB,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACjD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClE,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACtE,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,qBAAqB,EAAE,CAAA;IACvB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAM;IACjC,MAAM,OAAO,GAAG,WAAW,EAAE,CAAA;IAC7B,IAAI,CAAC,CAAC,GAAG,IAAI,OAAO,CAAC;QAAE,OAAM;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAA;IACnB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClE,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACtE,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,aAAa;IAC3B,qBAAqB,EAAE,CAAA;IACvB,IAAI,CAAC;QAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;AAC3D,CAAC"}
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { FinishReason, LlmMessage, LlmProvider, LlmUsage, ToolCallRequest, ToolChoice, ToolDef } from './provider-base.js';
|
|
2
2
|
import type { ToolDispatcher } from './tool-dispatcher.js';
|
|
3
|
+
/**
|
|
4
|
+
* Deterministic JSON.stringify with sorted object keys at every level.
|
|
5
|
+
* Used by the stuck-loop detector to derive a stable identity key for
|
|
6
|
+
* tool-call arguments: identical calls hash to identical strings
|
|
7
|
+
* regardless of key insertion order. Tolerates circular references and
|
|
8
|
+
* non-JSON-able values by collapsing them to a placeholder.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stableStringify(value: unknown, seen?: WeakSet<object>): string;
|
|
11
|
+
/**
|
|
12
|
+
* Stable error-type fingerprint of a dispatcher's result text. Returns
|
|
13
|
+
* the first non-empty line trimmed and truncated to 80 chars when
|
|
14
|
+
* isError; empty string otherwise. Used by the stuck-loop detector so
|
|
15
|
+
* "fs_read failed with ENOENT three times" → stuck, but "fs_read
|
|
16
|
+
* returned different bytes three times" → not stuck.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractErrorType(isError: boolean, text: string): string;
|
|
19
|
+
/** True iff the last 3 entries are the same tool called with identical
|
|
20
|
+
* args resulting in the identical outcome — the stuck-loop signal. */
|
|
21
|
+
export declare function isStuckLoop(reports: Array<Pick<ToolCallReport, 'name' | 'argsKey' | 'errorType'>>): boolean;
|
|
3
22
|
/**
|
|
4
23
|
* Per-tool-call gate. Return 'allow' to dispatch, 'deny' to inject a
|
|
5
24
|
* "user denied" tool result (so the model can react) without running
|
|
@@ -92,6 +111,17 @@ export interface AgentLoopInput {
|
|
|
92
111
|
goalTitle?: string;
|
|
93
112
|
goalBody?: string;
|
|
94
113
|
};
|
|
114
|
+
/** v1.2.109 — names of tools the caller declares parallel-safe (no
|
|
115
|
+
* shared state, no side effects, order-independent). When the model
|
|
116
|
+
* returns multiple tool_calls in one iteration, declared-safe calls
|
|
117
|
+
* run concurrently via Promise.allSettled; everything else (and any
|
|
118
|
+
* tool not in this set) keeps the legacy serial behaviour, with a
|
|
119
|
+
* barrier drain before each serial dispatch so serial calls observe
|
|
120
|
+
* a settled world. Emission order to history + toolCalls is always
|
|
121
|
+
* the original call order (tool_call_id ↔ tool_result alignment).
|
|
122
|
+
* Leave undefined → loop is fully serial (no behaviour change vs
|
|
123
|
+
* v1.2.108). */
|
|
124
|
+
parallelSafeTools?: ReadonlySet<string>;
|
|
95
125
|
/** Lifecycle hooks for caller-side observability (heartbeats, progress
|
|
96
126
|
* push, custom logging). All hooks are best-effort: thrown errors are
|
|
97
127
|
* swallowed so a faulty observer cannot break the loop. */
|
|
@@ -119,6 +149,17 @@ export interface ToolCallReport {
|
|
|
119
149
|
* `/heartbeat run-now` style introspection; longer payloads aren't
|
|
120
150
|
* retained to keep memory + log size bounded. */
|
|
121
151
|
preview: string;
|
|
152
|
+
/** v1.2.108 — stable JSON of the tool call's arguments (sorted keys),
|
|
153
|
+
* used by the stuck-loop detector. Identical calls hash to identical
|
|
154
|
+
* strings; model varying timestamps/UUIDs in the result no longer
|
|
155
|
+
* hides a stuck loop where the model keeps re-calling with the same
|
|
156
|
+
* args. See `stableStringify`. */
|
|
157
|
+
argsKey: string;
|
|
158
|
+
/** v1.2.108 — short fingerprint of the error (first non-empty line of
|
|
159
|
+
* the result, ≤80 chars) when isError; empty string otherwise. Lets
|
|
160
|
+
* the stuck-loop detector distinguish "same failure repeated" from
|
|
161
|
+
* "same args returning different bytes". See `extractErrorType`. */
|
|
162
|
+
errorType: string;
|
|
122
163
|
}
|
|
123
164
|
export interface AgentLoopResult {
|
|
124
165
|
/** Final assistant text the caller should show the user. May be the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../../../src/core/llm/agent-loop.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAEV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,EACf,UAAU,EACV,OAAO,EACR,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAoB,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAgB5E;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAAG,CACzB,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,MAAM,KACZ,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,CAAA;AAE9B;;+BAE+B;AAC/B,eAAO,MAAM,eAAe,EAAE,YAAkC,CAAA;AAEhE;;;;;;;;;qDASqD;AACrD;;;4DAG4D;AAC5D,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,gBAAgB,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,CAAA;AAEvG,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,WAAW,CAAA;IACrB;;iEAE6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;gCAC4B;IAC5B,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB;yCACqC;IACrC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAA;IACjB,sEAAsE;IACtE,UAAU,CAAC,EAAE,UAAU,CAAA;IAEvB;+DAC2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,wEAAwE;IACxE,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,qEAAqE;IACrE,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAE1E;4EACwE;IACxE,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,kDAAkD;IAClD,OAAO,CAAC,EAAE,YAAY,CAAA;IAEtB;gFAC4E;IAC5E,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,CAAA;IAED;;;;8BAI0B;IAC1B,OAAO,CAAC,EAAE;QACR,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;QACjB,QAAQ,EAAE,MAAM,CAAA;KACjB,CAAA;IAED;;;;;;sBAMkB;IAClB,YAAY,CAAC,EAAE;QACb,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;KAClB,CAAA;IAED;;gEAE4D;IAC5D,KAAK,CAAC,EAAE;QACN;mDAC2C;QAC3C,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAA;QAC7C,0DAA0D;QAC1D,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE;YAAE,OAAO,EAAE,OAAO,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAA;KAC/F,CAAA;CACF;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,iEAAiE;IACjE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAA;IAC1B,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB;;sDAEkD;IAClD,OAAO,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"agent-loop.d.ts","sourceRoot":"","sources":["../../../src/core/llm/agent-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,6 +56,57 @@ const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 min — generous; signal usually
|
|
|
56
56
|
const CRITIC_MIN_TOOL_CALLS = 5;
|
|
57
57
|
const CRITIC_EVERY_N = 5;
|
|
58
58
|
const CRITIC_MAX_CALLS = 2;
|
|
59
|
+
/**
|
|
60
|
+
* Deterministic JSON.stringify with sorted object keys at every level.
|
|
61
|
+
* Used by the stuck-loop detector to derive a stable identity key for
|
|
62
|
+
* tool-call arguments: identical calls hash to identical strings
|
|
63
|
+
* regardless of key insertion order. Tolerates circular references and
|
|
64
|
+
* non-JSON-able values by collapsing them to a placeholder.
|
|
65
|
+
*/
|
|
66
|
+
export function stableStringify(value, seen = new WeakSet()) {
|
|
67
|
+
if (value === null)
|
|
68
|
+
return 'null';
|
|
69
|
+
if (typeof value === 'undefined')
|
|
70
|
+
return 'null';
|
|
71
|
+
if (typeof value === 'function')
|
|
72
|
+
return '"[fn]"';
|
|
73
|
+
if (typeof value !== 'object')
|
|
74
|
+
return JSON.stringify(value);
|
|
75
|
+
if (seen.has(value))
|
|
76
|
+
return '"[circular]"';
|
|
77
|
+
seen.add(value);
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
return '[' + value.map((v) => stableStringify(v, seen)).join(',') + ']';
|
|
80
|
+
}
|
|
81
|
+
const obj = value;
|
|
82
|
+
const keys = Object.keys(obj).sort();
|
|
83
|
+
return '{' + keys.map((k) => JSON.stringify(k) + ':' + stableStringify(obj[k], seen)).join(',') + '}';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Stable error-type fingerprint of a dispatcher's result text. Returns
|
|
87
|
+
* the first non-empty line trimmed and truncated to 80 chars when
|
|
88
|
+
* isError; empty string otherwise. Used by the stuck-loop detector so
|
|
89
|
+
* "fs_read failed with ENOENT three times" → stuck, but "fs_read
|
|
90
|
+
* returned different bytes three times" → not stuck.
|
|
91
|
+
*/
|
|
92
|
+
export function extractErrorType(isError, text) {
|
|
93
|
+
if (!isError)
|
|
94
|
+
return '';
|
|
95
|
+
const firstNonEmpty = text.split('\n').find((line) => line.trim().length > 0) ?? '';
|
|
96
|
+
return firstNonEmpty.trim().slice(0, 80);
|
|
97
|
+
}
|
|
98
|
+
/** True iff the last 3 entries are the same tool called with identical
|
|
99
|
+
* args resulting in the identical outcome — the stuck-loop signal. */
|
|
100
|
+
export function isStuckLoop(reports) {
|
|
101
|
+
if (reports.length < 3)
|
|
102
|
+
return false;
|
|
103
|
+
const a = reports[reports.length - 1];
|
|
104
|
+
const b = reports[reports.length - 2];
|
|
105
|
+
const c = reports[reports.length - 3];
|
|
106
|
+
return a.name === b.name && b.name === c.name
|
|
107
|
+
&& a.argsKey === b.argsKey && b.argsKey === c.argsKey
|
|
108
|
+
&& a.errorType === b.errorType && b.errorType === c.errorType;
|
|
109
|
+
}
|
|
59
110
|
/** Default: skip approval entirely. The agent loop is safe-by-default
|
|
60
111
|
* for tests and the introspection use case; production AgentAdapters
|
|
61
112
|
* should pass a real gate. */
|
|
@@ -87,6 +138,11 @@ export async function runAgentLoop(input) {
|
|
|
87
138
|
const usageAcc = {};
|
|
88
139
|
// v1.2.98 — goal-critic per-turn fire counter (capped at CRITIC_MAX_CALLS).
|
|
89
140
|
let criticCallsFired = 0;
|
|
141
|
+
// v1.2.109 — throttle for the post-batch critic check. With parallel
|
|
142
|
+
// batching, toolCalls.length now jumps in chunks instead of advancing
|
|
143
|
+
// by 1 per per-call modulo check. Track the last fire to keep at-most-
|
|
144
|
+
// every-CRITIC_EVERY_N cadence regardless of batch size.
|
|
145
|
+
let lastCriticAtToolCalls = 0;
|
|
90
146
|
let iter = 0;
|
|
91
147
|
for (; iter < maxIter; iter++) {
|
|
92
148
|
if (signal.signal.aborted) {
|
|
@@ -154,29 +210,50 @@ export async function runAgentLoop(input) {
|
|
|
154
210
|
toolCalls: result.toolCalls,
|
|
155
211
|
reasoningContent: result.reasoningContent,
|
|
156
212
|
});
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
|
|
213
|
+
// v1.2.109 — execute tool calls with optional parallel batching.
|
|
214
|
+
// Tools whose names appear in input.parallelSafeTools dispatch via
|
|
215
|
+
// accumulating promises (Promise.allSettled); everything else (and
|
|
216
|
+
// any tool not declared safe) keeps the legacy serial path with a
|
|
217
|
+
// barrier drain before each serial dispatch so serial calls observe
|
|
218
|
+
// a settled world (MCP session state, file system, etc.).
|
|
219
|
+
// Emission order to history / toolCalls is always the original call
|
|
220
|
+
// order via slot arrays — some providers reject responses whose
|
|
221
|
+
// role:'tool' messages aren't laid out next to their assistant
|
|
222
|
+
// tool_calls in the expected order.
|
|
223
|
+
const parallelSafe = input.parallelSafeTools;
|
|
224
|
+
const historySlots = new Array(result.toolCalls.length).fill(null);
|
|
225
|
+
const reportSlots = new Array(result.toolCalls.length).fill(null);
|
|
226
|
+
const pendingTasks = [];
|
|
227
|
+
for (let idx = 0; idx < result.toolCalls.length; idx++) {
|
|
162
228
|
if (signal.signal.aborted)
|
|
163
229
|
break;
|
|
230
|
+
const call = result.toolCalls[idx];
|
|
231
|
+
// Stable arg key precomputed once per call so all three slot
|
|
232
|
+
// writers (deny / no-dispatcher / dispatched) share the same
|
|
233
|
+
// identity for the stuck-loop detector.
|
|
234
|
+
const argsKey = stableStringify(call.arguments);
|
|
164
235
|
const reportBase = {
|
|
165
236
|
name: call.name,
|
|
166
237
|
source: 'unknown',
|
|
167
238
|
preview: '',
|
|
239
|
+
argsKey,
|
|
240
|
+
errorType: '',
|
|
168
241
|
};
|
|
242
|
+
// Approval is ALWAYS serial — it may block on user input, and we
|
|
243
|
+
// don't want to ask for N approvals simultaneously (the IM UI
|
|
244
|
+
// would get spammed).
|
|
169
245
|
const decision = await safeApprove(approve, call, iter);
|
|
170
246
|
if (decision === 'deny') {
|
|
171
247
|
const denyText = `tool call denied by user: ${call.name}`;
|
|
172
|
-
|
|
173
|
-
|
|
248
|
+
historySlots[idx] = { role: 'tool', toolCallId: call.id, content: denyText };
|
|
249
|
+
reportSlots[idx] = {
|
|
174
250
|
...reportBase,
|
|
175
251
|
decision: 'deny',
|
|
176
252
|
isError: true,
|
|
177
253
|
durationMs: 0,
|
|
178
254
|
preview: denyText,
|
|
179
|
-
|
|
255
|
+
errorType: extractErrorType(true, denyText),
|
|
256
|
+
};
|
|
180
257
|
continue;
|
|
181
258
|
}
|
|
182
259
|
if (!dispatch) {
|
|
@@ -184,152 +261,164 @@ export async function runAgentLoop(input) {
|
|
|
184
261
|
// theoretically a caller could pass tools+approve without a
|
|
185
262
|
// dispatcher. Don't crash; tell the model the tool failed.
|
|
186
263
|
const denyText = `tool call refused: no dispatcher registered`;
|
|
187
|
-
|
|
188
|
-
|
|
264
|
+
historySlots[idx] = { role: 'tool', toolCallId: call.id, content: denyText };
|
|
265
|
+
reportSlots[idx] = {
|
|
189
266
|
...reportBase,
|
|
190
267
|
decision: 'allow',
|
|
191
268
|
isError: true,
|
|
192
269
|
durationMs: 0,
|
|
193
270
|
preview: denyText,
|
|
194
|
-
|
|
271
|
+
errorType: extractErrorType(true, denyText),
|
|
272
|
+
};
|
|
195
273
|
continue;
|
|
196
274
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
dispatched
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
input.hooks?.onToolEnd?.(call, { isError: !!dispatched.isError, durationMs });
|
|
217
|
-
}
|
|
218
|
-
catch { /* observer must not break loop */ }
|
|
219
|
-
history.push({
|
|
220
|
-
role: 'tool',
|
|
221
|
-
toolCallId: call.id,
|
|
222
|
-
content: dispatched.text,
|
|
223
|
-
});
|
|
224
|
-
toolCalls.push({
|
|
225
|
-
name: call.name,
|
|
226
|
-
decision: 'allow',
|
|
227
|
-
isError: dispatched.isError,
|
|
228
|
-
source: dispatched.source ?? 'unknown',
|
|
229
|
-
durationMs,
|
|
230
|
-
preview: dispatched.text.slice(0, 200),
|
|
231
|
-
});
|
|
232
|
-
// v1.2.95 — stuck-loop detector. If the last three tool reports
|
|
233
|
-
// share name + error-flag + preview content, the model is calling
|
|
234
|
-
// the SAME tool with the SAME outcome — almost always a sign it's
|
|
235
|
-
// re-running a failing SQL / fetch / command without changing
|
|
236
|
-
// strategy. Break out early so we don't waste the full
|
|
237
|
-
// maxIterations budget on a stuck model, and so the caller can
|
|
238
|
-
// surface a much more focused "you're stuck on X" recap.
|
|
239
|
-
//
|
|
240
|
-
// Three consecutive matches is the smallest threshold that
|
|
241
|
-
// unambiguously says "loop" without being triggered by a
|
|
242
|
-
// legitimate "two reads on the same file" sequence. The preview
|
|
243
|
-
// is already truncated to 200 chars at push time, so this
|
|
244
|
-
// comparison is bounded.
|
|
245
|
-
if (toolCalls.length >= 3) {
|
|
246
|
-
const a = toolCalls[toolCalls.length - 1];
|
|
247
|
-
const b = toolCalls[toolCalls.length - 2];
|
|
248
|
-
const c = toolCalls[toolCalls.length - 3];
|
|
249
|
-
if (a.name === b.name && b.name === c.name
|
|
250
|
-
&& a.isError === b.isError && b.isError === c.isError
|
|
251
|
-
&& a.preview === b.preview && b.preview === c.preview) {
|
|
275
|
+
// Capture dispatch into an async closure so it can be awaited
|
|
276
|
+
// either immediately (serial path) or later via Promise.allSettled
|
|
277
|
+
// (parallel batch path). Captures idx by closure so the writer
|
|
278
|
+
// hits the correct slot regardless of completion order.
|
|
279
|
+
const dispatchCall = dispatch;
|
|
280
|
+
const runDispatch = async () => {
|
|
281
|
+
try {
|
|
282
|
+
input.hooks?.onToolStart?.(call);
|
|
283
|
+
}
|
|
284
|
+
catch { /* observer must not break loop */ }
|
|
285
|
+
const t0 = Date.now();
|
|
286
|
+
let dispatched;
|
|
287
|
+
try {
|
|
288
|
+
dispatched = await dispatchCall(call, signal.signal);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
292
|
+
dispatched = { text: `tool threw: ${msg}`, isError: true, source: 'error' };
|
|
252
293
|
log.warn({
|
|
253
|
-
event: 'agent-loop.
|
|
254
|
-
tool:
|
|
255
|
-
|
|
256
|
-
iter,
|
|
257
|
-
totalToolCalls: toolCalls.length,
|
|
258
|
-
previewSnippet: (a.preview ?? '').slice(0, 80),
|
|
259
|
-
}, `stuck loop: 3× ${a.name} with identical outcome — early stop at iter ${iter}`);
|
|
260
|
-
signal.cleanup();
|
|
261
|
-
return {
|
|
262
|
-
text: '',
|
|
263
|
-
iterations: iter + 1,
|
|
264
|
-
toolCalls,
|
|
265
|
-
usage: usageAcc,
|
|
266
|
-
finishReason: 'stuck_loop',
|
|
267
|
-
};
|
|
294
|
+
event: 'agent-loop.dispatch_threw',
|
|
295
|
+
tool: call.name, err: msg,
|
|
296
|
+
});
|
|
268
297
|
}
|
|
298
|
+
const durationMs = Date.now() - t0;
|
|
299
|
+
try {
|
|
300
|
+
input.hooks?.onToolEnd?.(call, { isError: !!dispatched.isError, durationMs });
|
|
301
|
+
}
|
|
302
|
+
catch { /* observer must not break loop */ }
|
|
303
|
+
historySlots[idx] = {
|
|
304
|
+
role: 'tool',
|
|
305
|
+
toolCallId: call.id,
|
|
306
|
+
content: dispatched.text,
|
|
307
|
+
};
|
|
308
|
+
reportSlots[idx] = {
|
|
309
|
+
name: call.name,
|
|
310
|
+
decision: 'allow',
|
|
311
|
+
isError: dispatched.isError,
|
|
312
|
+
source: dispatched.source ?? 'unknown',
|
|
313
|
+
durationMs,
|
|
314
|
+
preview: dispatched.text.slice(0, 200),
|
|
315
|
+
argsKey,
|
|
316
|
+
errorType: extractErrorType(dispatched.isError, dispatched.text),
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
const isParallel = !!parallelSafe?.has(call.name);
|
|
320
|
+
if (isParallel) {
|
|
321
|
+
pendingTasks.push(runDispatch());
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
// Serial barrier: drain accumulated parallel work first.
|
|
325
|
+
if (pendingTasks.length > 0) {
|
|
326
|
+
await Promise.allSettled(pendingTasks.splice(0));
|
|
327
|
+
}
|
|
328
|
+
await runDispatch();
|
|
269
329
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
330
|
+
}
|
|
331
|
+
// End-of-iteration drain for any tail parallel tasks.
|
|
332
|
+
if (pendingTasks.length > 0) {
|
|
333
|
+
await Promise.allSettled(pendingTasks.splice(0));
|
|
334
|
+
}
|
|
335
|
+
// Commit slots in original call order so tool_call_id ↔ tool_result
|
|
336
|
+
// alignment is preserved regardless of dispatch completion order.
|
|
337
|
+
// Slots may be null if the loop aborted mid-iteration — skip those.
|
|
338
|
+
for (let idx = 0; idx < result.toolCalls.length; idx++) {
|
|
339
|
+
const h = historySlots[idx];
|
|
340
|
+
const r = reportSlots[idx];
|
|
341
|
+
if (h)
|
|
342
|
+
history.push(h);
|
|
343
|
+
if (r)
|
|
344
|
+
toolCalls.push(r);
|
|
345
|
+
}
|
|
346
|
+
// v1.2.108 — stuck-loop detector (now post-batch). Keyed on
|
|
347
|
+
// (name, argsKey, errorType) of the last 3 entries; with parallel
|
|
348
|
+
// batching this still fires correctly when 3 identical calls land
|
|
349
|
+
// in a row (whether in one batch or across batches).
|
|
350
|
+
if (isStuckLoop(toolCalls)) {
|
|
351
|
+
const a = toolCalls[toolCalls.length - 1];
|
|
352
|
+
log.warn({
|
|
353
|
+
event: 'agent-loop.stuck_loop_detected',
|
|
354
|
+
tool: a.name,
|
|
355
|
+
isError: a.isError,
|
|
356
|
+
argsKeySnippet: a.argsKey.slice(0, 200),
|
|
357
|
+
errorType: a.errorType,
|
|
358
|
+
iter,
|
|
359
|
+
totalToolCalls: toolCalls.length,
|
|
360
|
+
}, `stuck loop: 3× ${a.name} with identical args + outcome — early stop at iter ${iter}`);
|
|
361
|
+
signal.cleanup();
|
|
362
|
+
return {
|
|
363
|
+
text: '',
|
|
364
|
+
iterations: iter + 1,
|
|
365
|
+
toolCalls,
|
|
366
|
+
usage: usageAcc,
|
|
367
|
+
finishReason: 'stuck_loop',
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
// v1.2.98 / v1.2.99 / v1.2.109 — goal-critic check (semantic
|
|
371
|
+
// off-track), now post-batch with a since-last-fire throttle so
|
|
372
|
+
// cadence stays at-most-every-CRITIC_EVERY_N tool calls regardless
|
|
373
|
+
// of how many calls landed in this iteration's batch.
|
|
374
|
+
const criticMode = resolveCriticMode();
|
|
375
|
+
const criticGateOpen = criticMode === 'always'
|
|
376
|
+
|| (criticMode === 'goal-only' && !!input.criticAnchor?.goalTitle?.trim());
|
|
377
|
+
if (input.criticAnchor
|
|
378
|
+
&& criticGateOpen
|
|
379
|
+
&& toolCalls.length >= CRITIC_MIN_TOOL_CALLS
|
|
380
|
+
&& criticCallsFired < CRITIC_MAX_CALLS
|
|
381
|
+
&& toolCalls.length - lastCriticAtToolCalls >= CRITIC_EVERY_N) {
|
|
382
|
+
criticCallsFired += 1;
|
|
383
|
+
lastCriticAtToolCalls = toolCalls.length;
|
|
384
|
+
const recent = toolCalls.slice(-10);
|
|
385
|
+
const verdict = await runGoalCritic({
|
|
386
|
+
prompt: input.criticAnchor.prompt,
|
|
387
|
+
goalTitle: input.criticAnchor.goalTitle,
|
|
388
|
+
goalBody: input.criticAnchor.goalBody,
|
|
389
|
+
recentToolCalls: recent,
|
|
390
|
+
}, { signal: signal.signal });
|
|
391
|
+
log.info({
|
|
392
|
+
event: 'agent-loop.goal_critic_verdict',
|
|
393
|
+
iter,
|
|
394
|
+
totalToolCalls: toolCalls.length,
|
|
395
|
+
onTrack: verdict.onTrack,
|
|
396
|
+
reason: verdict.reason,
|
|
397
|
+
modelUsed: verdict.modelUsed,
|
|
398
|
+
});
|
|
399
|
+
if (!verdict.onTrack) {
|
|
400
|
+
log.warn({
|
|
401
|
+
event: 'agent-loop.off_track_detected',
|
|
303
402
|
iter,
|
|
304
|
-
totalToolCalls: toolCalls.length,
|
|
305
|
-
onTrack: verdict.onTrack,
|
|
306
403
|
reason: verdict.reason,
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// without re-importing goal-critic. Yes, abusing `error`
|
|
326
|
-
// for non-error info is mildly ugly; alternative is adding
|
|
327
|
-
// a new field to AgentLoopResult — overkill for one knob.
|
|
328
|
-
error: verdict.redirect
|
|
329
|
-
? `${verdict.reason} || redirect: ${verdict.redirect}`
|
|
330
|
-
: verdict.reason,
|
|
331
|
-
};
|
|
332
|
-
}
|
|
404
|
+
redirect: verdict.redirect,
|
|
405
|
+
}, `goal-critic flagged off-track at iter ${iter}: ${verdict.reason}`);
|
|
406
|
+
signal.cleanup();
|
|
407
|
+
return {
|
|
408
|
+
text: '',
|
|
409
|
+
iterations: iter + 1,
|
|
410
|
+
toolCalls,
|
|
411
|
+
usage: usageAcc,
|
|
412
|
+
finishReason: 'off_track',
|
|
413
|
+
// Smuggle the critic's reason + redirect into `error` so
|
|
414
|
+
// the native adapter can surface it verbatim in the recap
|
|
415
|
+
// without re-importing goal-critic. Yes, abusing `error`
|
|
416
|
+
// for non-error info is mildly ugly; alternative is adding
|
|
417
|
+
// a new field to AgentLoopResult — overkill for one knob.
|
|
418
|
+
error: verdict.redirect
|
|
419
|
+
? `${verdict.reason} || redirect: ${verdict.redirect}`
|
|
420
|
+
: verdict.reason,
|
|
421
|
+
};
|
|
333
422
|
}
|
|
334
423
|
}
|
|
335
424
|
// Loop tail → next iteration calls provider with appended history.
|