agim-cli 1.2.130 → 1.2.131
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 +55 -0
- package/dist/core/llm/plan-exit-dispatcher.d.ts +27 -0
- package/dist/core/llm/plan-exit-dispatcher.d.ts.map +1 -0
- package/dist/core/llm/plan-exit-dispatcher.js +210 -0
- package/dist/core/llm/plan-exit-dispatcher.js.map +1 -0
- package/dist/core/plan-history.d.ts +34 -0
- package/dist/core/plan-history.d.ts.map +1 -0
- package/dist/core/plan-history.js +89 -0
- package/dist/core/plan-history.js.map +1 -0
- package/dist/plugins/agents/native/index.d.ts.map +1 -1
- package/dist/plugins/agents/native/index.js +29 -2
- package/dist/plugins/agents/native/index.js.map +1 -1
- package/dist/web/public/assets/{a2a-Bp_OpTfW.js → a2a-Rq5d7Xk5.js} +2 -2
- package/dist/web/public/assets/{a2a-Bp_OpTfW.js.map → a2a-Rq5d7Xk5.js.map} +1 -1
- package/dist/web/public/assets/{activity-B5erGuWa.js → activity-FCJjBNRM.js} +2 -2
- package/dist/web/public/assets/{activity-B5erGuWa.js.map → activity-FCJjBNRM.js.map} +1 -1
- package/dist/web/public/assets/{admins-D3j7wkvL.js → admins-9fFdLRCv.js} +2 -2
- package/dist/web/public/assets/{admins-D3j7wkvL.js.map → admins-9fFdLRCv.js.map} +1 -1
- package/dist/web/public/assets/{agents-Cb3Vb2-8.js → agents-CiWiDAvY.js} +2 -2
- package/dist/web/public/assets/{agents-Cb3Vb2-8.js.map → agents-CiWiDAvY.js.map} +1 -1
- package/dist/web/public/assets/{approvals-BZULwrl5.js → approvals-4gNB4E5r.js} +2 -2
- package/dist/web/public/assets/{approvals-BZULwrl5.js.map → approvals-4gNB4E5r.js.map} +1 -1
- package/dist/web/public/assets/{arrow-down-OdSfpgG4.js → arrow-down-CL6JRd2c.js} +2 -2
- package/dist/web/public/assets/{arrow-down-OdSfpgG4.js.map → arrow-down-CL6JRd2c.js.map} +1 -1
- package/dist/web/public/assets/{arrow-up-DtrvOH7Z.js → arrow-up-B47-Pj-X.js} +2 -2
- package/dist/web/public/assets/{arrow-up-DtrvOH7Z.js.map → arrow-up-B47-Pj-X.js.map} +1 -1
- package/dist/web/public/assets/{asks-BaVT-eFe.js → asks-DPTxh_Os.js} +2 -2
- package/dist/web/public/assets/{asks-BaVT-eFe.js.map → asks-DPTxh_Os.js.map} +1 -1
- package/dist/web/public/assets/{audit-CSk2rv6z.js → audit-Dw3mL-g7.js} +2 -2
- package/dist/web/public/assets/{audit-CSk2rv6z.js.map → audit-Dw3mL-g7.js.map} +1 -1
- package/dist/web/public/assets/{bell-DifbuUaI.js → bell-ojwGPpS7.js} +2 -2
- package/dist/web/public/assets/{bell-DifbuUaI.js.map → bell-ojwGPpS7.js.map} +1 -1
- package/dist/web/public/assets/{bgjobs-D3wsuZBR.js → bgjobs-DDTjejCS.js} +2 -2
- package/dist/web/public/assets/{bgjobs-D3wsuZBR.js.map → bgjobs-DDTjejCS.js.map} +1 -1
- package/dist/web/public/assets/{brain-BH9ObIfA.js → brain-wzaKeCbt.js} +2 -2
- package/dist/web/public/assets/{brain-BH9ObIfA.js.map → brain-wzaKeCbt.js.map} +1 -1
- package/dist/web/public/assets/{briefcase-CZqK5oy3.js → briefcase-CCAq-ik6.js} +2 -2
- package/dist/web/public/assets/{briefcase-CZqK5oy3.js.map → briefcase-CCAq-ik6.js.map} +1 -1
- package/dist/web/public/assets/{chat-gOU1pe0E.js → chat-CbSvPFpG.js} +2 -2
- package/dist/web/public/assets/{chat-gOU1pe0E.js.map → chat-CbSvPFpG.js.map} +1 -1
- package/dist/web/public/assets/{chevron-left-BtuaBNRZ.js → chevron-left-bpKHh9Bu.js} +2 -2
- package/dist/web/public/assets/{chevron-left-BtuaBNRZ.js.map → chevron-left-bpKHh9Bu.js.map} +1 -1
- package/dist/web/public/assets/{chevron-right-uDVVnuGu.js → chevron-right-CUlLpETY.js} +2 -2
- package/dist/web/public/assets/{chevron-right-uDVVnuGu.js.map → chevron-right-CUlLpETY.js.map} +1 -1
- package/dist/web/public/assets/{circle-check-VgYKnBhy.js → circle-check-D98QQwz0.js} +2 -2
- package/dist/web/public/assets/{circle-check-VgYKnBhy.js.map → circle-check-D98QQwz0.js.map} +1 -1
- package/dist/web/public/assets/{circle-check-big-7idAFtyL.js → circle-check-big-BP-0K1le.js} +2 -2
- package/dist/web/public/assets/{circle-check-big-7idAFtyL.js.map → circle-check-big-BP-0K1le.js.map} +1 -1
- package/dist/web/public/assets/{circle-x-8dhkvHlT.js → circle-x-Et3rwVPd.js} +2 -2
- package/dist/web/public/assets/{circle-x-8dhkvHlT.js.map → circle-x-Et3rwVPd.js.map} +1 -1
- package/dist/web/public/assets/{confirm-dialog-_BtUs6oW.js → confirm-dialog-6dKowsEt.js} +2 -2
- package/dist/web/public/assets/{confirm-dialog-_BtUs6oW.js.map → confirm-dialog-6dKowsEt.js.map} +1 -1
- package/dist/web/public/assets/{copy-Ba1min4z.js → copy-kCWCP9Wu.js} +2 -2
- package/dist/web/public/assets/{copy-Ba1min4z.js.map → copy-kCWCP9Wu.js.map} +1 -1
- package/dist/web/public/assets/{data-table-BVcXWtPw.js → data-table-BbNkr6tW.js} +2 -2
- package/dist/web/public/assets/{data-table-BVcXWtPw.js.map → data-table-BbNkr6tW.js.map} +1 -1
- package/dist/web/public/assets/{dialog-BVYFRXlI.js → dialog-Cv87qelJ.js} +2 -2
- package/dist/web/public/assets/{dialog-BVYFRXlI.js.map → dialog-Cv87qelJ.js.map} +1 -1
- package/dist/web/public/assets/{download-DYc5g_1W.js → download-Cv8u9PxC.js} +2 -2
- package/dist/web/public/assets/{download-DYc5g_1W.js.map → download-Cv8u9PxC.js.map} +1 -1
- package/dist/web/public/assets/{email-6vlGBJXY.js → email-BS3f2y7i.js} +2 -2
- package/dist/web/public/assets/{email-6vlGBJXY.js.map → email-BS3f2y7i.js.map} +1 -1
- package/dist/web/public/assets/{empty-state-WHzNYVaT.js → empty-state-CRnid5FY.js} +2 -2
- package/dist/web/public/assets/{empty-state-WHzNYVaT.js.map → empty-state-CRnid5FY.js.map} +1 -1
- package/dist/web/public/assets/{external-link-CfQZDer8.js → external-link-CzQc-c_P.js} +2 -2
- package/dist/web/public/assets/{external-link-CfQZDer8.js.map → external-link-CzQc-c_P.js.map} +1 -1
- package/dist/web/public/assets/{eye-PzqPB6N6.js → eye-DEBkfJiC.js} +2 -2
- package/dist/web/public/assets/{eye-PzqPB6N6.js.map → eye-DEBkfJiC.js.map} +1 -1
- package/dist/web/public/assets/{facts-NmBeyMpC.js → facts-O3FFqdD1.js} +2 -2
- package/dist/web/public/assets/{facts-NmBeyMpC.js.map → facts-O3FFqdD1.js.map} +1 -1
- package/dist/web/public/assets/{goals-BfMwQtYm.js → goals-pr5ky9rG.js} +2 -2
- package/dist/web/public/assets/{goals-BfMwQtYm.js.map → goals-pr5ky9rG.js.map} +1 -1
- package/dist/web/public/assets/{health-D46iQ6Hj.js → health-D_rfBsgq.js} +2 -2
- package/dist/web/public/assets/{health-D46iQ6Hj.js.map → health-D_rfBsgq.js.map} +1 -1
- package/dist/web/public/assets/{heart-pulse-BHz53Ggd.js → heart-pulse-203oH09r.js} +2 -2
- package/dist/web/public/assets/{heart-pulse-BHz53Ggd.js.map → heart-pulse-203oH09r.js.map} +1 -1
- package/dist/web/public/assets/{heartbeat-DBpc9rKL.js → heartbeat-B5yJoH-X.js} +2 -2
- package/dist/web/public/assets/{heartbeat-DBpc9rKL.js.map → heartbeat-B5yJoH-X.js.map} +1 -1
- package/dist/web/public/assets/{hot-D_-tARKX.js → hot-CXGeh4Oo.js} +2 -2
- package/dist/web/public/assets/{hot-D_-tARKX.js.map → hot-CXGeh4Oo.js.map} +1 -1
- package/dist/web/public/assets/{index-DXI13nSQ.js → index-CuNnG-5O.js} +16 -16
- package/dist/web/public/assets/index-CuNnG-5O.js.map +1 -0
- package/dist/web/public/assets/{installed-BtOgT0Ea.js → installed-CQKXZNEU.js} +2 -2
- package/dist/web/public/assets/{installed-BtOgT0Ea.js.map → installed-CQKXZNEU.js.map} +1 -1
- package/dist/web/public/assets/{jobs-DUoPbupO.js → jobs-DPxN8Ko0.js} +2 -2
- package/dist/web/public/assets/{jobs-DUoPbupO.js.map → jobs-DPxN8Ko0.js.map} +1 -1
- package/dist/web/public/assets/{layout-BpS7td-S.js → layout-4C_GhacH.js} +2 -2
- package/dist/web/public/assets/{layout-BpS7td-S.js.map → layout-4C_GhacH.js.map} +1 -1
- package/dist/web/public/assets/{layout-wuoOyazR.js → layout-CQXWA5Ak.js} +2 -2
- package/dist/web/public/assets/{layout-wuoOyazR.js.map → layout-CQXWA5Ak.js.map} +1 -1
- package/dist/web/public/assets/{layout-C3e8vPrG.js → layout-CxQy-DtV.js} +2 -2
- package/dist/web/public/assets/{layout-C3e8vPrG.js.map → layout-CxQy-DtV.js.map} +1 -1
- package/dist/web/public/assets/{layout-C1EVvCs8.js → layout-VOwsqNph.js} +2 -2
- package/dist/web/public/assets/{layout-C1EVvCs8.js.map → layout-VOwsqNph.js.map} +1 -1
- package/dist/web/public/assets/{layout-DkoRqhBs.js → layout-tAyuz3-F.js} +2 -2
- package/dist/web/public/assets/{layout-DkoRqhBs.js.map → layout-tAyuz3-F.js.map} +1 -1
- package/dist/web/public/assets/{llm-iuvigJxr.js → llm-i15cVS6k.js} +2 -2
- package/dist/web/public/assets/{llm-iuvigJxr.js.map → llm-i15cVS6k.js.map} +1 -1
- package/dist/web/public/assets/{loader-circle-BS5FFFg-.js → loader-circle-CVEo3Hwx.js} +2 -2
- package/dist/web/public/assets/{loader-circle-BS5FFFg-.js.map → loader-circle-CVEo3Hwx.js.map} +1 -1
- package/dist/web/public/assets/{map-pin-CRS6YW4i.js → map-pin-BlmDrPGJ.js} +2 -2
- package/dist/web/public/assets/{map-pin-CRS6YW4i.js.map → map-pin-BlmDrPGJ.js.map} +1 -1
- package/dist/web/public/assets/{mcp-PWEuZOJ_.js → mcp-9MlUdHAS.js} +2 -2
- package/dist/web/public/assets/{mcp-PWEuZOJ_.js.map → mcp-9MlUdHAS.js.map} +1 -1
- package/dist/web/public/assets/{memos-C6i1hbv9.js → memos-MDYe436b.js} +2 -2
- package/dist/web/public/assets/{memos-C6i1hbv9.js.map → memos-MDYe436b.js.map} +1 -1
- package/dist/web/public/assets/{messengers-CZ3eL15d.js → messengers-D0F-cgJ0.js} +2 -2
- package/dist/web/public/assets/{messengers-CZ3eL15d.js.map → messengers-D0F-cgJ0.js.map} +1 -1
- package/dist/web/public/assets/{mobile-_rEEG_kX.js → mobile-Bezz5MnW.js} +2 -2
- package/dist/web/public/assets/{mobile-_rEEG_kX.js.map → mobile-Bezz5MnW.js.map} +1 -1
- package/dist/web/public/assets/{native-agent-B-Id8sOl.js → native-agent-bRS7DRfj.js} +2 -2
- package/dist/web/public/assets/{native-agent-B-Id8sOl.js.map → native-agent-bRS7DRfj.js.map} +1 -1
- package/dist/web/public/assets/{network-MVE8s4TA.js → network-3O1O1jsk.js} +2 -2
- package/dist/web/public/assets/{network-MVE8s4TA.js.map → network-3O1O1jsk.js.map} +1 -1
- package/dist/web/public/assets/{outbox-BRpgAP1d.js → outbox-D2iX9mcs.js} +2 -2
- package/dist/web/public/assets/{outbox-BRpgAP1d.js.map → outbox-D2iX9mcs.js.map} +1 -1
- package/dist/web/public/assets/{pagination-dm3r6K_2.js → pagination-79ms7QFI.js} +2 -2
- package/dist/web/public/assets/{pagination-dm3r6K_2.js.map → pagination-79ms7QFI.js.map} +1 -1
- package/dist/web/public/assets/{persona-Oq3C-gEw.js → persona-D37NzRbz.js} +2 -2
- package/dist/web/public/assets/{persona-Oq3C-gEw.js.map → persona-D37NzRbz.js.map} +1 -1
- package/dist/web/public/assets/{play-COQw7BqX.js → play-BedplEoY.js} +2 -2
- package/dist/web/public/assets/{play-COQw7BqX.js.map → play-BedplEoY.js.map} +1 -1
- package/dist/web/public/assets/{plus-CdOyGoU1.js → plus-4GdEpvTV.js} +2 -2
- package/dist/web/public/assets/{plus-CdOyGoU1.js.map → plus-4GdEpvTV.js.map} +1 -1
- package/dist/web/public/assets/{policy-docXezae.js → policy-Gmd4rmzp.js} +2 -2
- package/dist/web/public/assets/{policy-docXezae.js.map → policy-Gmd4rmzp.js.map} +1 -1
- package/dist/web/public/assets/{qr-code-8putJTrW.js → qr-code-DVgHWDky.js} +2 -2
- package/dist/web/public/assets/{qr-code-8putJTrW.js.map → qr-code-DVgHWDky.js.map} +1 -1
- package/dist/web/public/assets/{refresh-ccw-BPKXoMZa.js → refresh-ccw-DZHRYuFA.js} +2 -2
- package/dist/web/public/assets/{refresh-ccw-BPKXoMZa.js.map → refresh-ccw-DZHRYuFA.js.map} +1 -1
- package/dist/web/public/assets/{reminders-CP2qtNSr.js → reminders-C00V-Fs9.js} +2 -2
- package/dist/web/public/assets/{reminders-CP2qtNSr.js.map → reminders-C00V-Fs9.js.map} +1 -1
- package/dist/web/public/assets/{save-BXCmgeEj.js → save-DQlBAXAl.js} +2 -2
- package/dist/web/public/assets/{save-BXCmgeEj.js.map → save-DQlBAXAl.js.map} +1 -1
- package/dist/web/public/assets/{schedules-DVaY38v1.js → schedules-BtuWAAd7.js} +2 -2
- package/dist/web/public/assets/{schedules-DVaY38v1.js.map → schedules-BtuWAAd7.js.map} +1 -1
- package/dist/web/public/assets/{search-DzexDAbr.js → search-DJXxphzz.js} +2 -2
- package/dist/web/public/assets/{search-DzexDAbr.js.map → search-DJXxphzz.js.map} +1 -1
- package/dist/web/public/assets/{search-B-wDhzjs.js → search-dBGBHpnH.js} +2 -2
- package/dist/web/public/assets/{search-B-wDhzjs.js.map → search-dBGBHpnH.js.map} +1 -1
- package/dist/web/public/assets/{security-FHN-b43T.js → security-CP0MP_Gx.js} +2 -2
- package/dist/web/public/assets/{security-FHN-b43T.js.map → security-CP0MP_Gx.js.map} +1 -1
- package/dist/web/public/assets/{service-DXGUZTCp.js → service-BJFnNe7Y.js} +2 -2
- package/dist/web/public/assets/{service-DXGUZTCp.js.map → service-BJFnNe7Y.js.map} +1 -1
- package/dist/web/public/assets/{shield-alert-BM0-khVy.js → shield-alert-DRh8fC8Z.js} +2 -2
- package/dist/web/public/assets/{shield-alert-BM0-khVy.js.map → shield-alert-DRh8fC8Z.js.map} +1 -1
- package/dist/web/public/assets/{status-badge-DeESb2dc.js → status-badge-CTRG6Pb2.js} +2 -2
- package/dist/web/public/assets/{status-badge-DeESb2dc.js.map → status-badge-CTRG6Pb2.js.map} +1 -1
- package/dist/web/public/assets/{subtasks-DOwo6FnZ.js → subtasks-BiZ2mCPs.js} +2 -2
- package/dist/web/public/assets/{subtasks-DOwo6FnZ.js.map → subtasks-BiZ2mCPs.js.map} +1 -1
- package/dist/web/public/assets/{table-oUi0mGOn.js → table-Dbriy3Jz.js} +2 -2
- package/dist/web/public/assets/{table-oUi0mGOn.js.map → table-Dbriy3Jz.js.map} +1 -1
- package/dist/web/public/assets/{topn-D1LQXoLo.js → topn-BEi7Uvlg.js} +2 -2
- package/dist/web/public/assets/{topn-D1LQXoLo.js.map → topn-BEi7Uvlg.js.map} +1 -1
- package/dist/web/public/assets/{trash-2-CTkSvnZX.js → trash-2-BgltvQpz.js} +2 -2
- package/dist/web/public/assets/{trash-2-CTkSvnZX.js.map → trash-2-BgltvQpz.js.map} +1 -1
- package/dist/web/public/assets/{use-background-tasks-BkUIDOxX.js → use-background-tasks-DHo3Hnu9.js} +2 -2
- package/dist/web/public/assets/{use-background-tasks-BkUIDOxX.js.map → use-background-tasks-DHo3Hnu9.js.map} +1 -1
- package/dist/web/public/assets/{use-llm-admin-BrJMMEz5.js → use-llm-admin-EU1V1WQ5.js} +2 -2
- package/dist/web/public/assets/{use-llm-admin-BrJMMEz5.js.map → use-llm-admin-EU1V1WQ5.js.map} +1 -1
- package/dist/web/public/assets/{use-memory-CbTYEBTc.js → use-memory-D0FD5n0A.js} +2 -2
- package/dist/web/public/assets/{use-memory-CbTYEBTc.js.map → use-memory-D0FD5n0A.js.map} +1 -1
- package/dist/web/public/assets/{use-observability-Bum0mmDO.js → use-observability-CZg_sS2t.js} +2 -2
- package/dist/web/public/assets/{use-observability-Bum0mmDO.js.map → use-observability-CZg_sS2t.js.map} +1 -1
- package/dist/web/public/assets/{use-settings-DJlVLnjo.js → use-settings-D3vI1Dwg.js} +2 -2
- package/dist/web/public/assets/{use-settings-DJlVLnjo.js.map → use-settings-D3vI1Dwg.js.map} +1 -1
- package/dist/web/public/assets/{use-workspace-BJZUfkqw.js → use-workspace-DWm4dRVD.js} +2 -2
- package/dist/web/public/assets/{use-workspace-BJZUfkqw.js.map → use-workspace-DWm4dRVD.js.map} +1 -1
- package/dist/web/public/assets/{useQuery-CEwGD94N.js → useQuery-DIlm0vJ-.js} +2 -2
- package/dist/web/public/assets/{useQuery-CEwGD94N.js.map → useQuery-DIlm0vJ-.js.map} +1 -1
- package/dist/web/public/assets/{vector-BqffHZmp.js → vector-CaijGbpn.js} +2 -2
- package/dist/web/public/assets/{vector-BqffHZmp.js.map → vector-CaijGbpn.js.map} +1 -1
- package/dist/web/public/assets/{viewer-CKTTV-Wt.js → viewer-nU6uZJWq.js} +2 -2
- package/dist/web/public/assets/{viewer-CKTTV-Wt.js.map → viewer-nU6uZJWq.js.map} +1 -1
- package/dist/web/public/assets/{workspace-DQphgYwy.js → workspace-BluXJmIg.js} +2 -2
- package/dist/web/public/assets/{workspace-DQphgYwy.js.map → workspace-BluXJmIg.js.map} +1 -1
- package/dist/web/public/assets/{workspaces-BOA3TuDS.js → workspaces-B2nSpx9t.js} +2 -2
- package/dist/web/public/assets/{workspaces-BOA3TuDS.js.map → workspaces-B2nSpx9t.js.map} +1 -1
- package/dist/web/public/assets/{x-OHUicFfn.js → x-D_eFtxsK.js} +2 -2
- package/dist/web/public/assets/{x-OHUicFfn.js.map → x-D_eFtxsK.js.map} +1 -1
- package/dist/web/public/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/public/assets/index-DXI13nSQ.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,61 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.2.131] - 2026-06-01
|
|
8
|
+
|
|
9
|
+
### Added (Claude-Code 风格的 Plan Mode 退出握手 — P0)
|
|
10
|
+
|
|
11
|
+
之前 native 的 PlanMode 是单向开关:用户 `/plan on` 进,模型只能输出 plan 文本,
|
|
12
|
+
然后用户手动 `/plan off` 才能让模型动手。Claude Code 的对应体验更自然:模型
|
|
13
|
+
"自己说我想好了 → 用户审批 → 一键放行 + 立即继续"。本版补齐这个握手。
|
|
14
|
+
|
|
15
|
+
#### 新工具 `native_exit_plan_mode`
|
|
16
|
+
- 仅当所在线程的 PlanMode 已开启时才出现在工具集合中(其他场景模型看不到)
|
|
17
|
+
- 模型调用时传 `{ plan: '<markdown>' }`(≤ 4000 字)
|
|
18
|
+
- agim 通过 `approvalBus.registerSyntheticPending` 把 plan 推给 IM 端,渲染为审批卡
|
|
19
|
+
- **用户点 Approve** → 立即 `setPlanModeForThread(false)`,写 plan_history 审计行,
|
|
20
|
+
返回 "Plan approved — proceed" 给模型,模型下一轮就有完整写权限
|
|
21
|
+
- **用户点 Deny + 给原因** → 写 plan_history(outcome=rejected, detail=reason),
|
|
22
|
+
返回 "Plan REJECTED. Revise based on: <reason>" 给模型,PlanMode 保持开启
|
|
23
|
+
- 拒绝场景下模型会自动迭代再次调用 `native_exit_plan_mode`
|
|
24
|
+
|
|
25
|
+
#### 新增 plan_history 审计表
|
|
26
|
+
- `~/.agim/plan-history.db`(路径可由 `IMHUB_PLAN_HISTORY_DB` 覆盖)
|
|
27
|
+
- 每次握手写一条不可变记录:thread_key / plan_md / outcome (approved|rejected|edited|expired)
|
|
28
|
+
/ detail / pending_ms / resolved_at
|
|
29
|
+
- 为后续 v1.2.133 的 `/tasks/plans` admin 列表查询打底(本版不做 UI)
|
|
30
|
+
|
|
31
|
+
#### system prompt 改造
|
|
32
|
+
plan mode banner 之前结尾写"操作员清掉 IMHUB_NATIVE_PLAN_MODE 你才能动手"。
|
|
33
|
+
现在改为:
|
|
34
|
+
|
|
35
|
+
> Exit handshake (v1.2.131): when the plan is ready, call
|
|
36
|
+
> `native_exit_plan_mode({ plan: '<markdown of the steps>' })`
|
|
37
|
+
> The user will see an Approve/Reject card. On approve you regain full
|
|
38
|
+
> write access and proceed immediately. On reject you stay in Plan Mode
|
|
39
|
+
> with the user's feedback in the tool result — revise and call again.
|
|
40
|
+
|
|
41
|
+
#### 安全 / 边界
|
|
42
|
+
- 工具自带审批闸口;同时 native 默认 auto-allow 列表加入 `native_exit_plan_mode`,
|
|
43
|
+
避免双弹卡(policy gate 一张 + 内部 plan 卡一张)
|
|
44
|
+
- CLI / 无 notifier 环境调用 → 直接返回错误结果 + 写 rejected 审计行,不挂起
|
|
45
|
+
- 4000 字符上限防止 plan 把 approval-bus 卡撑爆
|
|
46
|
+
- bun 运行下 better-sqlite3 不可用,helper 走 fail-soft 路径:插入返回 null、
|
|
47
|
+
读取返回 [],行为可观察但不会抛错(与已有 plan-mode-state / todo-dispatcher 一致)
|
|
48
|
+
|
|
49
|
+
### 文件
|
|
50
|
+
- 新增 `src/core/plan-history.ts`(~125 行 — sqlite 表 + insert + list)
|
|
51
|
+
- 新增 `src/core/llm/plan-exit-dispatcher.ts`(~210 行 — 工具定义 + 握手逻辑)
|
|
52
|
+
- 新增 `test/unit/plan-history.test.ts`(5 case — 输入校验 + fail-soft)
|
|
53
|
+
- 新增 `test/unit/plan-exit-dispatcher.test.ts`(8 case — 工具定义 + 6 个 gate)
|
|
54
|
+
- 修改 `src/plugins/agents/native/index.ts`:import + 接入 combineDispatchers +
|
|
55
|
+
按 PlanMode 状态条件性 advertise 工具 + 默认 auto-allow + system prompt
|
|
56
|
+
banner 换文案
|
|
57
|
+
|
|
58
|
+
### v1.2.132 预告(P1)
|
|
59
|
+
`/plan` 命令打通 claude-code / codex / cursor / opencode 4 个 CLI adapter,
|
|
60
|
+
让"切 Plan Mode"在不同 agent 下都做对的事。下一版本。
|
|
61
|
+
|
|
7
62
|
## [1.2.130] - 2026-06-01
|
|
8
63
|
|
|
9
64
|
### Fixed (blank-retry 推空 assistant → openai-compat 400)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ToolDef } from './provider-base.js';
|
|
2
|
+
import type { ToolDispatcher } from './tool-dispatcher.js';
|
|
3
|
+
export declare const PLAN_EXIT_TOOL_NAME = "native_exit_plan_mode";
|
|
4
|
+
export declare function isPlanExitToolName(name: string): boolean;
|
|
5
|
+
export interface PlanExitCtx {
|
|
6
|
+
/** Composite thread key (same one PlanMode + Todo use). */
|
|
7
|
+
threadKey: string;
|
|
8
|
+
/** agent-loop run identifier — required by approvalBus.registerRun. */
|
|
9
|
+
runId: string;
|
|
10
|
+
/** RunContext fields. approvalBus needs these to address the IM card
|
|
11
|
+
* back to the right thread. When platform/channelId/threadId are
|
|
12
|
+
* empty, the approval-bus has no way to surface the prompt; we fall
|
|
13
|
+
* back to a synthetic "auto-approve only if running as loopback CLI"
|
|
14
|
+
* hint and degrade to immediate rejection so model isn't stuck. */
|
|
15
|
+
platform?: string;
|
|
16
|
+
channelId?: string;
|
|
17
|
+
threadId?: string;
|
|
18
|
+
userId?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Tool definitions to advertise — emitted ONLY when the thread is in
|
|
21
|
+
* plan mode. The native agent strips it from the roster otherwise so
|
|
22
|
+
* the model can't accidentally call it from a normal turn. */
|
|
23
|
+
export declare function getPlanExitTools(): ToolDef[];
|
|
24
|
+
/** Build a per-thread dispatcher. Each agent-loop run gets one; the
|
|
25
|
+
* closure captures threadKey + runId + IM context. */
|
|
26
|
+
export declare function buildPlanExitDispatcher(ctx: PlanExitCtx): ToolDispatcher;
|
|
27
|
+
//# sourceMappingURL=plan-exit-dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-exit-dispatcher.d.ts","sourceRoot":"","sources":["../../../src/core/llm/plan-exit-dispatcher.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAoB,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAK5E,eAAO,MAAM,mBAAmB,0BAA0B,CAAA;AAE1D,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAA;IACjB,uEAAuE;IACvE,KAAK,EAAE,MAAM,CAAA;IACb;;;;wEAIoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;+DAE+D;AAC/D,wBAAgB,gBAAgB,IAAI,OAAO,EAAE,CAmC5C;AAED;uDACuD;AACvD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAgJxE"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// plan-exit-dispatcher — native_exit_plan_mode tool (v1.2.131 P0b).
|
|
2
|
+
//
|
|
3
|
+
// Mirrors Claude Code's ExitPlanMode handshake:
|
|
4
|
+
// 1. Model is in PlanMode (write tools denied at policy gate).
|
|
5
|
+
// 2. Model thinks through the plan, then calls `native_exit_plan_mode`
|
|
6
|
+
// with the plan markdown.
|
|
7
|
+
// 3. Dispatcher routes the call through the approval-bus so the user
|
|
8
|
+
// sees an IM approval card carrying the plan content.
|
|
9
|
+
// 4. On approve: clear per-thread PlanMode, record history,
|
|
10
|
+
// return "approved — proceed" so the model picks up with full
|
|
11
|
+
// write access on the next iteration.
|
|
12
|
+
// 5. On reject: record history with reason, return rejection text so
|
|
13
|
+
// the model revises the plan (PlanMode stays ON).
|
|
14
|
+
//
|
|
15
|
+
// Why a dedicated dispatcher instead of relying on the existing
|
|
16
|
+
// PolicyApprovalGate ("askUser" path):
|
|
17
|
+
// * That gate decides allow/deny on an ARBITRARY tool call; here the
|
|
18
|
+
// tool call IS the request — its only payload is markdown.
|
|
19
|
+
// * The outcome's side effect (flipping PlanMode off) needs to happen
|
|
20
|
+
// server-side at dispatch time, not via prompt-instruction to the
|
|
21
|
+
// model.
|
|
22
|
+
// * The plan_history audit row is specific to this tool.
|
|
23
|
+
//
|
|
24
|
+
// Tool is only advertised when isPlanModeOn(threadKey) is true; the
|
|
25
|
+
// roster shrinks back when plan mode flips off.
|
|
26
|
+
import { logger as rootLogger } from '../logger.js';
|
|
27
|
+
import { approvalBus } from '../approval-bus.js';
|
|
28
|
+
import { recordPlanHistory } from '../plan-history.js';
|
|
29
|
+
import { effectivePlanModeOn, setPlanModeForThread, } from '../plan-mode-state.js';
|
|
30
|
+
import { randomUUID } from 'node:crypto';
|
|
31
|
+
const log = rootLogger.child({ component: 'plan-exit-dispatcher' });
|
|
32
|
+
export const PLAN_EXIT_TOOL_NAME = 'native_exit_plan_mode';
|
|
33
|
+
export function isPlanExitToolName(name) {
|
|
34
|
+
return name === PLAN_EXIT_TOOL_NAME;
|
|
35
|
+
}
|
|
36
|
+
/** Tool definitions to advertise — emitted ONLY when the thread is in
|
|
37
|
+
* plan mode. The native agent strips it from the roster otherwise so
|
|
38
|
+
* the model can't accidentally call it from a normal turn. */
|
|
39
|
+
export function getPlanExitTools() {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
name: PLAN_EXIT_TOOL_NAME,
|
|
43
|
+
description: 'Call this when your plan is ready for the user to approve. ' +
|
|
44
|
+
'You MUST already be in Plan Mode (write/exec tools are blocked). ' +
|
|
45
|
+
'Pass a single `plan` argument: a concise markdown description of ' +
|
|
46
|
+
'EXACTLY the steps you will take after approval — one bullet per ' +
|
|
47
|
+
'concrete action. The user will see this in an approval card with ' +
|
|
48
|
+
'Approve / Reject buttons. On approve you regain full write access ' +
|
|
49
|
+
'and should immediately proceed; on reject you stay in Plan Mode ' +
|
|
50
|
+
'and revise the plan based on the user feedback.',
|
|
51
|
+
parameters: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
plan: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Markdown plan, ≤ 4000 chars. Use - bullets or numbered steps. ' +
|
|
57
|
+
'Each step should be a single concrete action (read a file, edit X, ' +
|
|
58
|
+
'run command Y). Avoid hand-wavy "review and refactor" — be specific.',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
required: ['plan'],
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
},
|
|
64
|
+
// The approval handshake can take a while — user might be away.
|
|
65
|
+
// 10 minutes is generous enough for any reasonable round-trip
|
|
66
|
+
// and short enough that a forgotten card doesn't pin a session
|
|
67
|
+
// indefinitely (the approval-bus default timeout still applies
|
|
68
|
+
// independently).
|
|
69
|
+
timeoutMs: 10 * 60 * 1000,
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
/** Build a per-thread dispatcher. Each agent-loop run gets one; the
|
|
74
|
+
* closure captures threadKey + runId + IM context. */
|
|
75
|
+
export function buildPlanExitDispatcher(ctx) {
|
|
76
|
+
return async (call, _signal, _ctxDispatcher) => {
|
|
77
|
+
if (call.name !== PLAN_EXIT_TOOL_NAME) {
|
|
78
|
+
return { text: `not handled: ${call.name}`, isError: true, source: 'unknown' };
|
|
79
|
+
}
|
|
80
|
+
// 1. Validate plan mode IS on. If the model calls this from a
|
|
81
|
+
// non-plan turn, refuse — the tool only makes sense as the
|
|
82
|
+
// exit handshake from plan mode.
|
|
83
|
+
if (!ctx.threadKey || !effectivePlanModeOn(ctx.threadKey)) {
|
|
84
|
+
return {
|
|
85
|
+
text: 'native_exit_plan_mode can only be called while Plan Mode is active. ' +
|
|
86
|
+
'This thread is not in Plan Mode — proceed with normal tool calls.',
|
|
87
|
+
isError: true,
|
|
88
|
+
source: 'plan-exit',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// 2. Validate + cap the plan markdown. Bigger plans don't help — IM
|
|
92
|
+
// approval cards are short; force the model to be concise.
|
|
93
|
+
const raw = call.arguments?.plan;
|
|
94
|
+
if (typeof raw !== 'string') {
|
|
95
|
+
return { text: '`plan` must be a string', isError: true, source: 'plan-exit' };
|
|
96
|
+
}
|
|
97
|
+
const planMd = raw.trim();
|
|
98
|
+
if (!planMd) {
|
|
99
|
+
return { text: '`plan` is empty', isError: true, source: 'plan-exit' };
|
|
100
|
+
}
|
|
101
|
+
if (planMd.length > 4000) {
|
|
102
|
+
return {
|
|
103
|
+
text: `\`plan\` too long (${planMd.length} > 4000 chars). Tighten it.`,
|
|
104
|
+
isError: true, source: 'plan-exit',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// 3. Need a real IM thread to surface the approval card. CLI smoke
|
|
108
|
+
// and tests run without a notifier; in that case we degrade
|
|
109
|
+
// deterministically with a "no UI to ask" message and stay in
|
|
110
|
+
// plan mode so the model can recover.
|
|
111
|
+
if (!ctx.platform || !ctx.threadId || !approvalBus.hasNotifier()) {
|
|
112
|
+
recordPlanHistory({
|
|
113
|
+
threadKey: ctx.threadKey,
|
|
114
|
+
planMd,
|
|
115
|
+
outcome: 'rejected',
|
|
116
|
+
detail: 'no IM notifier available — cannot surface approval card',
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
text: 'No interactive UI is available to confirm this plan ' +
|
|
120
|
+
'(headless / CLI mode). Cannot exit Plan Mode automatically. ' +
|
|
121
|
+
'Ask the operator to run `/plan off` from an IM session.',
|
|
122
|
+
isError: true,
|
|
123
|
+
source: 'plan-exit',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// 4. Hand off to approval-bus. Same shape as the buildNativeAskUser
|
|
127
|
+
// flow — we register a synthetic pending and resolve when the
|
|
128
|
+
// user clicks. Capture wall time so the audit row reports how
|
|
129
|
+
// long the user took.
|
|
130
|
+
const reqId = randomUUID();
|
|
131
|
+
const t0 = Date.now();
|
|
132
|
+
const decision = await new Promise((resolve, reject) => {
|
|
133
|
+
void approvalBus.registerSyntheticPending({
|
|
134
|
+
runId: ctx.runId,
|
|
135
|
+
reqId,
|
|
136
|
+
toolName: PLAN_EXIT_TOOL_NAME,
|
|
137
|
+
input: { plan: planMd },
|
|
138
|
+
ctx: {
|
|
139
|
+
platform: ctx.platform,
|
|
140
|
+
channelId: ctx.channelId ?? 'default',
|
|
141
|
+
threadId: ctx.threadId,
|
|
142
|
+
userId: ctx.userId ?? 'unknown',
|
|
143
|
+
callerAgent: 'native',
|
|
144
|
+
},
|
|
145
|
+
// `message` is only present on the deny branch (Decision is a
|
|
146
|
+
// discriminated union); we narrow defensively so the approve
|
|
147
|
+
// branch's lack of `message` doesn't trip the type checker.
|
|
148
|
+
dispatch: (d) => resolve({
|
|
149
|
+
behavior: d.behavior,
|
|
150
|
+
reason: d.behavior === 'deny' ? d.message : undefined,
|
|
151
|
+
}),
|
|
152
|
+
}).catch((err) => reject(err));
|
|
153
|
+
});
|
|
154
|
+
const pendingMs = Date.now() - t0;
|
|
155
|
+
if (decision.behavior === 'allow') {
|
|
156
|
+
// 5a. Flip plan mode OFF for the thread + record audit row.
|
|
157
|
+
// Use setPlanModeForThread(false) — explicit "this thread is
|
|
158
|
+
// out", which beats the env default even if env says ON.
|
|
159
|
+
setPlanModeForThread(ctx.threadKey, false);
|
|
160
|
+
recordPlanHistory({
|
|
161
|
+
threadKey: ctx.threadKey,
|
|
162
|
+
planMd,
|
|
163
|
+
outcome: 'approved',
|
|
164
|
+
detail: decision.reason ?? null,
|
|
165
|
+
pendingMs,
|
|
166
|
+
});
|
|
167
|
+
log.info({
|
|
168
|
+
event: 'plan-exit.approved',
|
|
169
|
+
threadKey: ctx.threadKey,
|
|
170
|
+
planLen: planMd.length,
|
|
171
|
+
pendingMs,
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
text: 'Plan approved by user. You are now OUT of Plan Mode and have full ' +
|
|
175
|
+
'write/exec access for the rest of this conversation. Proceed with ' +
|
|
176
|
+
'the plan exactly as agreed.',
|
|
177
|
+
isError: false,
|
|
178
|
+
source: 'plan-exit',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
// 5b. Reject — keep plan mode on, record audit, give model the
|
|
182
|
+
// rejection reason so it can revise. Surfaces the reason
|
|
183
|
+
// directly in tool result so it lands in the next turn's
|
|
184
|
+
// context window.
|
|
185
|
+
recordPlanHistory({
|
|
186
|
+
threadKey: ctx.threadKey,
|
|
187
|
+
planMd,
|
|
188
|
+
outcome: 'rejected',
|
|
189
|
+
detail: decision.reason ?? null,
|
|
190
|
+
pendingMs,
|
|
191
|
+
});
|
|
192
|
+
log.info({
|
|
193
|
+
event: 'plan-exit.rejected',
|
|
194
|
+
threadKey: ctx.threadKey,
|
|
195
|
+
planLen: planMd.length,
|
|
196
|
+
pendingMs,
|
|
197
|
+
reason: decision.reason,
|
|
198
|
+
});
|
|
199
|
+
const reason = decision.reason?.trim();
|
|
200
|
+
return {
|
|
201
|
+
text: 'Plan REJECTED by user. You remain in Plan Mode. Revise the plan to ' +
|
|
202
|
+
'address this feedback:\n\n' +
|
|
203
|
+
(reason ? `> ${reason}` : '> (no reason provided)') +
|
|
204
|
+
'\n\nWhen ready, call native_exit_plan_mode again with the revised plan.',
|
|
205
|
+
isError: true,
|
|
206
|
+
source: 'plan-exit',
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=plan-exit-dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-exit-dispatcher.js","sourceRoot":"","sources":["../../../src/core/llm/plan-exit-dispatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Outcome strings written into the row. `expired` is reserved for the
|
|
2
|
+
* later approval-timeout path (v1.2.131 P0b only writes the first
|
|
3
|
+
* three). */
|
|
4
|
+
export type PlanOutcome = 'approved' | 'rejected' | 'edited' | 'expired';
|
|
5
|
+
/** What the audit callers pass in. `pendingMs` is optional because
|
|
6
|
+
* callers may not have measured it (e.g. test paths); the column
|
|
7
|
+
* stays NULL in that case. */
|
|
8
|
+
export interface RecordPlanInput {
|
|
9
|
+
threadKey: string;
|
|
10
|
+
planMd: string;
|
|
11
|
+
outcome: PlanOutcome;
|
|
12
|
+
detail?: string | null;
|
|
13
|
+
pendingMs?: number | null;
|
|
14
|
+
}
|
|
15
|
+
/** Insert a new immutable row. Returns the auto-id or null on failure. */
|
|
16
|
+
export declare function recordPlanHistory(input: RecordPlanInput): number | null;
|
|
17
|
+
export interface PlanHistoryRow {
|
|
18
|
+
id: number;
|
|
19
|
+
threadKey: string;
|
|
20
|
+
planMd: string;
|
|
21
|
+
outcome: PlanOutcome;
|
|
22
|
+
detail: string | null;
|
|
23
|
+
resolvedAt: string;
|
|
24
|
+
pendingMs: number | null;
|
|
25
|
+
}
|
|
26
|
+
/** Recent entries — admin UI uses this. `threadKey` filter is optional;
|
|
27
|
+
* omit to see global history. */
|
|
28
|
+
export declare function listPlanHistory(opts?: {
|
|
29
|
+
threadKey?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}): PlanHistoryRow[];
|
|
32
|
+
/** Test-only: close the db handle so a tmp file can be unlinked. */
|
|
33
|
+
export declare function closePlanHistoryDb(): void;
|
|
34
|
+
//# sourceMappingURL=plan-history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-history.d.ts","sourceRoot":"","sources":["../../src/core/plan-history.ts"],"names":[],"mappings":"AAoBA;;cAEc;AACd,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAA;AAiCxE;;+BAE+B;AAC/B,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAsBvE;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,WAAW,CAAA;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED;kCACkC;AAClC,wBAAgB,eAAe,CAAC,IAAI,GAAE;IACpC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACV,GAAG,cAAc,EAAE,CAmBxB;AAED,oEAAoE;AACpE,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// plan-history.ts — audit log for native_exit_plan_mode handshakes
|
|
2
|
+
// (v1.2.131 P0a).
|
|
3
|
+
//
|
|
4
|
+
// Why a separate sqlite table from plan-mode.db:
|
|
5
|
+
// plan-mode.db holds the CURRENT state per thread (PK = thread_key,
|
|
6
|
+
// one row max). plan-history records EACH transition — many rows
|
|
7
|
+
// per thread over time — so operators can see "what did the model
|
|
8
|
+
// propose 3 turns ago, did the user accept, why was it rejected".
|
|
9
|
+
//
|
|
10
|
+
// Schema is intentionally small + write-once. We never UPDATE rows;
|
|
11
|
+
// each entry is immutable for audit integrity. Retention is the
|
|
12
|
+
// operator's problem (vacuum-on-rotation could be added later).
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { logger as rootLogger } from './logger.js';
|
|
15
|
+
import { createSqliteHelper } from './sqlite-helper.js';
|
|
16
|
+
import { AGIM_HOME } from './agim-paths.js';
|
|
17
|
+
const log = rootLogger.child({ component: 'plan-history' });
|
|
18
|
+
const SCHEMA = `
|
|
19
|
+
CREATE TABLE IF NOT EXISTS plan_history (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
thread_key TEXT NOT NULL,
|
|
22
|
+
plan_md TEXT NOT NULL,
|
|
23
|
+
outcome TEXT NOT NULL,
|
|
24
|
+
/** Free-form text — rejection reason, edit diff summary, etc. */
|
|
25
|
+
detail TEXT,
|
|
26
|
+
/** ISO timestamp at the moment of resolution. */
|
|
27
|
+
resolved_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
28
|
+
/** Wall-clock seconds spent waiting for the user. */
|
|
29
|
+
pending_ms INTEGER
|
|
30
|
+
);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_plan_history_thread
|
|
32
|
+
ON plan_history(thread_key, resolved_at DESC);
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_plan_history_resolved
|
|
34
|
+
ON plan_history(resolved_at DESC);
|
|
35
|
+
`;
|
|
36
|
+
const DB_PATH_FROM_ENV = process.env.IMHUB_PLAN_HISTORY_DB;
|
|
37
|
+
const dbPath = DB_PATH_FROM_ENV && DB_PATH_FROM_ENV.length > 0
|
|
38
|
+
? DB_PATH_FROM_ENV
|
|
39
|
+
: join(AGIM_HOME, 'plan-history.db');
|
|
40
|
+
const helper = createSqliteHelper({
|
|
41
|
+
file: dbPath,
|
|
42
|
+
schema: SCHEMA,
|
|
43
|
+
logger: rootLogger,
|
|
44
|
+
component: 'plan-history-db',
|
|
45
|
+
});
|
|
46
|
+
/** Insert a new immutable row. Returns the auto-id or null on failure. */
|
|
47
|
+
export function recordPlanHistory(input) {
|
|
48
|
+
if (!input.threadKey || !input.planMd || !input.outcome)
|
|
49
|
+
return null;
|
|
50
|
+
return helper.safe((d) => {
|
|
51
|
+
const res = d.prepare(`INSERT INTO plan_history (thread_key, plan_md, outcome, detail, pending_ms)
|
|
52
|
+
VALUES (?, ?, ?, ?, ?)`).run(input.threadKey, input.planMd, input.outcome, input.detail ?? null, input.pendingMs ?? null);
|
|
53
|
+
log.info({
|
|
54
|
+
event: 'plan-history.recorded',
|
|
55
|
+
threadKey: input.threadKey,
|
|
56
|
+
outcome: input.outcome,
|
|
57
|
+
planLen: input.planMd.length,
|
|
58
|
+
id: res.lastInsertRowid,
|
|
59
|
+
});
|
|
60
|
+
return Number(res.lastInsertRowid);
|
|
61
|
+
}, null);
|
|
62
|
+
}
|
|
63
|
+
/** Recent entries — admin UI uses this. `threadKey` filter is optional;
|
|
64
|
+
* omit to see global history. */
|
|
65
|
+
export function listPlanHistory(opts = {}) {
|
|
66
|
+
const limit = Math.max(1, Math.min(opts.limit ?? 50, 500));
|
|
67
|
+
return helper.safe((d) => {
|
|
68
|
+
const sql = opts.threadKey
|
|
69
|
+
? `SELECT id, thread_key as threadKey, plan_md as planMd, outcome,
|
|
70
|
+
detail, resolved_at as resolvedAt, pending_ms as pendingMs
|
|
71
|
+
FROM plan_history WHERE thread_key = ?
|
|
72
|
+
ORDER BY resolved_at DESC, id DESC
|
|
73
|
+
LIMIT ?`
|
|
74
|
+
: `SELECT id, thread_key as threadKey, plan_md as planMd, outcome,
|
|
75
|
+
detail, resolved_at as resolvedAt, pending_ms as pendingMs
|
|
76
|
+
FROM plan_history
|
|
77
|
+
ORDER BY resolved_at DESC, id DESC
|
|
78
|
+
LIMIT ?`;
|
|
79
|
+
const rows = opts.threadKey
|
|
80
|
+
? d.prepare(sql).all(opts.threadKey, limit)
|
|
81
|
+
: d.prepare(sql).all(limit);
|
|
82
|
+
return rows;
|
|
83
|
+
}, []);
|
|
84
|
+
}
|
|
85
|
+
/** Test-only: close the db handle so a tmp file can be unlinked. */
|
|
86
|
+
export function closePlanHistoryDb() {
|
|
87
|
+
helper.close();
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=plan-history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-history.js","sourceRoot":"","sources":["../../src/core/plan-history.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/agents/native/index.ts"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,wBAAwB,CAAA;AAG/B,OAAO,EAOL,KAAK,WAAW,EAEjB,MAAM,4BAA4B,CAAA;AAKnC,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,2CAA2C,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/agents/native/index.ts"],"names":[],"mappings":"AAyCA,OAAO,KAAK,EACV,YAAY,EAGb,MAAM,wBAAwB,CAAA;AAG/B,OAAO,EAOL,KAAK,WAAW,EAEjB,MAAM,4BAA4B,CAAA;AAKnC,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,2CAA2C,CAAA;AAyClD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,WAAW,EACrB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CA0KR;AAED;;;;;;;+DAO+D;AAC/D,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmBpD;AAwPD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,wBAAwB,CAgE1E;AAWD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAExD;AA8ED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAkCvF;AAmmBD,eAAO,MAAM,kBAAkB,EAAE,YAAuC,CAAA;AAExE;;cAEc;AACd,wBAAgB,mBAAmB,IAAI,MAAM,CAI5C"}
|
|
@@ -48,6 +48,7 @@ import { buildFsDispatcher, getFsTools, } from '../../../core/llm/fs-dispatcher.
|
|
|
48
48
|
import { buildWebDispatcher, getWebTools, } from '../../../core/llm/web-dispatcher.js';
|
|
49
49
|
import { buildExecDispatcher, getExecTools, } from '../../../core/llm/exec-dispatcher.js';
|
|
50
50
|
import { buildTodoDispatcher, getTodoTools, } from '../../../core/llm/todo-dispatcher.js';
|
|
51
|
+
import { buildPlanExitDispatcher, getPlanExitTools, } from '../../../core/llm/plan-exit-dispatcher.js';
|
|
51
52
|
import { listSkills } from '../../../core/skills/loader.js';
|
|
52
53
|
import { describeRegistry as describeMcpRegistry } from '../../../core/llm/mcp-registry.js';
|
|
53
54
|
import { resolveAgentCwd, defaultAgentCwd } from '../../../core/agent-cwd.js';
|
|
@@ -105,7 +106,7 @@ export function buildSystemPrompt(provider, role, cwd, threadKey) {
|
|
|
105
106
|
// the policy gate gives a bare "tool call denied" message and the
|
|
106
107
|
// model wastes iterations trying alternative write paths.
|
|
107
108
|
if (isPlanModeOn(threadKey)) {
|
|
108
|
-
lines.push(`⚠ Plan mode is ACTIVE
|
|
109
|
+
lines.push(`⚠ Plan mode is ACTIVE`, ` - You MUST produce a read-only plan; native_write_file and native_exec are HARD-BLOCKED.`, ` - Use read tools freely: native_read_file / native_list_dir / native_glob / native_grep / native_web_fetch / native_web_search.`, ` - Exit handshake (v1.2.131): when the plan is ready, call`, ` native_exit_plan_mode({ plan: '<markdown of the steps>' })`, ` The user will see an Approve/Reject card. On approve you regain full write access and proceed immediately. On reject you stay in Plan Mode with the user's feedback in the tool result — revise and call again.`, ` - DO NOT just describe the plan in prose and stop — the user expects the exit handshake. Skip it only if the user explicitly asks for "no exit" / "just brainstorm".`, ``);
|
|
109
110
|
}
|
|
110
111
|
lines.push(`Tools available beyond the four native built-ins (echo / now / sleep / random_uuid):`, ` - agim built-in MCP tools (mcp__imhub__*): read_skill, list_skills, save_memo, search_memos, update_memo, delete_memo, push_message, ask_user, call_agent, long_task, complete_goal`, ` - native filesystem tools: native_read_file, native_write_file, native_list_dir, native_glob, native_grep — constrained to your workspace cwd unless IMHUB_NATIVE_FS_RESTRICT=0`, ` - native web tools: native_web_fetch (r.jina.ai reader by default), native_web_search (duckduckgo → metaso fallback). Private IPs blocked.`, ` - native_exec(command, timeout_ms?, cwd?): run shell commands. Always approval-gated; bwrap sandbox when IMHUB_EXEC_SANDBOX=bwrap.`, ` - External MCP servers configured by the operator:`, externalMcp, ``, `Available skill cards (call mcp__imhub__read_skill('<name>') for the full body):`, skillsBlock, ``, `Guidance:`, ` - Be terse; avoid filler. Prefer tool use over guessing.`, ` - When uncertain, call mcp__imhub__ask_user(question, choices[]) instead of free-form back-and-forth.`, ` - When the user references something they told the bot before, search memos via mcp__imhub__search_memos.`, ``, `Tool selection priority (HARD RULE — v1.2.59):`, ` - For "read this file / list this dir / search this content / fetch this URL", you MUST FIRST try`, ` your own native tools: native_read_file, native_list_dir, native_glob, native_grep,`, ` native_web_fetch (if available). Do NOT delegate these to call_agent.`, ` - call_agent is reserved for tasks that genuinely need a CLI agent's specialised capabilities`, ` (writing/editing source code in a real repo → claude-code; long-running plans → codex; etc).`, ` - You have a per-turn call_agent cap (default 2). Burning it on file reads will leave you`, ` unable to delegate later when you actually need to.`, ``, `Web tool routing (HARD RULE — v1.2.64):`, ` - If the user provided a SPECIFIC URL (http://… or https://…) → native_web_fetch.`, ` - If the user wants to FIND / DISCOVER something by keywords ("查找最新 X" / "search for Y" /`, ` "find docs on Z" / "今天 / 最近的 W") → native_web_search FIRST. Don't guess a URL.`, ` - Common pattern: native_web_search(query) → pick a result → native_web_fetch(that.url).`, ` - NEVER call native_web_fetch with a URL you fabricated from the user's keywords.`, ``, `Short-input rule:`, ` - If the user's message is ONLY a slash command alias for an agent name (e.g. "/native", "/llm",`, ` "/na", "/cc", "/oc") and you are already that agent, respond with ONE short line confirming`, ` your identity (e.g. "我是 native,正在听。"). Do NOT call any tool. The slash router handles`, ` actual agent switching; if it didn't switch, the user is already on this agent.`, ``, `Plan tracking (v1.2.124 — native_todo_write):`, ` - When the user gives you a task with ≥ 3 distinct steps, FIRST call native_todo_write({items}) to`, ` write out your plan, then update statuses as you complete each step.`, ` Status values: pending | in_progress | completed. Keep exactly one item in_progress at a time.`, ` - The tool result is a rendered markdown checklist; the user sees your progress.`, ` - Don't call native_todo_write for trivial one-step tasks — overhead.`, ` - Example sequence:`, ` 1) native_todo_write([{c:"Fetch market data",s:"in_progress"}, {c:"Analyse",s:"pending"}, {c:"Reply",s:"pending"}])`, ` 2) … do fetch via native_web_fetch …`, ` 3) native_todo_write([{c:"Fetch market data",s:"completed"}, {c:"Analyse",s:"in_progress"}, {c:"Reply",s:"pending"}])`, ` 4) … analysis …`, ` 5) write final answer to user`, ``, `Closure rule (v1.2.94 — HARD RULE):`, ` - When you finish a tool chain (read_file / web_fetch / native_exec / search_memos / etc.) you`, ` MUST write a short Chinese summary BEFORE stopping. The tool output by itself is not a`, ` user-facing answer — the user can't see the raw JSON / shell stdout. Always close with at`, ` least one sentence stating the finding / conclusion.`, ` - Do NOT end a turn with empty assistant text when you've just called tools. If you genuinely`, ` have nothing to add, say so explicitly ("已查完,未发现 X").`, ``, `Proactive memory rule (v1.2.96 — borrowed from Hermes Agent's "agent-curated memory"):`, ` - Before ending a substantive turn, scan the conversation for facts that should outlive the`, ` current chat. Persist them yourself via mcp__imhub__save_memo — don't wait for the user to`, ` ask you to remember.`, ` - Worth saving (call save_memo for each):`, ` · personal preferences ("我不喝咖啡" / "我用 vim"),`, ` · holdings or portfolio codes ("我持有 600519"),`, ` · recurring people / places ("我家在朝阳" / "爸爸生日 5月8日"),`, ` · stable identifiers (账号 / 邮箱 / API base / 配置路径),`, ` · explicit "记一下" / "remember this" instructions.`, ` - NOT worth saving: one-off questions, transient debugging context, tool outputs that are`, ` already cached elsewhere (memos point AT data, they're not a cache of data).`, ` - Each save_memo call is cheap. Two short memos beat one long one — small atomic facts`, ` search better. Add a 1-line user-facing acknowledgement so the user knows you remembered`, ` (e.g. "已记下 600519 是你的持仓").`, ``, `Long-task SOP (v1.2.93 — for any work you estimate will run > 10 minutes):`, ` - You CANNOT keep a long synchronous turn alive: the IM bridge times out around 30 min, and`, ` most useful work past the 10-min mark loses intermediate state if it crashes mid-flight.`, ` - Instead, use native_exec to invoke the agim bgjob wrapper, which spawns a detached worker`, ` that survives independent of this conversation:`, ` native_exec("/root/.claude/scripts/bgjob start <slug> -- /usr/bin/python3 /path/to/script.py [args]")`, ` Substitute python3 for the runtime you actually need. The wrapper returns a job_id; relay it`, ` to the user verbatim and tell them how to check back: \`bgjob status <id>\` / \`bgjob tail <id> -f\`.`, ` - When the user follows up asking about the job, native_exec calls like \`bgjob status <id>\` or`, ` \`bgjob tail <id> -n 100\` give you the current state + recent log lines.`, ` - The bwrap sandbox (when configured) is bypassed for this specific wrapper path so the`, ` setsid-detached worker actually survives. Any OTHER native_exec command remains sandboxed.`, ` - DO NOT use \`nohup ... &\` or backgrounded shell pipelines for long work — those die with the`, ` parent shell. bgjob is the only correct path on this platform.`, ``, `Python-RPC bridge (v1.2.97 — when a task means MANY similar tool calls):`, ` - When you would otherwise call mcp__imhub__* dozens of times in this chat turn (saving 30`, ` facts, fetching 50 stocks, scoring 100 candidates), DO NOT do it inline — that wastes the`, ` iteration budget and is likely to trip the stuck-loop detector. Write ONE Python script,`, ` run it in bgjob, and let it loop locally while calling back to agim's tool surface via the`, ` local RPC bridge agim sets up automatically for every native_exec child.`, ` - The Python sidecar lives at \`<npm install dir>/bin/agim_rpc.py\` (typically`, ` /usr/local/lib/node_modules/agim-cli/bin/agim_rpc.py — find it with`, ` \`node -e "console.log(require.resolve('agim-cli'))"\`). Import it and instantiate the client:`, ` from agim_rpc import client`, ` rpc = client() # reads env, validates token, no args needed`, ` memos = rpc.search_memos(query="茅台", k=10)`, ` for m in memos.get("rows", []):`, ` ...`, ` rpc.push_message(text="后台跑完了,结果是 X")`, ` - Available tools through the bridge (whitelist): search_memos, save_memo, read_skill,`, ` list_skills, push_message. Everything else (native_exec, fs writes, call_agent, long_task,`, ` ask_user) is NOT exposed — the worker already has a shell + filesystem.`, ` - The token is automatically injected via env (IMHUB_RPC_SOCKET + IMHUB_RPC_TOKEN), bound`, ` to THIS IM thread, valid for 24 h. The worker can only drive this thread; it cannot`, ` push_message into someone else's chat.`, ` - End the worker with rpc.push_message(text="…done…") so the user sees the result come back`, ` asynchronously. Don't expect the user to poll \`bgjob tail\` themselves.`);
|
|
111
112
|
return lines.join('\n');
|
|
@@ -435,6 +436,13 @@ export function resolvePolicy(threadKey) {
|
|
|
435
436
|
// per-thread; zero side effects beyond the model's own state. Safe
|
|
436
437
|
// to default-allow; never asks the user.
|
|
437
438
|
'native_todo_write',
|
|
439
|
+
// v1.2.131 — Plan-mode exit handshake. The tool's OWN dispatcher
|
|
440
|
+
// raises the user-facing approval card (with the plan markdown
|
|
441
|
+
// payload); auto-allowing here just keeps the policy gate from
|
|
442
|
+
// double-prompting. Without this entry an operator using allow-list
|
|
443
|
+
// mode would see two cards back-to-back ("native_exit_plan_mode
|
|
444
|
+
// ok?" then "approve plan?").
|
|
445
|
+
'native_exit_plan_mode',
|
|
438
446
|
];
|
|
439
447
|
const effectiveAllow = mode === 'allow-list'
|
|
440
448
|
? Array.from(new Set([...autoAllow, ...defaultBuiltins]))
|
|
@@ -776,7 +784,18 @@ class NativeAgentAdapter {
|
|
|
776
784
|
// before imhub so a stray tool name collision (none today) would
|
|
777
785
|
// be caught early. Uses the same composite thread key the PlanMode
|
|
778
786
|
// resolver does so users can find their list in audit.
|
|
779
|
-
buildTodoDispatcher(planThreadKey ?? `native:${sessionId}`),
|
|
787
|
+
buildTodoDispatcher(planThreadKey ?? `native:${sessionId}`),
|
|
788
|
+
// v1.2.131 — ExitPlanMode handshake. Always wired; the dispatcher
|
|
789
|
+
// itself refuses calls when the thread is NOT in plan mode, so
|
|
790
|
+
// the model can't accidentally exit something it didn't enter.
|
|
791
|
+
buildPlanExitDispatcher({
|
|
792
|
+
threadKey: planThreadKey ?? `native:${sessionId}`,
|
|
793
|
+
runId: sessionId,
|
|
794
|
+
platform: opts.platform,
|
|
795
|
+
channelId: opts.channelId,
|
|
796
|
+
threadId: opts.threadId,
|
|
797
|
+
userId: opts.userId,
|
|
798
|
+
}), buildImhubDispatcher(imhubCtx), buildMcpDispatcher());
|
|
780
799
|
// Compose tool list the same way (built-in → fs → web → exec → todo → imhub → external MCP).
|
|
781
800
|
// Provider tool roster wins on duplicate names via "later
|
|
782
801
|
// definition wins" semantics inside the provider, but our naming
|
|
@@ -791,6 +810,14 @@ class NativeAgentAdapter {
|
|
|
791
810
|
...getImhubTools(),
|
|
792
811
|
...getAllMcpTools(),
|
|
793
812
|
];
|
|
813
|
+
// v1.2.131 — when this thread is in Plan Mode, advertise the
|
|
814
|
+
// dedicated exit tool so the model knows the handshake exists. We
|
|
815
|
+
// don't always advertise it: a model in normal mode shouldn't be
|
|
816
|
+
// tempted to call it (the dispatcher would refuse anyway, but
|
|
817
|
+
// keeping the roster honest is cleaner).
|
|
818
|
+
if (planThreadKey && effectivePlanModeOn(planThreadKey)) {
|
|
819
|
+
tools.push(...getPlanExitTools());
|
|
820
|
+
}
|
|
794
821
|
const policy = resolvePolicy(planThreadKey);
|
|
795
822
|
// v1.2.60 — when the policy would silently deny a tool call,
|
|
796
823
|
// escalate to the user via an IM approval card instead. Only
|