@hienlh/ppm 0.9.0-beta.9 → 0.9.1

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.
Files changed (262) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/bun.lock +17 -0
  3. package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
  4. package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
  5. package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
  6. package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
  7. package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
  8. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
  9. package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
  10. package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
  11. package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
  12. package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
  13. package/dist/web/assets/channel-By7bn0Yq.js +1 -0
  14. package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
  15. package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
  16. package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
  17. package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
  18. package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
  19. package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
  20. package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
  21. package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
  22. package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
  23. package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
  24. package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
  25. package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
  26. package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
  27. package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
  28. package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
  29. package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
  30. package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
  31. package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
  32. package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
  33. package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
  34. package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
  35. package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
  36. package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
  37. package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
  38. package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
  39. package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
  40. package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
  41. package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
  42. package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
  43. package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
  44. package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
  45. package/dist/web/assets/clone-LRxlvnMj.js +1 -0
  46. package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
  47. package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
  48. package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
  49. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
  50. package/dist/web/assets/{csv-preview-DUbHtTAS.js → csv-preview-ncSOnJSC.js} +2 -2
  51. package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
  52. package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
  53. package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
  54. package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
  55. package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
  56. package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
  57. package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
  58. package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
  59. package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
  60. package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
  61. package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
  62. package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
  63. package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
  64. package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
  65. package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
  66. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
  67. package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
  68. package/dist/web/assets/index-C8byznLO.js +37 -0
  69. package/dist/web/assets/index-KwC2YrG4.css +2 -0
  70. package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
  71. package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
  72. package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
  73. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
  74. package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
  75. package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
  76. package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
  77. package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
  78. package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
  79. package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
  80. package/dist/web/assets/{markdown-renderer-VIZB1GXE.js → markdown-renderer-DPLdR9xc.js} +5 -5
  81. package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
  82. package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
  83. package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
  84. package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
  85. package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
  86. package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
  87. package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
  88. package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
  89. package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
  90. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
  91. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
  92. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
  93. package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
  94. package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
  95. package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
  96. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
  97. package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
  98. package/dist/web/assets/table-DFevCOMd.js +1 -0
  99. package/dist/web/assets/tag-CXMT0QB6.js +1 -0
  100. package/dist/web/assets/{terminal-tab-XhKfb4ei.js → terminal-tab-BBi0pEji.js} +1 -1
  101. package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
  102. package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
  103. package/dist/web/assets/{use-monaco-theme-0p0-84jJ.js → use-monaco-theme-B5pG2d1w.js} +1 -1
  104. package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
  105. package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
  106. package/dist/web/index.html +10 -9
  107. package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
  108. package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
  109. package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
  110. package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
  111. package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
  112. package/dist/web/sw.js +1 -1
  113. package/docs/code-standards.md +128 -1
  114. package/docs/codebase-summary.md +79 -12
  115. package/docs/extension-development-guide.md +532 -0
  116. package/docs/project-changelog.md +51 -1
  117. package/docs/project-roadmap.md +9 -3
  118. package/docs/streaming-input-guide.md +267 -0
  119. package/docs/system-architecture.md +432 -3
  120. package/package.json +6 -3
  121. package/packages/ext-database/package.json +41 -0
  122. package/packages/ext-database/src/connection-tree.ts +142 -0
  123. package/packages/ext-database/src/extension.ts +346 -0
  124. package/packages/ext-database/src/query-panel.ts +120 -0
  125. package/packages/ext-database/src/table-viewer-panel.ts +410 -0
  126. package/packages/ext-database/tsconfig.json +8 -0
  127. package/packages/vscode-compat/package.json +16 -0
  128. package/packages/vscode-compat/src/commands.ts +39 -0
  129. package/packages/vscode-compat/src/context.ts +65 -0
  130. package/packages/vscode-compat/src/disposable.ts +21 -0
  131. package/packages/vscode-compat/src/env.ts +20 -0
  132. package/packages/vscode-compat/src/event-emitter.ts +28 -0
  133. package/packages/vscode-compat/src/index.ts +93 -0
  134. package/packages/vscode-compat/src/not-supported.ts +15 -0
  135. package/packages/vscode-compat/src/types.ts +167 -0
  136. package/packages/vscode-compat/src/uri.ts +65 -0
  137. package/packages/vscode-compat/src/window.ts +229 -0
  138. package/packages/vscode-compat/src/workspace.ts +76 -0
  139. package/packages/vscode-compat/tsconfig.json +10 -0
  140. package/snapshot-state.md +1526 -0
  141. package/src/cli/commands/autostart.ts +1 -1
  142. package/src/cli/commands/ext-cmd.ts +121 -0
  143. package/src/cli/commands/restart.ts +9 -1
  144. package/src/cli/commands/status.ts +19 -0
  145. package/src/index.ts +5 -3
  146. package/src/providers/claude-agent-sdk.ts +221 -17
  147. package/src/providers/cli-provider-base.ts +6 -0
  148. package/src/server/index.ts +55 -155
  149. package/src/server/routes/chat.ts +81 -11
  150. package/src/server/routes/extensions.ts +81 -0
  151. package/src/server/routes/project-scoped.ts +2 -0
  152. package/src/server/routes/settings.ts +27 -0
  153. package/src/server/routes/workspace.ts +35 -0
  154. package/src/server/ws/chat.ts +9 -3
  155. package/src/server/ws/extensions.ts +175 -0
  156. package/src/services/account-selector.service.ts +14 -5
  157. package/src/services/account.service.ts +7 -7
  158. package/src/services/claude-usage.service.ts +11 -11
  159. package/src/services/cloud-ws.service.ts +228 -0
  160. package/src/services/cloud.service.ts +1 -0
  161. package/src/services/contribution-registry.ts +110 -0
  162. package/src/services/db.service.ts +181 -4
  163. package/src/services/extension-host-worker.ts +160 -0
  164. package/src/services/extension-installer.ts +112 -0
  165. package/src/services/extension-manifest.ts +65 -0
  166. package/src/services/extension-rpc-handlers.ts +235 -0
  167. package/src/services/extension-rpc.ts +105 -0
  168. package/src/services/extension.service.ts +228 -0
  169. package/src/services/mcp-config.service.ts +15 -6
  170. package/src/services/supervisor.ts +271 -25
  171. package/src/types/api.ts +1 -0
  172. package/src/types/chat.ts +4 -0
  173. package/src/types/extension-messages.ts +64 -0
  174. package/src/types/extension.ts +131 -0
  175. package/src/web/app.tsx +69 -48
  176. package/src/web/components/chat/account-rotation-settings.tsx +163 -0
  177. package/src/web/components/chat/chat-history-bar.tsx +106 -10
  178. package/src/web/components/chat/chat-tab.tsx +15 -10
  179. package/src/web/components/chat/chat-welcome.tsx +148 -0
  180. package/src/web/components/chat/message-list.tsx +19 -6
  181. package/src/web/components/chat/session-picker.tsx +80 -32
  182. package/src/web/components/chat/usage-badge.tsx +68 -8
  183. package/src/web/components/extensions/extension-inputbox.tsx +92 -0
  184. package/src/web/components/extensions/extension-quickpick.tsx +194 -0
  185. package/src/web/components/extensions/extension-tree-view.tsx +240 -0
  186. package/src/web/components/extensions/extension-webview.tsx +83 -0
  187. package/src/web/components/layout/command-palette.tsx +22 -2
  188. package/src/web/components/layout/editor-panel.tsx +163 -18
  189. package/src/web/components/layout/mobile-nav.tsx +2 -1
  190. package/src/web/components/layout/sidebar.tsx +21 -3
  191. package/src/web/components/layout/status-bar.tsx +64 -0
  192. package/src/web/components/layout/tab-bar.tsx +2 -0
  193. package/src/web/components/layout/tab-content.tsx +5 -0
  194. package/src/web/components/layout/upgrade-banner.tsx +15 -5
  195. package/src/web/components/settings/change-password-section.tsx +128 -0
  196. package/src/web/components/settings/extension-manager-section.tsx +214 -0
  197. package/src/web/components/settings/settings-tab.tsx +9 -2
  198. package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
  199. package/src/web/hooks/use-chat.ts +28 -0
  200. package/src/web/hooks/use-extension-ws.ts +181 -0
  201. package/src/web/hooks/use-global-keybindings.ts +18 -2
  202. package/src/web/hooks/use-server-reload.ts +9 -0
  203. package/src/web/hooks/use-url-sync.ts +173 -21
  204. package/src/web/stores/connection-store.ts +39 -0
  205. package/src/web/stores/extension-store.ts +204 -0
  206. package/src/web/stores/panel-store.ts +63 -9
  207. package/src/web/stores/panel-utils.ts +145 -3
  208. package/src/web/stores/settings-store.ts +7 -2
  209. package/src/web/stores/tab-store.ts +2 -1
  210. package/test-session-ops.mjs +444 -0
  211. package/test-tokens.mjs +212 -0
  212. package/tsconfig.json +3 -1
  213. package/dist/web/assets/api-settings-CEMxVMCV.js +0 -1
  214. package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
  215. package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
  216. package/dist/web/assets/browser-tab-D1Zua62g.js +0 -1
  217. package/dist/web/assets/channel-C2fMafck.js +0 -1
  218. package/dist/web/assets/chat-tab-BnD27Vp9.js +0 -7
  219. package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
  220. package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
  221. package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
  222. package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
  223. package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
  224. package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
  225. package/dist/web/assets/clone-B2hUek6n.js +0 -1
  226. package/dist/web/assets/code-editor-DGRg8stf.js +0 -2
  227. package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
  228. package/dist/web/assets/database-viewer-DxCXZQcE.js +0 -1
  229. package/dist/web/assets/diff-viewer-C1sDJG35.js +0 -4
  230. package/dist/web/assets/git-graph-BDn-EiGE.js +0 -1
  231. package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
  232. package/dist/web/assets/index-Bun94AK3.js +0 -37
  233. package/dist/web/assets/index-Db8uky1a.css +0 -2
  234. package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
  235. package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
  236. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
  237. package/dist/web/assets/keybindings-store-COmK4Dte.js +0 -1
  238. package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
  239. package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
  240. package/dist/web/assets/postgres-viewer-CvQZ8gkh.js +0 -1
  241. package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
  242. package/dist/web/assets/settings-tab-RCnvZ29H.js +0 -1
  243. package/dist/web/assets/sqlite-viewer-CEEm2W4C.js +0 -1
  244. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
  245. package/dist/web/assets/tab-store-Bjh6bXFP.js +0 -1
  246. package/dist/web/assets/table-CQVQM2SB.js +0 -1
  247. package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
  248. package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
  249. /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
  250. /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  251. /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
  252. /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  253. /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
  254. /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  255. /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
  256. /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
  257. /package/dist/web/assets/{lib-BeaDXEkP.js → lib-BQ34Db2e.js} +0 -0
  258. /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
  259. /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  260. /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
  261. /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
  262. /package/dist/web/assets/{utils-DMiycH3O.js → utils-BNytJOb1.js} +0 -0
