@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.
- package/CHANGELOG.md +233 -0
- package/bun.lock +17 -0
- package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
- package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
- package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
- package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
- package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
- package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
- package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
- package/dist/web/assets/channel-By7bn0Yq.js +1 -0
- package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
- package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
- package/dist/web/assets/clone-LRxlvnMj.js +1 -0
- package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
- package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
- package/dist/web/assets/{csv-preview-DUbHtTAS.js → csv-preview-ncSOnJSC.js} +2 -2
- package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
- package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
- package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
- package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
- package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
- package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
- package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
- package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
- package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
- package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
- package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
- package/dist/web/assets/index-C8byznLO.js +37 -0
- package/dist/web/assets/index-KwC2YrG4.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
- package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
- package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
- package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
- package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
- package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
- package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
- package/dist/web/assets/{markdown-renderer-VIZB1GXE.js → markdown-renderer-DPLdR9xc.js} +5 -5
- package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
- package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
- package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
- package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
- package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
- package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
- package/dist/web/assets/table-DFevCOMd.js +1 -0
- package/dist/web/assets/tag-CXMT0QB6.js +1 -0
- package/dist/web/assets/{terminal-tab-XhKfb4ei.js → terminal-tab-BBi0pEji.js} +1 -1
- package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
- package/dist/web/assets/{use-monaco-theme-0p0-84jJ.js → use-monaco-theme-B5pG2d1w.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
- package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
- package/dist/web/index.html +10 -9
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/docs/code-standards.md +128 -1
- package/docs/codebase-summary.md +79 -12
- package/docs/extension-development-guide.md +532 -0
- package/docs/project-changelog.md +51 -1
- package/docs/project-roadmap.md +9 -3
- package/docs/streaming-input-guide.md +267 -0
- package/docs/system-architecture.md +432 -3
- package/package.json +6 -3
- package/packages/ext-database/package.json +41 -0
- package/packages/ext-database/src/connection-tree.ts +142 -0
- package/packages/ext-database/src/extension.ts +346 -0
- package/packages/ext-database/src/query-panel.ts +120 -0
- package/packages/ext-database/src/table-viewer-panel.ts +410 -0
- package/packages/ext-database/tsconfig.json +8 -0
- package/packages/vscode-compat/package.json +16 -0
- package/packages/vscode-compat/src/commands.ts +39 -0
- package/packages/vscode-compat/src/context.ts +65 -0
- package/packages/vscode-compat/src/disposable.ts +21 -0
- package/packages/vscode-compat/src/env.ts +20 -0
- package/packages/vscode-compat/src/event-emitter.ts +28 -0
- package/packages/vscode-compat/src/index.ts +93 -0
- package/packages/vscode-compat/src/not-supported.ts +15 -0
- package/packages/vscode-compat/src/types.ts +167 -0
- package/packages/vscode-compat/src/uri.ts +65 -0
- package/packages/vscode-compat/src/window.ts +229 -0
- package/packages/vscode-compat/src/workspace.ts +76 -0
- package/packages/vscode-compat/tsconfig.json +10 -0
- package/snapshot-state.md +1526 -0
- package/src/cli/commands/autostart.ts +1 -1
- package/src/cli/commands/ext-cmd.ts +121 -0
- package/src/cli/commands/restart.ts +9 -1
- package/src/cli/commands/status.ts +19 -0
- package/src/index.ts +5 -3
- package/src/providers/claude-agent-sdk.ts +221 -17
- package/src/providers/cli-provider-base.ts +6 -0
- package/src/server/index.ts +55 -155
- package/src/server/routes/chat.ts +81 -11
- package/src/server/routes/extensions.ts +81 -0
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/settings.ts +27 -0
- package/src/server/routes/workspace.ts +35 -0
- package/src/server/ws/chat.ts +9 -3
- package/src/server/ws/extensions.ts +175 -0
- package/src/services/account-selector.service.ts +14 -5
- package/src/services/account.service.ts +7 -7
- package/src/services/claude-usage.service.ts +11 -11
- package/src/services/cloud-ws.service.ts +228 -0
- package/src/services/cloud.service.ts +1 -0
- package/src/services/contribution-registry.ts +110 -0
- package/src/services/db.service.ts +181 -4
- package/src/services/extension-host-worker.ts +160 -0
- package/src/services/extension-installer.ts +112 -0
- package/src/services/extension-manifest.ts +65 -0
- package/src/services/extension-rpc-handlers.ts +235 -0
- package/src/services/extension-rpc.ts +105 -0
- package/src/services/extension.service.ts +228 -0
- package/src/services/mcp-config.service.ts +15 -6
- package/src/services/supervisor.ts +271 -25
- package/src/types/api.ts +1 -0
- package/src/types/chat.ts +4 -0
- package/src/types/extension-messages.ts +64 -0
- package/src/types/extension.ts +131 -0
- package/src/web/app.tsx +69 -48
- package/src/web/components/chat/account-rotation-settings.tsx +163 -0
- package/src/web/components/chat/chat-history-bar.tsx +106 -10
- package/src/web/components/chat/chat-tab.tsx +15 -10
- package/src/web/components/chat/chat-welcome.tsx +148 -0
- package/src/web/components/chat/message-list.tsx +19 -6
- package/src/web/components/chat/session-picker.tsx +80 -32
- package/src/web/components/chat/usage-badge.tsx +68 -8
- package/src/web/components/extensions/extension-inputbox.tsx +92 -0
- package/src/web/components/extensions/extension-quickpick.tsx +194 -0
- package/src/web/components/extensions/extension-tree-view.tsx +240 -0
- package/src/web/components/extensions/extension-webview.tsx +83 -0
- package/src/web/components/layout/command-palette.tsx +22 -2
- package/src/web/components/layout/editor-panel.tsx +163 -18
- package/src/web/components/layout/mobile-nav.tsx +2 -1
- package/src/web/components/layout/sidebar.tsx +21 -3
- package/src/web/components/layout/status-bar.tsx +64 -0
- package/src/web/components/layout/tab-bar.tsx +2 -0
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/upgrade-banner.tsx +15 -5
- package/src/web/components/settings/change-password-section.tsx +128 -0
- package/src/web/components/settings/extension-manager-section.tsx +214 -0
- package/src/web/components/settings/settings-tab.tsx +9 -2
- package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
- package/src/web/hooks/use-chat.ts +28 -0
- package/src/web/hooks/use-extension-ws.ts +181 -0
- package/src/web/hooks/use-global-keybindings.ts +18 -2
- package/src/web/hooks/use-server-reload.ts +9 -0
- package/src/web/hooks/use-url-sync.ts +173 -21
- package/src/web/stores/connection-store.ts +39 -0
- package/src/web/stores/extension-store.ts +204 -0
- package/src/web/stores/panel-store.ts +63 -9
- package/src/web/stores/panel-utils.ts +145 -3
- package/src/web/stores/settings-store.ts +7 -2
- package/src/web/stores/tab-store.ts +2 -1
- package/test-session-ops.mjs +444 -0
- package/test-tokens.mjs +212 -0
- package/tsconfig.json +3 -1
- package/dist/web/assets/api-settings-CEMxVMCV.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
- package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
- package/dist/web/assets/browser-tab-D1Zua62g.js +0 -1
- package/dist/web/assets/channel-C2fMafck.js +0 -1
- package/dist/web/assets/chat-tab-BnD27Vp9.js +0 -7
- package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
- package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
- package/dist/web/assets/clone-B2hUek6n.js +0 -1
- package/dist/web/assets/code-editor-DGRg8stf.js +0 -2
- package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
- package/dist/web/assets/database-viewer-DxCXZQcE.js +0 -1
- package/dist/web/assets/diff-viewer-C1sDJG35.js +0 -4
- package/dist/web/assets/git-graph-BDn-EiGE.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
- package/dist/web/assets/index-Bun94AK3.js +0 -37
- package/dist/web/assets/index-Db8uky1a.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
- package/dist/web/assets/keybindings-store-COmK4Dte.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
- package/dist/web/assets/postgres-viewer-CvQZ8gkh.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
- package/dist/web/assets/settings-tab-RCnvZ29H.js +0 -1
- package/dist/web/assets/sqlite-viewer-CEEm2W4C.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
- package/dist/web/assets/tab-store-Bjh6bXFP.js +0 -1
- package/dist/web/assets/table-CQVQM2SB.js +0 -1
- package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
- /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
- /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
- /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
- /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
- /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
- /package/dist/web/assets/{lib-BeaDXEkP.js → lib-BQ34Db2e.js} +0 -0
- /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
- /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
- /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
- /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
- /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", "
|
|
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("-
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
//
|
|
889
|
-
|
|
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
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
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
|
}
|