@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
|
@@ -35,8 +35,27 @@ const LOG_FILE = resolve(PPM_DIR, "ppm.log");
|
|
|
35
35
|
let serverChild: Subprocess | null = null;
|
|
36
36
|
let tunnelChild: Subprocess | null = null;
|
|
37
37
|
let tunnelUrl: string | null = null;
|
|
38
|
+
let adoptedTunnelPid: number | null = null; // PID of tunnel kept alive across upgrade
|
|
38
39
|
let shuttingDown = false;
|
|
39
40
|
|
|
41
|
+
type SupervisorState = "running" | "paused" | "upgrading";
|
|
42
|
+
let supervisorState: SupervisorState = "running";
|
|
43
|
+
|
|
44
|
+
let resumeResolve: (() => void) | null = null;
|
|
45
|
+
|
|
46
|
+
function waitForResume(): Promise<void> {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
resumeResolve = resolve;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function triggerResume(): void {
|
|
53
|
+
if (resumeResolve) {
|
|
54
|
+
resumeResolve();
|
|
55
|
+
resumeResolve = null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
let serverRestarts = 0;
|
|
41
60
|
let lastServerCrash = 0;
|
|
42
61
|
let tunnelRestarts = 0;
|
|
@@ -129,8 +148,25 @@ export async function spawnServer(
|
|
|
129
148
|
serverRestarts++;
|
|
130
149
|
|
|
131
150
|
if (serverRestarts > MAX_RESTARTS) {
|
|
132
|
-
log("
|
|
133
|
-
|
|
151
|
+
log("WARN", `Server exceeded ${MAX_RESTARTS} restarts, pausing`);
|
|
152
|
+
notifyStateChange("running", "paused", "max_restarts_exceeded");
|
|
153
|
+
supervisorState = "paused";
|
|
154
|
+
updateStatus({
|
|
155
|
+
state: "paused",
|
|
156
|
+
pid: null,
|
|
157
|
+
pausedAt: new Date().toISOString(),
|
|
158
|
+
pauseReason: "max_restarts",
|
|
159
|
+
lastCrashError: `exit ${exitCode}`,
|
|
160
|
+
});
|
|
161
|
+
// Wait for resume signal — supervisor stays alive
|
|
162
|
+
await waitForResume();
|
|
163
|
+
// Resumed — reset and respawn
|
|
164
|
+
notifyStateChange("paused", "running", "user_resume");
|
|
165
|
+
supervisorState = "running";
|
|
166
|
+
serverRestarts = 0;
|
|
167
|
+
updateStatus({ state: "running", pausedAt: null, pauseReason: null });
|
|
168
|
+
log("INFO", "Resuming server after pause");
|
|
169
|
+
if (!shuttingDown) return spawnServer(serverArgs, logFd);
|
|
134
170
|
return;
|
|
135
171
|
}
|
|
136
172
|
|
|
@@ -189,12 +225,7 @@ async function syncUrlToCloud(url: string) {
|
|
|
189
225
|
} catch {}
|
|
190
226
|
}
|
|
191
227
|
|
|
192
|
-
|
|
193
|
-
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
194
|
-
heartbeatTimer = setInterval(() => {
|
|
195
|
-
if (tunnelUrl) syncUrlToCloud(tunnelUrl);
|
|
196
|
-
}, 5 * 60 * 1000);
|
|
197
|
-
}
|
|
228
|
+
// HTTP heartbeat removed — WS is the sole heartbeat mechanism (Phase 4)
|
|
198
229
|
|
|
199
230
|
export async function spawnTunnel(port: number): Promise<void> {
|
|
200
231
|
let bin: string;
|
|
@@ -230,9 +261,8 @@ export async function spawnTunnel(port: number): Promise<void> {
|
|
|
230
261
|
updateStatus({ shareUrl: tunnelUrl, tunnelPid: tunnelChild.pid });
|
|
231
262
|
log("INFO", `Tunnel ready: ${tunnelUrl} (PID: ${tunnelChild.pid})`);
|
|
232
263
|
|
|
233
|
-
//
|
|
264
|
+
// One-time sync of tunnel URL to cloud (WS handles periodic heartbeat)
|
|
234
265
|
await syncUrlToCloud(tunnelUrl);
|
|
235
|
-
startCloudHeartbeat(tunnelUrl);
|
|
236
266
|
|
|
237
267
|
const exitCode = await tunnelChild.exited;
|
|
238
268
|
tunnelChild = null;
|
|
@@ -282,26 +312,46 @@ function startServerHealthCheck(port: number) {
|
|
|
282
312
|
|
|
283
313
|
function startTunnelProbe(port: number) {
|
|
284
314
|
tunnelProbeTimer = setInterval(async () => {
|
|
285
|
-
if (shuttingDown || !tunnelUrl
|
|
286
|
-
|
|
287
|
-
|
|
315
|
+
if (shuttingDown || !tunnelUrl) { tunnelFailCount = 0; return; }
|
|
316
|
+
if (!tunnelChild && !adoptedTunnelPid) { tunnelFailCount = 0; return; }
|
|
317
|
+
|
|
318
|
+
// Check if adopted tunnel process is still alive
|
|
319
|
+
if (adoptedTunnelPid && !tunnelChild) {
|
|
320
|
+
try { process.kill(adoptedTunnelPid, 0); } catch {
|
|
321
|
+
log("WARN", "Adopted tunnel process died, respawning");
|
|
322
|
+
adoptedTunnelPid = null;
|
|
323
|
+
tunnelUrl = null;
|
|
324
|
+
updateStatus({ shareUrl: null, tunnelPid: null });
|
|
325
|
+
tunnelFailCount = 0;
|
|
326
|
+
spawnTunnel(port);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
288
329
|
}
|
|
330
|
+
|
|
289
331
|
try {
|
|
290
332
|
const res = await fetch(`${tunnelUrl}/api/health`, {
|
|
291
333
|
signal: AbortSignal.timeout(10_000),
|
|
292
334
|
});
|
|
293
335
|
if (res.ok) {
|
|
294
336
|
tunnelFailCount = 0;
|
|
295
|
-
tunnelRestarts = 0;
|
|
337
|
+
tunnelRestarts = 0;
|
|
296
338
|
return;
|
|
297
339
|
}
|
|
298
340
|
} catch {}
|
|
299
341
|
tunnelFailCount++;
|
|
300
|
-
if (tunnelFailCount >= TUNNEL_PROBE_FAIL_THRESHOLD
|
|
342
|
+
if (tunnelFailCount >= TUNNEL_PROBE_FAIL_THRESHOLD) {
|
|
301
343
|
log("WARN", `Tunnel URL dead (${tunnelFailCount} failures), regenerating`);
|
|
302
|
-
|
|
344
|
+
if (tunnelChild) {
|
|
345
|
+
try { tunnelChild.kill(); } catch {}
|
|
346
|
+
// spawnTunnel loop handles respawn via exited promise
|
|
347
|
+
} else if (adoptedTunnelPid) {
|
|
348
|
+
try { process.kill(adoptedTunnelPid, "SIGTERM"); } catch {}
|
|
349
|
+
adoptedTunnelPid = null;
|
|
350
|
+
tunnelUrl = null;
|
|
351
|
+
updateStatus({ shareUrl: null, tunnelPid: null });
|
|
352
|
+
spawnTunnel(port);
|
|
353
|
+
}
|
|
303
354
|
tunnelFailCount = 0;
|
|
304
|
-
// spawnTunnel loop handles respawn via exited promise
|
|
305
355
|
}
|
|
306
356
|
}, TUNNEL_PROBE_INTERVAL_MS);
|
|
307
357
|
}
|
|
@@ -322,6 +372,39 @@ async function checkAvailableVersion() {
|
|
|
322
372
|
}
|
|
323
373
|
}
|
|
324
374
|
|
|
375
|
+
/** Try to adopt an existing tunnel process from status.json (survives upgrade) */
|
|
376
|
+
function adoptTunnel(): boolean {
|
|
377
|
+
try {
|
|
378
|
+
const status = readStatus();
|
|
379
|
+
const pid = status.tunnelPid as number;
|
|
380
|
+
const url = status.shareUrl as string;
|
|
381
|
+
if (!pid || !url) {
|
|
382
|
+
log("DEBUG", `adoptTunnel: missing tunnelPid(${pid}) or shareUrl(${url}) in status`);
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
process.kill(pid, 0); // throws if process is dead
|
|
386
|
+
adoptedTunnelPid = pid;
|
|
387
|
+
tunnelUrl = url;
|
|
388
|
+
log("INFO", `Adopted existing tunnel (PID: ${pid}, URL: ${url})`);
|
|
389
|
+
return true;
|
|
390
|
+
} catch (e) {
|
|
391
|
+
log("WARN", `adoptTunnel: tunnel PID ${(readStatus().tunnelPid)} unreachable: ${e}`);
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Kill stale tunnel PID from status.json (cleanup after failed adoption) */
|
|
397
|
+
function killStaleTunnel() {
|
|
398
|
+
try {
|
|
399
|
+
const status = readStatus();
|
|
400
|
+
const pid = status.tunnelPid as number;
|
|
401
|
+
if (!pid) return;
|
|
402
|
+
try { process.kill(pid, "SIGTERM"); } catch {}
|
|
403
|
+
log("INFO", `Killed stale tunnel (PID: ${pid})`);
|
|
404
|
+
} catch {}
|
|
405
|
+
updateStatus({ tunnelPid: null, shareUrl: null });
|
|
406
|
+
}
|
|
407
|
+
|
|
325
408
|
/** Spawn new supervisor from updated code, wait for it to be healthy, then exit */
|
|
326
409
|
async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
327
410
|
log("INFO", "Starting self-replace for upgrade");
|
|
@@ -330,11 +413,13 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
330
413
|
try {
|
|
331
414
|
// Prevent spawnServer crash-restart loop from respawning killed children
|
|
332
415
|
shuttingDown = true;
|
|
416
|
+
notifyStateChange(supervisorState, "upgrading", "self_replace");
|
|
417
|
+
supervisorState = "upgrading";
|
|
418
|
+
updateStatus({ state: "upgrading" });
|
|
333
419
|
|
|
334
|
-
// Kill server
|
|
335
|
-
log("INFO", "Stopping server
|
|
420
|
+
// Kill server child to free the port; keep tunnel alive for domain continuity
|
|
421
|
+
log("INFO", "Stopping server before spawning new supervisor (tunnel kept alive)");
|
|
336
422
|
if (serverChild) { try { serverChild.kill(); } catch {} serverChild = null; }
|
|
337
|
-
if (tunnelChild) { try { tunnelChild.kill(); } catch {} tunnelChild = null; }
|
|
338
423
|
// Clear health timers so we don't try to respawn killed children
|
|
339
424
|
if (healthTimer) { clearInterval(healthTimer); healthTimer = null; }
|
|
340
425
|
if (tunnelProbeTimer) { clearInterval(tunnelProbeTimer); tunnelProbeTimer = null; }
|
|
@@ -372,20 +457,159 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
372
457
|
log("ERROR", "Self-replace timeout: new supervisor did not start");
|
|
373
458
|
try { child.kill(); } catch {}
|
|
374
459
|
shuttingDown = false;
|
|
460
|
+
notifyStateChange("upgrading", "running", "upgrade_failed");
|
|
461
|
+
supervisorState = "running";
|
|
462
|
+
updateStatus({ state: "running" });
|
|
375
463
|
return { success: false, error: "New supervisor failed to start within 30s" };
|
|
376
464
|
} catch (e) {
|
|
377
465
|
log("ERROR", `Self-replace error: ${e}`);
|
|
378
466
|
shuttingDown = false;
|
|
467
|
+
notifyStateChange("upgrading", "running", "upgrade_failed");
|
|
468
|
+
supervisorState = "running";
|
|
469
|
+
updateStatus({ state: "running" });
|
|
379
470
|
return { success: false, error: (e as Error).message };
|
|
380
471
|
}
|
|
381
472
|
}
|
|
382
473
|
|
|
474
|
+
// ─── Cloud WS integration ─────────────────────────────────────────────
|
|
475
|
+
|
|
476
|
+
/** Notify Cloud of supervisor state change via WS */
|
|
477
|
+
async function notifyStateChange(from: string, to: string, reason: string) {
|
|
478
|
+
try {
|
|
479
|
+
const { send, isConnected } = await import("./cloud-ws.service.ts");
|
|
480
|
+
if (isConnected()) {
|
|
481
|
+
send({
|
|
482
|
+
type: "state_change",
|
|
483
|
+
from,
|
|
484
|
+
to,
|
|
485
|
+
reason,
|
|
486
|
+
timestamp: new Date().toISOString(),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
} catch {}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** Connect supervisor to Cloud via WebSocket (if device is linked) */
|
|
493
|
+
async function connectCloud(opts: { port: number }, serverArgs: string[], logFd: number) {
|
|
494
|
+
try {
|
|
495
|
+
const { getCloudDevice } = await import("./cloud.service.ts");
|
|
496
|
+
const device = getCloudDevice();
|
|
497
|
+
if (!device) return; // not linked to cloud
|
|
498
|
+
|
|
499
|
+
const { connect, onCommand } = await import("./cloud-ws.service.ts");
|
|
500
|
+
const { VERSION } = await import("../version.ts");
|
|
501
|
+
const startTime = Date.now();
|
|
502
|
+
|
|
503
|
+
connect({
|
|
504
|
+
cloudUrl: device.cloud_url,
|
|
505
|
+
deviceId: device.device_id,
|
|
506
|
+
secretKey: device.secret_key,
|
|
507
|
+
heartbeatFn: () => {
|
|
508
|
+
const status = readStatus();
|
|
509
|
+
// Re-read device file each heartbeat to pick up name changes
|
|
510
|
+
const currentDevice = getCloudDevice();
|
|
511
|
+
return {
|
|
512
|
+
type: "heartbeat" as const,
|
|
513
|
+
tunnelUrl,
|
|
514
|
+
state: supervisorState,
|
|
515
|
+
// Use server-reported version (source of truth) with supervisor fallback
|
|
516
|
+
appVersion: (status.serverVersion as string) || VERSION,
|
|
517
|
+
availableVersion: (status.availableVersion as string) || null,
|
|
518
|
+
serverPid: serverChild?.pid ?? null,
|
|
519
|
+
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
520
|
+
deviceName: currentDevice?.name ?? device.name,
|
|
521
|
+
timestamp: new Date().toISOString(),
|
|
522
|
+
};
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// Handle commands from Cloud
|
|
527
|
+
onCommand(async (cmd) => {
|
|
528
|
+
const { send } = await import("./cloud-ws.service.ts");
|
|
529
|
+
const sendResult = (success: boolean, error?: string, data?: Record<string, unknown>) => {
|
|
530
|
+
send({
|
|
531
|
+
type: "command_result",
|
|
532
|
+
id: cmd.id,
|
|
533
|
+
success,
|
|
534
|
+
error,
|
|
535
|
+
data,
|
|
536
|
+
timestamp: new Date().toISOString(),
|
|
537
|
+
});
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
log("INFO", `Cloud command received: ${cmd.action}`);
|
|
541
|
+
|
|
542
|
+
// Send immediate ack so Cloud can update UI before processing
|
|
543
|
+
send({
|
|
544
|
+
type: "command_ack",
|
|
545
|
+
id: cmd.id,
|
|
546
|
+
timestamp: new Date().toISOString(),
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
switch (cmd.action) {
|
|
550
|
+
case "restart":
|
|
551
|
+
if (serverChild) {
|
|
552
|
+
serverRestartRequested = true;
|
|
553
|
+
try { serverChild.kill(); } catch {}
|
|
554
|
+
sendResult(true);
|
|
555
|
+
} else if (supervisorState === "paused") {
|
|
556
|
+
triggerResume();
|
|
557
|
+
sendResult(true);
|
|
558
|
+
} else {
|
|
559
|
+
sendResult(false, "No server child to restart");
|
|
560
|
+
}
|
|
561
|
+
break;
|
|
562
|
+
|
|
563
|
+
case "resume":
|
|
564
|
+
if (supervisorState === "paused") {
|
|
565
|
+
triggerResume();
|
|
566
|
+
sendResult(true);
|
|
567
|
+
} else {
|
|
568
|
+
sendResult(false, "Not in paused state");
|
|
569
|
+
}
|
|
570
|
+
break;
|
|
571
|
+
|
|
572
|
+
case "stop":
|
|
573
|
+
sendResult(true);
|
|
574
|
+
// Delay exit to allow WS buffer to flush
|
|
575
|
+
setTimeout(() => {
|
|
576
|
+
shutdown();
|
|
577
|
+
process.exit(0);
|
|
578
|
+
}, 500);
|
|
579
|
+
break;
|
|
580
|
+
|
|
581
|
+
case "status":
|
|
582
|
+
sendResult(true, undefined, {
|
|
583
|
+
state: supervisorState,
|
|
584
|
+
serverPid: serverChild?.pid ?? null,
|
|
585
|
+
tunnelUrl,
|
|
586
|
+
serverRestarts,
|
|
587
|
+
});
|
|
588
|
+
break;
|
|
589
|
+
|
|
590
|
+
default:
|
|
591
|
+
sendResult(false, `Unknown action: ${cmd.action}`);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
} catch (e) {
|
|
595
|
+
log("WARN", `Cloud WS setup failed: ${e}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
383
599
|
// ─── Shutdown ──────────────────────────────────────────────────────────
|
|
384
600
|
export function shutdown() {
|
|
385
601
|
if (shuttingDown) return;
|
|
386
602
|
shuttingDown = true;
|
|
387
603
|
log("INFO", "Supervisor shutting down");
|
|
388
604
|
|
|
605
|
+
// Unblock if paused
|
|
606
|
+
triggerResume();
|
|
607
|
+
|
|
608
|
+
// Disconnect Cloud WS
|
|
609
|
+
import("./cloud-ws.service.ts")
|
|
610
|
+
.then(({ disconnect }) => disconnect())
|
|
611
|
+
.catch(() => {});
|
|
612
|
+
|
|
389
613
|
if (healthTimer) clearInterval(healthTimer);
|
|
390
614
|
if (tunnelProbeTimer) clearInterval(tunnelProbeTimer);
|
|
391
615
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
@@ -394,6 +618,7 @@ export function shutdown() {
|
|
|
394
618
|
|
|
395
619
|
if (serverChild) { try { serverChild.kill(); } catch {} }
|
|
396
620
|
if (tunnelChild) { try { tunnelChild.kill(); } catch {} }
|
|
621
|
+
if (adoptedTunnelPid) { try { process.kill(adoptedTunnelPid, "SIGTERM"); } catch {} }
|
|
397
622
|
}
|
|
398
623
|
|
|
399
624
|
// ─── Main entry ────────────────────────────────────────────────────────
|
|
@@ -414,7 +639,10 @@ export async function runSupervisor(opts: {
|
|
|
414
639
|
|
|
415
640
|
// Write supervisor PID + clear stale availableVersion from previous run
|
|
416
641
|
writeFileSync(PID_FILE, String(process.pid));
|
|
417
|
-
updateStatus({
|
|
642
|
+
updateStatus({
|
|
643
|
+
supervisorPid: process.pid, port: opts.port, host: opts.host, availableVersion: null,
|
|
644
|
+
state: "running", pausedAt: null, pauseReason: null, lastCrashError: null,
|
|
645
|
+
});
|
|
418
646
|
|
|
419
647
|
// Build __serve__ args
|
|
420
648
|
const serverArgs = [
|
|
@@ -428,8 +656,13 @@ export async function runSupervisor(opts: {
|
|
|
428
656
|
process.on("SIGTERM", () => { shutdown(); process.exit(0); });
|
|
429
657
|
process.on("SIGINT", () => { shutdown(); process.exit(0); });
|
|
430
658
|
|
|
431
|
-
// SIGUSR2 = graceful server restart (tunnel stays alive)
|
|
659
|
+
// SIGUSR2 = graceful server restart (tunnel stays alive) or resume from paused
|
|
432
660
|
process.on("SIGUSR2", () => {
|
|
661
|
+
if (supervisorState === "paused") {
|
|
662
|
+
log("INFO", "SIGUSR2 received while paused, resuming server");
|
|
663
|
+
triggerResume();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
433
666
|
log("INFO", "SIGUSR2 received, restarting server only");
|
|
434
667
|
if (serverChild) {
|
|
435
668
|
serverRestartRequested = true; // flag so spawnServer skips backoff
|
|
@@ -443,9 +676,9 @@ export async function runSupervisor(opts: {
|
|
|
443
676
|
const result = await selfReplace();
|
|
444
677
|
if (!result.success) {
|
|
445
678
|
log("ERROR", `Self-replace failed: ${result.error}, restarting children`);
|
|
446
|
-
// Respawn server (and tunnel if configured) since selfReplace killed them
|
|
447
679
|
spawnServer(serverArgs, logFd);
|
|
448
|
-
|
|
680
|
+
// Tunnel was kept alive during selfReplace; only respawn if dead
|
|
681
|
+
if (opts.share && !tunnelChild && !tunnelUrl) spawnTunnel(opts.port);
|
|
449
682
|
}
|
|
450
683
|
});
|
|
451
684
|
|
|
@@ -458,16 +691,29 @@ export async function runSupervisor(opts: {
|
|
|
458
691
|
upgradeCheckTimer = setInterval(checkAvailableVersion, UPGRADE_CHECK_INTERVAL_MS);
|
|
459
692
|
}, UPGRADE_SKIP_INITIAL_MS);
|
|
460
693
|
|
|
694
|
+
// Connect to Cloud via WebSocket (if device is linked)
|
|
695
|
+
connectCloud(opts, serverArgs, logFd);
|
|
696
|
+
|
|
461
697
|
// Spawn server + tunnel in parallel
|
|
462
698
|
const promises: Promise<void>[] = [spawnServer(serverArgs, logFd)];
|
|
463
699
|
|
|
464
700
|
if (opts.share) {
|
|
465
701
|
startTunnelProbe(opts.port);
|
|
466
|
-
|
|
702
|
+
// Try adopting tunnel kept alive from previous upgrade; spawn new if dead
|
|
703
|
+
if (!adoptTunnel()) {
|
|
704
|
+
killStaleTunnel(); // kill orphaned tunnel before spawning new one
|
|
705
|
+
promises.push(spawnTunnel(opts.port));
|
|
706
|
+
}
|
|
467
707
|
}
|
|
468
708
|
|
|
469
709
|
await Promise.all(promises);
|
|
470
710
|
|
|
711
|
+
// If upgrading, selfReplace handles process.exit — wait for it
|
|
712
|
+
if (supervisorState === "upgrading") {
|
|
713
|
+
log("INFO", "Server loop exited during upgrade, waiting for selfReplace to finish");
|
|
714
|
+
await new Promise(() => {}); // selfReplace will call process.exit()
|
|
715
|
+
}
|
|
716
|
+
|
|
471
717
|
// If we get here, both loops exited (shutdown or max restarts)
|
|
472
718
|
log("INFO", "Supervisor exiting");
|
|
473
719
|
process.exit(shuttingDown ? 0 : 1);
|
package/src/types/api.ts
CHANGED
|
@@ -44,4 +44,5 @@ export type ChatWsServerMessage =
|
|
|
44
44
|
| { type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: { requestId: string; tool: string; input: unknown } | null; sessionTitle: string | null }
|
|
45
45
|
| { type: "turn_events"; events: unknown[] }
|
|
46
46
|
| { type: "title_updated"; title: string }
|
|
47
|
+
| { type: "compact_status"; status: "compacting" | "done" }
|
|
47
48
|
| { type: "ping" };
|
package/src/types/chat.ts
CHANGED
|
@@ -29,6 +29,8 @@ export interface AIProvider {
|
|
|
29
29
|
listSessionsByDir?(dir: string): Promise<SessionInfo[]>;
|
|
30
30
|
ensureProjectPath?(sessionId: string, path: string): void;
|
|
31
31
|
setForkSource?(sessionId: string, sourceSessionId: string): void;
|
|
32
|
+
forkAtMessage?(sessionId: string, messageId: string, opts?: { title?: string; dir?: string }): Promise<{ sessionId: string }>;
|
|
33
|
+
markAsResumed?(sessionId: string): void;
|
|
32
34
|
isAvailable?(): Promise<boolean>;
|
|
33
35
|
listModels?(): Promise<ModelOption[]>;
|
|
34
36
|
}
|
|
@@ -61,6 +63,7 @@ export interface SessionInfo {
|
|
|
61
63
|
projectName?: string;
|
|
62
64
|
createdAt: string;
|
|
63
65
|
updatedAt?: string;
|
|
66
|
+
pinned?: boolean;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
export interface LimitBucket {
|
|
@@ -111,6 +114,7 @@ export type ChatEvent =
|
|
|
111
114
|
| { type: "done"; sessionId: string; resultSubtype?: ResultSubtype; numTurns?: number; contextWindowPct?: number }
|
|
112
115
|
| { type: "session_migrated"; oldSessionId: string; newSessionId: string }
|
|
113
116
|
| { type: "account_info"; accountId: string; accountLabel: string }
|
|
117
|
+
| { type: "account_retry"; reason: string; accountId?: string; accountLabel?: string }
|
|
114
118
|
| { type: "system"; subtype: string };
|
|
115
119
|
|
|
116
120
|
export type ToolApprovalHandler = (
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared message types for the Extension WebSocket bridge.
|
|
3
|
+
* Server ↔ Client communication for extension UI updates.
|
|
4
|
+
*/
|
|
5
|
+
import type { ExtensionContributes } from "./extension.ts";
|
|
6
|
+
|
|
7
|
+
// --- UI types (shared with extension-store) ---
|
|
8
|
+
|
|
9
|
+
export interface StatusBarItemMsg {
|
|
10
|
+
id: string;
|
|
11
|
+
text: string;
|
|
12
|
+
tooltip?: string;
|
|
13
|
+
command?: string;
|
|
14
|
+
alignment: "left" | "right";
|
|
15
|
+
priority: number;
|
|
16
|
+
extensionId?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TreeItemMsg {
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
tooltip?: string;
|
|
24
|
+
icon?: string;
|
|
25
|
+
collapsibleState: "none" | "collapsed" | "expanded";
|
|
26
|
+
command?: string;
|
|
27
|
+
children?: TreeItemMsg[];
|
|
28
|
+
contextValue?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface QuickPickItemMsg {
|
|
32
|
+
label: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
detail?: string;
|
|
35
|
+
picked?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --- Server → Client messages ---
|
|
39
|
+
|
|
40
|
+
export type ExtServerMsg =
|
|
41
|
+
| { type: "tree:update"; viewId: string; items: TreeItemMsg[]; parentId?: string }
|
|
42
|
+
| { type: "tree:refresh"; viewId: string }
|
|
43
|
+
| { type: "statusbar:update"; item: StatusBarItemMsg }
|
|
44
|
+
| { type: "statusbar:remove"; itemId: string }
|
|
45
|
+
| { type: "notification"; id: string; level: "info" | "warn" | "error"; message: string; actions?: string[] }
|
|
46
|
+
| { type: "quickpick:show"; requestId: string; items: QuickPickItemMsg[]; options?: { placeholder?: string; canPickMany?: boolean } }
|
|
47
|
+
| { type: "inputbox:show"; requestId: string; options: { prompt?: string; value?: string; placeholder?: string; password?: boolean } }
|
|
48
|
+
| { type: "webview:create"; panelId: string; extensionId: string; viewType: string; title: string }
|
|
49
|
+
| { type: "webview:html"; panelId: string; html: string }
|
|
50
|
+
| { type: "webview:dispose"; panelId: string }
|
|
51
|
+
| { type: "webview:postMessage"; panelId: string; message: unknown }
|
|
52
|
+
| { type: "contributions:update"; contributions: ExtensionContributes };
|
|
53
|
+
|
|
54
|
+
// --- Client → Server messages ---
|
|
55
|
+
|
|
56
|
+
export type ExtClientMsg =
|
|
57
|
+
| { type: "ready" }
|
|
58
|
+
| { type: "command:execute"; command: string; args?: unknown[] }
|
|
59
|
+
| { type: "tree:expand"; viewId: string; itemId: string }
|
|
60
|
+
| { type: "tree:click"; viewId: string; itemId: string; command?: string }
|
|
61
|
+
| { type: "webview:message"; panelId: string; message: unknown }
|
|
62
|
+
| { type: "quickpick:resolve"; requestId: string; selected: QuickPickItemMsg[] | null }
|
|
63
|
+
| { type: "inputbox:resolve"; requestId: string; value: string | null }
|
|
64
|
+
| { type: "notification:action"; id: string; action: string | null };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/** Extension manifest — parsed from package.json of an installed extension */
|
|
2
|
+
export interface ExtensionManifest {
|
|
3
|
+
id: string; // npm package name, e.g. @ppm/ext-database
|
|
4
|
+
version: string;
|
|
5
|
+
main: string; // JS entry point relative to extension root
|
|
6
|
+
displayName?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
icon?: string;
|
|
9
|
+
engines?: { ppm?: string };
|
|
10
|
+
activationEvents?: string[];
|
|
11
|
+
contributes?: ExtensionContributes;
|
|
12
|
+
ppm?: {
|
|
13
|
+
displayName?: string;
|
|
14
|
+
icon?: string;
|
|
15
|
+
webviewDir?: string;
|
|
16
|
+
};
|
|
17
|
+
permissions?: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** VSCode-compatible contributes section */
|
|
21
|
+
export interface ExtensionContributes {
|
|
22
|
+
commands?: ContributedCommand[];
|
|
23
|
+
views?: Record<string, ContributedView[]>;
|
|
24
|
+
configuration?: { properties?: Record<string, ConfigProperty> };
|
|
25
|
+
menus?: Record<string, ContributedMenu[]>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ContributedCommand {
|
|
29
|
+
command: string;
|
|
30
|
+
title: string;
|
|
31
|
+
icon?: string;
|
|
32
|
+
category?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ContributedView {
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
type?: "tree" | "webview";
|
|
39
|
+
icon?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ConfigProperty {
|
|
43
|
+
type?: string;
|
|
44
|
+
default?: unknown;
|
|
45
|
+
description?: string;
|
|
46
|
+
enum?: unknown[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ContributedMenu {
|
|
50
|
+
command: string;
|
|
51
|
+
when?: string;
|
|
52
|
+
group?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Runtime extension info returned by API */
|
|
56
|
+
export interface ExtensionInfo {
|
|
57
|
+
id: string;
|
|
58
|
+
version: string;
|
|
59
|
+
displayName: string;
|
|
60
|
+
description: string;
|
|
61
|
+
icon: string;
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
activated: boolean;
|
|
64
|
+
manifest: ExtensionManifest;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** DB row for extensions table */
|
|
68
|
+
export interface ExtensionRow {
|
|
69
|
+
id: string;
|
|
70
|
+
version: string;
|
|
71
|
+
display_name: string | null;
|
|
72
|
+
description: string | null;
|
|
73
|
+
icon: string | null;
|
|
74
|
+
enabled: number; // 0 | 1
|
|
75
|
+
manifest: string; // JSON
|
|
76
|
+
installed_at: string;
|
|
77
|
+
updated_at: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** DB row for extension_storage table */
|
|
81
|
+
export interface ExtensionStorageRow {
|
|
82
|
+
ext_id: string;
|
|
83
|
+
scope: string;
|
|
84
|
+
key: string;
|
|
85
|
+
value: string | null; // JSON
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Extension activation context passed to ext.activate() */
|
|
89
|
+
export interface ExtensionContext {
|
|
90
|
+
extensionId: string;
|
|
91
|
+
extensionPath: string;
|
|
92
|
+
globalState: StateStore;
|
|
93
|
+
workspaceState: StateStore;
|
|
94
|
+
subscriptions: Disposable[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface StateStore {
|
|
98
|
+
get<T = unknown>(key: string, defaultValue?: T): T | undefined;
|
|
99
|
+
update(key: string, value: unknown): Promise<void>;
|
|
100
|
+
keys(): readonly string[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface Disposable {
|
|
104
|
+
dispose(): void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** RPC message types between main process and extension host worker */
|
|
108
|
+
export type RpcMessage =
|
|
109
|
+
| RpcRequest
|
|
110
|
+
| RpcResponse
|
|
111
|
+
| RpcEvent;
|
|
112
|
+
|
|
113
|
+
export interface RpcRequest {
|
|
114
|
+
type: "request";
|
|
115
|
+
id: number;
|
|
116
|
+
method: string;
|
|
117
|
+
params: unknown[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface RpcResponse {
|
|
121
|
+
type: "response";
|
|
122
|
+
id: number;
|
|
123
|
+
result?: unknown;
|
|
124
|
+
error?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface RpcEvent {
|
|
128
|
+
type: "event";
|
|
129
|
+
event: string;
|
|
130
|
+
data: unknown;
|
|
131
|
+
}
|