@@ -10,7 +10,7 @@ export function registerAutoStartCommands(program: Command): void {
10
10
  .command("enable")
11
11
  .description("Register PPM to start automatically on boot")
12
12
  .option("-p, --port <port>", "Override port")
13
- .option("-s, --share", "Enable Cloudflare tunnel on boot")
13
+ .option("-s, --share", "(deprecated) Tunnel is now always enabled")
14
14
  .option("-c, --config <path>", "Config file path")
15
15
  .option("--profile <name>", "DB profile name")
16
16
  .action(async (options) => {
@@ -0,0 +1,121 @@
1
+ import { Command } from "commander";
2
+
3
+ const C = {
4
+ reset: "\x1b[0m",
5
+ bold: "\x1b[1m",
6
+ green: "\x1b[32m",
7
+ red: "\x1b[31m",
8
+ yellow: "\x1b[33m",
9
+ cyan: "\x1b[36m",
10
+ dim: "\x1b[2m",
11
+ };
12
+
13
+ function printTable(headers: string[], rows: string[][]): void {
14
+ const colWidths = headers.map((h, i) =>
15
+ Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)),
16
+ );
17
+ const sep = colWidths.map((w) => "-".repeat(w + 2)).join("+");
18
+ const headerLine = headers.map((h, i) => ` ${h.padEnd(colWidths[i]!)} `).join("|");
19
+ console.log(`+${sep}+`);
20
+ console.log(`|${C.bold}${headerLine}${C.reset}|`);
21
+ console.log(`+${sep}+`);
22
+ for (const row of rows) {
23
+ const line = row.map((cell, i) => ` ${(cell ?? "").padEnd(colWidths[i]!)} `).join("|");
24
+ console.log(`|${line}|`);
25
+ }
26
+ console.log(`+${sep}+`);
27
+ }
28
+
29
+ export function registerExtCommands(program: Command): void {
30
+ const ext = program.command("ext").description("Manage PPM extensions");
31
+
32
+ ext
33
+ .command("install <name>")
34
+ .description("Install an extension from npm")
35
+ .action(async (name: string) => {
36
+ const { extensionService } = await import("../../services/extension.service.ts");
37
+ try {
38
+ console.log(`${C.dim}Installing ${name}...${C.reset}`);
39
+ const manifest = await extensionService.install(name);
40
+ console.log(`${C.green}✓${C.reset} Installed ${C.bold}${manifest.id}${C.reset}@${manifest.version}`);
41
+ } catch (e) {
42
+ console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
43
+ process.exit(1);
44
+ }
45
+ });
46
+
47
+ ext
48
+ .command("remove <name>")
49
+ .description("Remove an installed extension")
50
+ .action(async (name: string) => {
51
+ const { extensionService } = await import("../../services/extension.service.ts");
52
+ try {
53
+ await extensionService.remove(name);
54
+ console.log(`${C.green}✓${C.reset} Removed ${C.bold}${name}${C.reset}`);
55
+ } catch (e) {
56
+ console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
57
+ process.exit(1);
58
+ }
59
+ });
60
+
61
+ ext
62
+ .command("list")
63
+ .description("List installed extensions")
64
+ .action(async () => {
65
+ const { extensionService } = await import("../../services/extension.service.ts");
66
+ const extensions = extensionService.list();
67
+ if (extensions.length === 0) {
68
+ console.log(`${C.dim}No extensions installed.${C.reset}`);
69
+ return;
70
+ }
71
+ const rows = extensions.map((e) => [
72
+ e.id,
73
+ e.version,
74
+ e.enabled ? `${C.green}enabled${C.reset}` : `${C.dim}disabled${C.reset}`,
75
+ e.activated ? `${C.green}active${C.reset}` : `${C.dim}inactive${C.reset}`,
76
+ ]);
77
+ printTable(["ID", "Version", "Enabled", "Status"], rows);
78
+ });
79
+
80
+ ext
81
+ .command("enable <name>")
82
+ .description("Enable an extension")
83
+ .action(async (name: string) => {
84
+ const { extensionService } = await import("../../services/extension.service.ts");
85
+ try {
86
+ await extensionService.setEnabled(name, true);
87
+ console.log(`${C.green}✓${C.reset} Enabled ${C.bold}${name}${C.reset}`);
88
+ } catch (e) {
89
+ console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
90
+ process.exit(1);
91
+ }
92
+ });
93
+
94
+ ext
95
+ .command("disable <name>")
96
+ .description("Disable an extension")
97
+ .action(async (name: string) => {
98
+ const { extensionService } = await import("../../services/extension.service.ts");
99
+ try {
100
+ await extensionService.setEnabled(name, false);
101
+ console.log(`${C.green}✓${C.reset} Disabled ${C.bold}${name}${C.reset}`);
102
+ } catch (e) {
103
+ console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
104
+ process.exit(1);
105
+ }
106
+ });
107
+
108
+ ext
109
+ .command("dev <path>")
110
+ .description("Symlink a local extension for development")
111
+ .action(async (localPath: string) => {
112
+ const { extensionService } = await import("../../services/extension.service.ts");
113
+ try {
114
+ const manifest = await extensionService.devLink(localPath);
115
+ console.log(`${C.green}✓${C.reset} Dev-linked ${C.bold}${manifest.id}${C.reset} → ${localPath}`);
116
+ } catch (e) {
117
+ console.error(`${C.red}✗${C.reset} ${e instanceof Error ? e.message : String(e)}`);
118
+ process.exit(1);
119
+ }
120
+ });
121
+ }
@@ -9,7 +9,7 @@ const RESTARTING_FLAG = resolve(PPM_DIR, ".restarting");
9
9
  const RESTART_RESULT = resolve(PPM_DIR, ".restart-result");
10
10
 
11
11
  /** Restart only the server process, keeping the tunnel alive */
12
- export async function restartServer(options: { config?: string }) {
12
+ export async function restartServer(options: { config?: string; force?: boolean }) {
13
13
  // Ignore SIGHUP so this process survives when PPM terminal dies
14
14
  process.on("SIGHUP", () => {});
15
15
 
@@ -34,6 +34,14 @@ export async function restartServer(options: { config?: string }) {
34
34
  process.exit(1);
35
35
  }
36
36
 
37
+ // Check if supervisor is paused — require --force to resume
38
+ const state = status.state as string | undefined;
39
+ if (state === "paused" && !options.force) {
40
+ console.log("\n Server is paused (crashed too many times).");
41
+ console.log(" Use 'ppm restart --force' to resume.\n");
42
+ process.exit(1);
43
+ }
44
+
37
45
  const oldServerPid = status.pid as number | undefined;
38
46
  console.log("\n Restarting PPM server via supervisor...");
39
47
  console.log(" If you're using PPM terminal, wait a few seconds for auto-reconnect.\n");
@@ -15,6 +15,10 @@ interface DaemonStatus {
15
15
  tunnelAlive: boolean;
16
16
  supervisorPid: number | null;
17
17
  supervisorAlive: boolean;
18
+ state: string | null;
19
+ pausedAt: string | null;
20
+ pauseReason: string | null;
21
+ lastCrashError: string | null;
18
22
  }
19
23
 
20
24
  function isAlive(pid: number): boolean {
@@ -26,6 +30,7 @@ function getDaemonStatus(): DaemonStatus {
26
30
  running: false, pid: null, port: null, host: null,
27
31
  shareUrl: null, tunnelPid: null, tunnelAlive: false,
28
32
  supervisorPid: null, supervisorAlive: false,
33
+ state: null, pausedAt: null, pauseReason: null, lastCrashError: null,
29
34
  };
30
35
 
31
36
  if (existsSync(STATUS_FILE)) {
@@ -46,6 +51,10 @@ function getDaemonStatus(): DaemonStatus {
46
51
  tunnelAlive,
47
52
  supervisorPid,
48
53
  supervisorAlive,
54
+ state: (data.state as string) ?? null,
55
+ pausedAt: (data.pausedAt as string) ?? null,
56
+ pauseReason: (data.pauseReason as string) ?? null,
57
+ lastCrashError: (data.lastCrashError as string) ?? null,
49
58
  };
50
59
  } catch { return dead; }
51
60
  }
@@ -161,6 +170,16 @@ export async function showStatus(options: { json?: boolean; all?: boolean }) {
161
170
  if (status.supervisorPid) {
162
171
  console.log(` Supervisor: ${status.supervisorAlive ? "running" : "stopped"} (PID: ${status.supervisorPid})`);
163
172
  }
173
+ // Show state info
174
+ const state = status.state ?? (status.running ? "running" : "stopped");
175
+ if (state === "paused") {
176
+ console.log(` State: PAUSED — ${status.pauseReason ?? "unknown reason"}`);
177
+ if (status.pausedAt) console.log(` Paused: ${status.pausedAt}`);
178
+ if (status.lastCrashError) console.log(` Error: ${status.lastCrashError}`);
179
+ console.log(`\n Resume: ppm restart --force`);
180
+ } else if (state === "upgrading") {
181
+ console.log(` State: UPGRADING`);
182
+ }
164
183
  console.log(` Server: ${status.running ? "running" : "stopped"} (PID: ${status.pid})`);
165
184
  if (status.port) console.log(` Local: http://localhost:${status.port}/`);
166
185
  if (status.tunnelPid) {
package/src/index.ts CHANGED
@@ -16,9 +16,7 @@ program
16
16
  .command("start")
17
17
  .description("Start the PPM server (background by default)")
18
18
  .option("-p, --port <port>", "Port to listen on")
19
- .option("-f, --foreground", "Run in foreground (default: background daemon)")
20
- .option("-d, --daemon", "Run as background daemon (default, kept for compat)")
21
- .option("-s, --share", "Share via public URL (Cloudflare tunnel)")
19
+ .option("-s, --share", "(deprecated) Tunnel is now always enabled")
22
20
  .option("-c, --config <path>", "Path to config file (YAML import into DB)")
23
21
  .option("--profile <name>", "DB profile name (e.g. 'dev' → ppm.dev.db)")
24
22
  .action(async (options) => {
@@ -51,6 +49,7 @@ program
51
49
  .command("restart")
52
50
  .description("Restart the server (keeps tunnel alive)")
53
51
  .option("-c, --config <path>", "Path to config file")
52
+ .option("--force", "Force resume from paused state")
54
53
  .action(async (options) => {
55
54
  const { restartServer } = await import("./cli/commands/restart.ts");
56
55
  await restartServer(options);
@@ -136,4 +135,7 @@ registerAutoStartCommands(program);
136
135
  const { registerCloudCommands } = await import("./cli/commands/cloud.ts");
137
136
  registerCloudCommands(program);
138
137
 
138
+ const { registerExtCommands } = await import("./cli/commands/ext-cmd.ts");
139
+ registerExtCommands(program);
140
+
139
141
  program.parse();
@@ -15,13 +15,15 @@ import type {
15
15
  import { configService } from "../services/config.service.ts";
16
16
  import { mcpConfigService } from "../services/mcp-config.service.ts";
17
17
  import { updateFromSdkEvent } from "../services/claude-usage.service.ts";
18
- import { getSessionMapping, setSessionMapping, getSessionTitles } from "../services/db.service.ts";
18
+ import { getSessionMapping, getSessionProjectPath, setSessionMapping, getSessionTitles } from "../services/db.service.ts";
19
19
  import { accountSelector } from "../services/account-selector.service.ts";
20
20
  import { accountService } from "../services/account.service.ts";
21
21
  import { resolve } from "node:path";
22
- import { existsSync } from "node:fs";
22
+ import { existsSync, readdirSync, unlinkSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
 
25
+ const CLAUDE_PROJECTS_DIR = resolve(homedir(), ".claude/projects");
26
+
25
27
  function getSdkSessionId(ppmId: string): string {
26
28
  return getSessionMapping(ppmId) ?? ppmId;
27
29
  }
@@ -213,6 +215,16 @@ export class ClaudeAgentSdkProvider implements AIProvider {
213
215
  return null;
214
216
  }
215
217
 
218
+ /** Extract text content from an SDK assistant message */
219
+ private extractAssistantText(msg: unknown): string {
220
+ const content = (msg as any)?.message?.content;
221
+ if (!Array.isArray(content)) return "";
222
+ return content
223
+ .filter((b: any) => b.type === "text" && typeof b.text === "string")
224
+ .map((b: any) => b.text)
225
+ .join("");
226
+ }
227
+
216
228
  /** Read current provider config from yaml (fresh each call) */
217
229
  private getProviderConfig(): Partial<import("../types/config.ts").AIProviderConfig> {
218
230
  const ai = configService.get("ai");
@@ -232,6 +244,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
232
244
  };
233
245
  this.activeSessions.set(id, meta);
234
246
  this.messageCount.set(id, 0);
247
+ // Pre-persist mapping so project_path survives server restarts
248
+ setSessionMapping(id, id, config.projectName, config.projectPath);
235
249
  return meta;
236
250
  }
237
251
 
@@ -241,6 +255,8 @@ export class ClaudeAgentSdkProvider implements AIProvider {
241
255
 
242
256
  // Check if we have a mapped SDK session ID (from a previous query)
243
257
  const mappedSdkId = getSdkSessionId(sessionId);
258
+ // Restore project_path from DB so resumed sessions can find JSONL
259
+ const dbProjectPath = getSessionProjectPath(sessionId) ?? undefined;
244
260
 
245
261
  try {
246
262
  const sdkSessions = await sdkListSessions({ limit: 100 });
@@ -252,6 +268,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
252
268
  id: sessionId,
253
269
  providerId: this.id,
254
270
  title: found.customTitle ?? found.summary ?? "Resumed Chat",
271
+ projectPath: dbProjectPath,
255
272
  createdAt: new Date(found.lastModified).toISOString(),
256
273
  };
257
274
  this.activeSessions.set(sessionId, meta);
@@ -268,6 +285,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
268
285
  id: sessionId,
269
286
  providerId: this.id,
270
287
  title: "Resumed Chat",
288
+ projectPath: dbProjectPath,
271
289
  createdAt: new Date().toISOString(),
272
290
  };
273
291
  this.activeSessions.set(sessionId, meta);
@@ -307,6 +325,21 @@ export class ClaudeAgentSdkProvider implements AIProvider {
307
325
  this.closeStreamingSession(sessionId);
308
326
  this.activeSessions.delete(sessionId);
309
327
  this.messageCount.delete(sessionId);
328
+ this.pendingApprovals.delete(sessionId);
329
+ this.forkSources.delete(sessionId);
330
+
331
+ // Best-effort: delete JSONL from ~/.claude/projects/
332
+ const sdkId = getSessionMapping(sessionId) ?? sessionId;
333
+ try {
334
+ if (existsSync(CLAUDE_PROJECTS_DIR)) {
335
+ const projectDirs = readdirSync(CLAUDE_PROJECTS_DIR);
336
+ for (const dir of projectDirs) {
337
+ if (dir.includes("..") || dir.includes("/")) continue; // safety
338
+ const jsonlPath = resolve(CLAUDE_PROJECTS_DIR, dir, `${sdkId}.jsonl`);
339
+ if (existsSync(jsonlPath)) { unlinkSync(jsonlPath); break; }
340
+ }
341
+ }
342
+ } catch { /* best-effort */ }
310
343
  }
311
344
 
312
345
  /**
@@ -325,6 +358,29 @@ export class ClaudeAgentSdkProvider implements AIProvider {
325
358
  this.forkSources.set(sessionId, sourceSessionId);
326
359
  }
327
360
 
361
+ /** Fork a session at a specific message using SDK forkSession() */
362
+ async forkAtMessage(
363
+ sessionId: string,
364
+ messageId: string,
365
+ opts?: { title?: string; dir?: string },
366
+ ): Promise<{ sessionId: string }> {
367
+ const sdkId = getSessionMapping(sessionId) ?? sessionId;
368
+ // Dynamic import: Bun's ESM linker fails to resolve forkSession as a static named export
369
+ // in certain test configurations. Lazy import avoids the module linking issue.
370
+ const { forkSession } = await import("@anthropic-ai/claude-agent-sdk");
371
+ const result = await forkSession(sdkId, {
372
+ upToMessageId: messageId,
373
+ title: opts?.title,
374
+ dir: opts?.dir,
375
+ });
376
+ return { sessionId: result.sessionId };
377
+ }
378
+
379
+ /** Mark session as resumed so next sendMessage uses resume path */
380
+ markAsResumed(sessionId: string): void {
381
+ this.messageCount.set(sessionId, 1);
382
+ }
383
+
328
384
  async listModels(): Promise<ModelOption[]> {
329
385
  return [
330
386
  { value: "claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
@@ -594,7 +650,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
594
650
  allowDangerouslySkipPermissions: isBypass,
595
651
  ...(providerConfig.model && { model: providerConfig.model }),
596
652
  ...(providerConfig.effort && { effort: providerConfig.effort }),
597
- maxTurns: providerConfig.max_turns ?? 100,
653
+ maxTurns: providerConfig.max_turns ?? 1000,
598
654
  ...(providerConfig.max_budget_usd && { maxBudgetUsd: providerConfig.max_budget_usd }),
599
655
  ...(providerConfig.thinking_budget_tokens != null && {
600
656
  thinkingBudgetTokens: providerConfig.thinking_budget_tokens,
@@ -632,7 +688,10 @@ export class ClaudeAgentSdkProvider implements AIProvider {
632
688
  // it's a transient subprocess failure — retry once before surfacing the error.
633
689
  // Also handles authentication_failed by refreshing OAuth token and retrying.
634
690
  const MAX_RETRIES = 1;
691
+ const MAX_RATE_LIMIT_RETRIES = 3;
692
+ const RATE_LIMIT_BACKOFF_MS = [15_000, 30_000, 60_000]; // 15s, 30s, 60s
635
693
  let retryCount = 0;
694
+ let rateLimitRetryCount = 0;
636
695
  let authRetried = false;
637
696
 
638
697
  let hadAnyEvents = false;
@@ -680,7 +739,17 @@ export class ClaudeAgentSdkProvider implements AIProvider {
680
739
  if (subtype === "init") {
681
740
  const initMsg = msg as any;
682
741
  if (initMsg.session_id && initMsg.session_id !== sessionId) {
683
- setSessionMapping(sessionId, initMsg.session_id);
742
+ // Only update sdk_id mapping for brand-new sessions (first message).
743
+ // For resumed sessions the SDK may create a new session_id, but the
744
+ // old JSONL (keyed by the original sdk_id) still holds the full
745
+ // conversation history. Overwriting the mapping would orphan it.
746
+ const existingSdkId = getSessionMapping(sessionId);
747
+ const isFirstMessage = existingSdkId === null || existingSdkId === sessionId;
748
+ if (isFirstMessage) {
749
+ setSessionMapping(sessionId, initMsg.session_id, meta.projectName, meta.projectPath);
750
+ } else {
751
+ console.log(`[sdk] session=${sessionId} ignoring new sdk_id=${initMsg.session_id} to preserve existing mapping → ${existingSdkId}`);
752
+ }
684
753
  const oldMeta = this.activeSessions.get(sessionId);
685
754
  if (oldMeta) {
686
755
  this.activeSessions.set(initMsg.session_id, { ...oldMeta, id: initMsg.session_id });
@@ -688,6 +757,24 @@ export class ClaudeAgentSdkProvider implements AIProvider {
688
757
  }
689
758
  }
690
759
 
760
+ // Detect compacting status
761
+ if (subtype === "status") {
762
+ const status = (msg as any).status;
763
+ if (status === "compacting") {
764
+ console.log(`[sdk] session=${sessionId} COMPACTING`);
765
+ yield { type: "system" as const, subtype: "compacting" } as ChatEvent;
766
+ continue;
767
+ }
768
+ }
769
+
770
+ // Detect compact boundary (compact finished, messages replaced in JSONL)
771
+ if (subtype === "compact_boundary") {
772
+ const meta = (msg as any).compact_metadata;
773
+ console.log(`[sdk] session=${sessionId} COMPACT_BOUNDARY trigger=${meta?.trigger} pre_tokens=${meta?.pre_tokens}`);
774
+ yield { type: "system" as const, subtype: "compact_done" } as ChatEvent;
775
+ continue;
776
+ }
777
+
691
778
  // Yield system events so streaming loop can transition phases
692
779
  // (e.g. connecting → thinking when hooks/init arrive)
693
780
  yield { type: "system" as any, subtype } as any;
@@ -787,7 +874,18 @@ export class ClaudeAgentSdkProvider implements AIProvider {
787
874
  // Full assistant message
788
875
  if (msg.type === "assistant") {
789
876
  // SDK assistant messages can carry an error field for auth/billing/rate-limit failures
790
- const assistantError = (msg as any).error as string | undefined;
877
+ let assistantError = (msg as any).error as string | undefined;
878
+
879
+ // SDK sometimes returns auth errors as text content without setting error field.
880
+ // Detect 401 pattern in text: "Failed to authenticate. API Error: 401 ..."
881
+ if (!assistantError) {
882
+ const textContent = this.extractAssistantText(msg);
883
+ if (textContent && /API Error:\s*401\b.*authentication_error/i.test(textContent)) {
884
+ assistantError = "authentication_failed";
885
+ console.warn(`[sdk] session=${sessionId} detected 401 in assistant text content — treating as auth error`);
886
+ }
887
+ }
888
+
791
889
  if (assistantError) {
792
890
  // Dump full SDK message for debugging
793
891
  console.error(`[sdk] session=${sessionId} cwd=${effectiveCwd} assistant error: ${assistantError} (isFirst=${isFirstMessage} retry=${retryCount})`);
@@ -798,10 +896,11 @@ export class ClaudeAgentSdkProvider implements AIProvider {
798
896
  authRetried = true;
799
897
  try {
800
898
  await accountService.refreshAccessToken(account.id, false);
801
- console.log(`[sdk] session=${sessionId} OAuth token refreshed for ${account.id} — retrying`);
802
- // Re-build env with refreshed token
803
899
  const refreshedAccount = accountService.getWithTokens(account.id);
804
900
  if (refreshedAccount) {
901
+ const label = refreshedAccount.label ?? refreshedAccount.email ?? "Unknown";
902
+ console.log(`[sdk] session=${sessionId} OAuth token refreshed for ${account.id} (${label}) — retrying`);
903
+ yield { type: "account_retry" as const, reason: "Token refreshed", accountId: refreshedAccount.id, accountLabel: label };
805
904
  const retryEnv = this.buildQueryEnv(meta.projectPath, refreshedAccount);
806
905
  // Close failed query and old channel, create new channel + query with refreshed token
807
906
  streamCtrl.done();
@@ -824,16 +923,65 @@ export class ClaudeAgentSdkProvider implements AIProvider {
824
923
  }
825
924
  }
826
925
 
926
+ // Auth failed permanently after retry — cooldown account and break loop.
927
+ // SDK doesn't send a result event after auth errors in streaming mode,
928
+ // so the streaming session would stay alive with broken credentials forever.
929
+ // Breaking here lets the finally block tear down the session, so the next
930
+ // user message creates a fresh session with a different account.
931
+ if (assistantError === "authentication_failed" && account && authRetried) {
932
+ accountSelector.onAuthError(account.id);
933
+ console.warn(`[sdk] session=${sessionId} auth permanently failed — tearing down streaming session`);
934
+ yield { type: "error", message: "API authentication failed. Check your account credentials in Settings → Accounts." };
935
+ break;
936
+ }
937
+
938
+ // Rate limit — auto-retry with exponential backoff, switching account if possible
939
+ if ((assistantError === "rate_limit" || assistantError === "server_error") && rateLimitRetryCount < MAX_RATE_LIMIT_RETRIES) {
940
+ const backoff = RATE_LIMIT_BACKOFF_MS[rateLimitRetryCount] ?? 60_000;
941
+ rateLimitRetryCount++;
942
+ if (account) accountSelector.onRateLimit(account.id);
943
+
944
+ // Try to switch to a different account
945
+ const nextAccount = accountSelector.next();
946
+ if (nextAccount && account && nextAccount.id !== account.id) {
947
+ account = nextAccount;
948
+ const label = nextAccount.label ?? nextAccount.email ?? "Unknown";
949
+ console.warn(`[sdk] session=${sessionId} rate limited — switching to account ${nextAccount.id} (${label}), retrying in ${backoff / 1000}s (attempt ${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})`);
950
+ yield { type: "account_retry" as const, reason: `Rate limited — switching account`, accountId: nextAccount.id, accountLabel: label };
951
+ } else {
952
+ console.warn(`[sdk] session=${sessionId} rate limited — retrying in ${backoff / 1000}s (attempt ${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})`);
953
+ }
954
+ yield { type: "error", message: `Rate limited. Auto-retrying in ${backoff / 1000}s... (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})` };
955
+ await new Promise((r) => setTimeout(r, backoff));
956
+ // Close failed query and recreate with (potentially new) account env
957
+ streamCtrl.done();
958
+ q.close();
959
+ const rlRetryEnv = this.buildQueryEnv(meta.projectPath, account);
960
+ const { generator: rlRetryGen, controller: rlRetryCtrl } = createMessageChannel();
961
+ rlRetryCtrl.push(firstMsg);
962
+ const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined, env: rlRetryEnv };
963
+ const rq = query({
964
+ prompt: rlRetryGen,
965
+ options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
966
+ });
967
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller: rlRetryCtrl });
968
+ this.activeQueries.set(sessionId, rq);
969
+ eventSource = rq;
970
+ continue retryLoop;
971
+ }
972
+
827
973
  const errorHints: Record<string, string> = {
828
974
  authentication_failed: "API authentication failed. Check your account credentials in Settings → Accounts.",
829
975
  billing_error: "Billing error on this account. Check your subscription status.",
830
- rate_limit: "Rate limited by the API. Please wait and try again.",
976
+ rate_limit: `Rate limited by the API. Retried ${MAX_RATE_LIMIT_RETRIES} times without success.`,
831
977
  invalid_request: "Invalid request sent to the API.",
832
- server_error: "Anthropic API server error. Try again shortly.",
978
+ server_error: `Anthropic API server error. Retried ${MAX_RATE_LIMIT_RETRIES} times without success.`,
833
979
  unknown: `API error in project "${effectiveCwd}". Debug:\n1. Run: \`cd ${effectiveCwd} && claude -p "hi"\`\n2. Check env: \`echo $ANTHROPIC_API_KEY $ANTHROPIC_BASE_URL\` — stale/invalid keys cause this\n3. Try: \`ANTHROPIC_API_KEY="" ANTHROPIC_BASE_URL="" claude -p "hi"\`\n4. Refresh auth: \`claude login\``,
834
980
  };
835
981
  const hint = errorHints[assistantError] ?? `API error: ${assistantError}`;
836
982
  yield { type: "error", message: hint };
983
+ // Skip emitting the raw 401 error as text content — already shown as error event
984
+ continue;
837
985
  }
838
986
  const content = (msg as any).message?.content;
839
987
  if (Array.isArray(content)) {
@@ -885,15 +1033,65 @@ export class ClaudeAgentSdkProvider implements AIProvider {
885
1033
  const errCode = this.detectResultErrorCode(msg);
886
1034
  if (errCode === 429) {
887
1035
  accountSelector.onRateLimit(account.id);
888
- // Post-stream 429 surface error, continue waiting for next turn
889
- yield { type: "error", message: "Rate limited. This account is now on cooldown. Please retry." };
1036
+ // Auto-retry with backoff for result-level 429, switching account if possible
1037
+ if (rateLimitRetryCount < MAX_RATE_LIMIT_RETRIES) {
1038
+ const backoff = RATE_LIMIT_BACKOFF_MS[rateLimitRetryCount] ?? 60_000;
1039
+ rateLimitRetryCount++;
1040
+
1041
+ // Try to switch to a different account
1042
+ const nextAccount = accountSelector.next();
1043
+ if (nextAccount && nextAccount.id !== account.id) {
1044
+ account = nextAccount;
1045
+ const label = nextAccount.label ?? nextAccount.email ?? "Unknown";
1046
+ console.warn(`[sdk] session=${sessionId} result 429 — switching to account ${nextAccount.id} (${label}), retrying in ${backoff / 1000}s (attempt ${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})`);
1047
+ yield { type: "account_retry" as const, reason: `Rate limited — switching account`, accountId: nextAccount.id, accountLabel: label };
1048
+ } else {
1049
+ console.warn(`[sdk] session=${sessionId} result 429 — retrying in ${backoff / 1000}s (attempt ${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})`);
1050
+ }
1051
+ yield { type: "error", message: `Rate limited. Auto-retrying in ${backoff / 1000}s... (${rateLimitRetryCount}/${MAX_RATE_LIMIT_RETRIES})` };
1052
+ await new Promise((r) => setTimeout(r, backoff));
1053
+ streamCtrl.done();
1054
+ q.close();
1055
+ const rlRetryEnv = this.buildQueryEnv(meta.projectPath, account);
1056
+ const { generator: rlRetryGen, controller: rlRetryCtrl } = createMessageChannel();
1057
+ rlRetryCtrl.push(firstMsg);
1058
+ const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined, env: rlRetryEnv };
1059
+ const rq = query({
1060
+ prompt: rlRetryGen,
1061
+ options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
1062
+ });
1063
+ this.streamingSessions.set(sessionId, { meta, query: rq, controller: rlRetryCtrl });
1064
+ this.activeQueries.set(sessionId, rq);
1065
+ eventSource = rq;
1066
+ continue retryLoop;
1067
+ }
1068
+ yield { type: "error", message: `Rate limited. Retried ${MAX_RATE_LIMIT_RETRIES} times without success.` };
890
1069
  continue;
891
1070
  } else if (errCode === 401) {
892
- // Try refresh once
893
- try {
894
- await accountService.refreshAccessToken(account.id, false);
895
- console.log(`[sdk] 401 on account ${account.id} — token refreshed`);
896
- } catch {
1071
+ // Refresh token and retry with fresh session (same logic as assistant-level auth retry)
1072
+ if (!authRetried) {
1073
+ authRetried = true;
1074
+ try {
1075
+ await accountService.refreshAccessToken(account.id, false);
1076
+ const refreshedAccount = accountService.getWithTokens(account.id);
1077
+ if (refreshedAccount) {
1078
+ const label = refreshedAccount.label ?? refreshedAccount.email ?? "Unknown";
1079
+ console.log(`[sdk] 401 in result on account ${account.id} (${label}) — token refreshed, retrying`);
1080
+ yield { type: "account_retry" as const, reason: "Token refreshed", accountId: refreshedAccount.id, accountLabel: label };
1081
+ const retryEnv = this.buildQueryEnv(meta.projectPath, refreshedAccount);
1082
+ const retryOpts = { ...queryOptions, sessionId: undefined, resume: undefined, env: retryEnv };
1083
+ const rq = query({
1084
+ prompt: message,
1085
+ options: { ...retryOpts, ...(permissionHooks && { hooks: permissionHooks }), canUseTool } as any,
1086
+ });
1087
+ this.activeQueries.set(sessionId, rq);
1088
+ eventSource = rq;
1089
+ continue retryLoop;
1090
+ }
1091
+ } catch {
1092
+ accountSelector.onAuthError(account.id);
1093
+ }
1094
+ } else {
897
1095
  accountSelector.onAuthError(account.id);
898
1096
  }
899
1097
  } else {
@@ -1051,7 +1249,13 @@ export class ClaudeAgentSdkProvider implements AIProvider {
1051
1249
  }
1052
1250
  } finally {
1053
1251
  this.activeQueries.delete(sessionId);
1054
- this.streamingSessions.delete(sessionId);
1252
+ // Properly close streaming session: terminate subprocess + generator
1253
+ const ss = this.streamingSessions.get(sessionId);
1254
+ if (ss) {
1255
+ ss.controller.done();
1256
+ ss.query.close();
1257
+ this.streamingSessions.delete(sessionId);
1258
+ }
1055
1259
  console.log(`[sdk] session=${sessionId} streaming session ended`);
1056
1260
  }
1057
1261
 
@@ -84,7 +84,13 @@ export abstract class CliProvider implements AIProvider {
84
84
  }));
85
85
  }
86
86
 
87
+ markAsResumed(sessionId: string): void {
88
+ this.messageCount.set(sessionId, 1);
89
+ }
90
+
87
91
  async deleteSession(sessionId: string): Promise<void> {
92
+ const proc = this.activeProcesses.get(sessionId);
93
+ if (proc) { proc.kill(); this.activeProcesses.delete(sessionId); }
88
94
  this.sessions.delete(sessionId);
89
95
  this.messageCount.delete(sessionId);
90
96
  }