@hienlh/ppm 0.18.11 → 0.18.13
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/AGENTS.md +17 -5
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +3 -0
- package/README.md +87 -29
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +19 -1
- package/dist/web/assets/{adaptive-context-menu-DGCKKeTy.js → adaptive-context-menu-CbgPYQmj.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-BOHHTTe7.js → ai-resource-editor-unM2HNmg.js} +1 -1
- package/dist/web/assets/{ai-settings-section-BigC_nZR.js → ai-settings-section-CdktdrwF.js} +1 -1
- package/dist/web/assets/{angular-html-BYPaydvh.js → angular-html-DcpbdcNG.js} +1 -1
- package/dist/web/assets/{angular-ts-Bb4AMKlk.js → angular-ts-Bv_WIk34.js} +1 -1
- package/dist/web/assets/api-client-BogqsFna.js +1 -0
- package/dist/web/assets/{api-settings-Bd3M4YpI.js → api-settings-BdCtex9i.js} +1 -1
- package/dist/web/assets/api-settings-DfXViPKz.js +1 -0
- package/dist/web/assets/{api-themes-Cpx419SL.js → api-themes-pRRoLvgD.js} +1 -1
- package/dist/web/assets/{apl-DB2VjFCS.js → apl-D5_lLszu.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-BzFm9ELu.js +1 -0
- package/dist/web/assets/{astro-BeoJdVpk.js → astro-CBHwddYL.js} +1 -1
- package/dist/web/assets/{audio-preview-BgFuTn-0.js → audio-preview-BD1iDovf.js} +1 -1
- package/dist/web/assets/{blade-Cf2q1sPf.js → blade-DGe1d2Pq.js} +1 -1
- package/dist/web/assets/{bsl-W1kOaNpu.js → bsl-2lyAzRJA.js} +1 -1
- package/dist/web/assets/{button-5zTrkEoN.js → button-2WCAS8dI.js} +1 -1
- package/dist/web/assets/{cairo-BoAZFcje.js → cairo-DjzZeJrq.js} +1 -1
- package/dist/web/assets/chat-tab-DzK8LGti.js +15 -0
- package/dist/web/assets/{cobol-B0Nm7RXj.js → cobol-cC3XIuAJ.js} +1 -1
- package/dist/web/assets/code-editor-CuMfPN9X.js +10 -0
- package/dist/web/assets/{coffee-BakMn3on.js → coffee-4eGboVoU.js} +1 -1
- package/dist/web/assets/{conflict-editor-BUs5k9q_.js → conflict-editor-DdJag4QK.js} +1 -1
- package/dist/web/assets/{context-menu-Bq92km4s.js → context-menu-BO1c9jKy.js} +1 -1
- package/dist/web/assets/{cpp-BRAJQHK2.js → cpp-DF9_G-OU.js} +1 -1
- package/dist/web/assets/{crystal-kqqO3zkK.js → crystal-OavZlzcm.js} +1 -1
- package/dist/web/assets/{csv-preview-ByoDgLAZ.js → csv-preview-DnXwouF3.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-Brkf9z41.js → data-grid-overlay-editor-B9Gpjm3l.js} +1 -1
- package/dist/web/assets/{database-viewer-Btc3h8Yu.js → database-viewer-Dk8oGyZx.js} +1 -1
- package/dist/web/assets/{dialog-DwtGlowE.js → dialog-B-l2ZxVm.js} +1 -1
- package/dist/web/assets/{diff-viewer-CZaDnR4w.js → diff-viewer-eaUBjLCH.js} +1 -1
- package/dist/web/assets/{docx-preview-DAD_vg59.js → docx-preview-DAdHPlhZ.js} +1 -1
- package/dist/web/assets/{edge-Cq1ZKZuE.js → edge-TzYh8OlN.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-B_VE7ppQ.js → edit-diff-preview-DlwP3UIU.js} +1 -1
- package/dist/web/assets/{elixir-BM_JoJRk.js → elixir-Cy3RhKKH.js} +1 -1
- package/dist/web/assets/{elm-Dw7oO08Y.js → elm-BTBZdweK.js} +1 -1
- package/dist/web/assets/{erb-CfKOGAfk.js → erb-dv1mI0y4.js} +1 -1
- package/dist/web/assets/{erlang-DlDmGVeE.js → erlang-DjeiDrkl.js} +1 -1
- package/dist/web/assets/{esm-DGAP0GFh.js → esm-FRwvcesu.js} +1 -1
- package/dist/web/assets/explorer-body-DkCkSVrN.js +2 -0
- package/dist/web/assets/explorer-window-content-MsnTVngu.js +1 -0
- package/dist/web/assets/{extension-store-Cpfh2vMm.js → extension-store-DUaZINyC.js} +1 -1
- package/dist/web/assets/{extension-webview-B2OnjuCu.js → extension-webview-W0qRfaAU.js} +1 -1
- package/dist/web/assets/{file-store-aOERIEo-.js → file-store-yReVbR8q.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-XnAhQWxS.js → fortran-fixed-form-BCH9DAIJ.js} +1 -1
- package/dist/web/assets/{fsharp-CdNvy0PG.js → fsharp-C93NYKYD.js} +1 -1
- package/dist/web/assets/{gdresource-DxuG5i8i.js → gdresource-BcIEE2eX.js} +1 -1
- package/dist/web/assets/{git-commit-3lMonsur.js → git-commit-BVjlXEy8.js} +1 -1
- package/dist/web/assets/{git-log-panel-Co0PBPHC.js → git-log-panel-2_4HuP47.js} +1 -1
- package/dist/web/assets/{git-rebase-N8Z9kIMf.js → git-rebase-Bj_21B1V.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-B72vcb2r.js +1 -0
- package/dist/web/assets/glide-data-grid-B0IY6n_Q.js +138 -0
- package/dist/web/assets/{glimmer-js-Dp9OXiib.js → glimmer-js-C20yj4Zv.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CDVKBcDj.js → glimmer-ts-ipbYC7kh.js} +1 -1
- package/dist/web/assets/{glsl-DPJxI4us.js → glsl-Dovazu3t.js} +1 -1
- package/dist/web/assets/{graphql-BrFhaQQl.js → graphql-DpMlM7hC.js} +1 -1
- package/dist/web/assets/{group-chat-tab-rOd8J8GC.js → group-chat-tab-DRLDHOp6.js} +1 -1
- package/dist/web/assets/{hack-BzXbDMlA.js → hack-gc7LCBuV.js} +1 -1
- package/dist/web/assets/{haml-CPpkX0s5.js → haml-vtlFm9gm.js} +1 -1
- package/dist/web/assets/{handlebars-BqrApy52.js → handlebars-DGKhpCji.js} +1 -1
- package/dist/web/assets/{html-DmjUl-OA.js → html-DvfhQbXb.js} +1 -1
- package/dist/web/assets/{html-derivative-Cnd6XFR5.js → html-derivative-C3oTSSCN.js} +1 -1
- package/dist/web/assets/{http-DMLWYun2.js → http-SG6zLTo7.js} +1 -1
- package/dist/web/assets/{hurl-CKkCVxbj.js → hurl-BWXsYlqW.js} +1 -1
- package/dist/web/assets/{hxml-CAx19TtQ.js → hxml-DUdMzaoh.js} +1 -1
- package/dist/web/assets/{image-preview-DHUoylQ1.js → image-preview-DxgFOV1T.js} +1 -1
- package/dist/web/assets/index-CBn4SOoT.js +52 -0
- package/dist/web/assets/index-DWpNR3nB.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-3irPxRK_.js +1 -0
- package/dist/web/assets/{input-DcEk7mA3.js → input-DuAF-Stj.js} +1 -1
- package/dist/web/assets/{jinja-BmB9YN9l.js → jinja-955w9RKM.js} +1 -1
- package/dist/web/assets/{jison-BaVsKKpw.js → jison-WWfqPo50.js} +1 -1
- package/dist/web/assets/{julia-BgVBLveX.js → julia-BFrfnaNl.js} +1 -1
- package/dist/web/assets/{just-C2ENIbl9.js → just-BrZdyR_I.js} +1 -1
- package/dist/web/assets/{keybindings-store-BGIyqXHI.js → keybindings-store-0Wqfa7Qp.js} +1 -1
- package/dist/web/assets/keybindings-store-d54pv2NK.js +1 -0
- package/dist/web/assets/{latex-BiDIWxKL.js → latex-CwtPPASD.js} +1 -1
- package/dist/web/assets/{liquid-NxpapreE.js → liquid-CYZuvDDd.js} +1 -1
- package/dist/web/assets/{lua-DVepQvwH.js → lua-BV095ZyY.js} +1 -1
- package/dist/web/assets/markdown-renderer-BTS8ifdu.js +1 -0
- package/dist/web/assets/{markdown-renderer-DlcDhgrF.js → markdown-renderer-CnRcKK3d.js} +3 -3
- package/dist/web/assets/{marko-CJ2606Xl.js → marko-CNeljZ1C.js} +1 -1
- package/dist/web/assets/{mdc-D2bjqclE.js → mdc-JfGapShy.js} +1 -1
- package/dist/web/assets/{media-url-H17XJF-I.js → media-url-Bm4PsduE.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-g-r2hCFa.js → mobile-bottom-sheet-M5iZDX7y.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BqK2-WuN.js +1 -0
- package/dist/web/assets/{nextflow-BtJwUbfF.js → nextflow-DD5C8nqX.js} +1 -1
- package/dist/web/assets/{nginx-j4gyIhHE.js → nginx-Ca7B__x_.js} +1 -1
- package/dist/web/assets/{nim-ClH_gjP5.js → nim-B8FogsDw.js} +1 -1
- package/dist/web/assets/notification-store-Yvy1I8gd.js +1 -0
- package/dist/web/assets/{number-overlay-editor-C1EqJnWq.js → number-overlay-editor-DG9xJwEG.js} +1 -1
- package/dist/web/assets/{overview-panel-vTeeJYVZ.js → overview-panel-s53bnl0q.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-Z5jWjFCV.js +1 -0
- package/dist/web/assets/{panel-store-BSnkjgHt.js → panel-store-B4rijFKN.js} +1 -1
- package/dist/web/assets/{pdf-preview-CqFGRY0Z.js → pdf-preview-DYt3aBzM.js} +1 -1
- package/dist/web/assets/{perl-C9H7X7CT.js → perl-BR78hS8R.js} +1 -1
- package/dist/web/assets/{php-Bx2U-cQ_.js → php-DXBOMC9z.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-Bq7UMi8P.js +1 -0
- package/dist/web/assets/{postgres-viewer-DHwcQucn.js → postgres-viewer-BvoWSuoZ.js} +3 -3
- package/dist/web/assets/{process-table-BGRJNlpn.js → process-table-BYwKigiD.js} +1 -1
- package/dist/web/assets/{project-store--nvQiy75.js → project-store-BuY7rxn_.js} +1 -1
- package/dist/web/assets/{pug-Cw74sKsE.js → pug-CvTwVVKV.js} +1 -1
- package/dist/web/assets/{qml-BXD94Eqz.js → qml-CPSoZ1-B.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-BLo4BOTD.js +1 -0
- package/dist/web/assets/{razor-DZ0-Ge0S.js → razor-BUA1YKtF.js} +1 -1
- package/dist/web/assets/{rst-Dtdh0QUj.js → rst-BJsq7ISz.js} +1 -1
- package/dist/web/assets/{ruby-BC51BW2Q.js → ruby-DSDMeYf5.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Sd3KyjN4.js → run-in-terminal-C9xpskFn.js} +1 -1
- package/dist/web/assets/{sas-BN8i4qOU.js → sas-B1nJOjbN.js} +1 -1
- package/dist/web/assets/{scroll-area-0gljZ6lF.js → scroll-area-DBcG1fQo.js} +1 -1
- package/dist/web/assets/{scss-H-QtA7Rz.js → scss-BZFyRiQn.js} +1 -1
- package/dist/web/assets/{send-to-chat-u6m8l-rH.js → send-to-chat-BIg70MUZ.js} +1 -1
- package/dist/web/assets/{settings-store-CHY6Kzin.js → settings-store-GV5pkkke.js} +2 -2
- package/dist/web/assets/{settings-tab-IY6K1MiZ.js → settings-tab-BePvIcjd.js} +1 -1
- package/dist/web/assets/settings-tab-Br-lJSPa.js +1 -0
- package/dist/web/assets/{shaderlab-CalNsj_x.js → shaderlab-laINRXBo.js} +1 -1
- package/dist/web/assets/{shellsession-BFrcoR8x.js → shellsession-BpOOvWVR.js} +1 -1
- package/dist/web/assets/{soy-CNbku7vP.js → soy-BW4d91Sa.js} +1 -1
- package/dist/web/assets/{sparql-BN_orcVp.js → sparql-BsvvyoHn.js} +1 -1
- package/dist/web/assets/{sql-query-editor-C6pC3I8l.js → sql-query-editor-qQce5QJ6.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DTr5PzUb.js → sqlite-viewer-yhc0YNvu.js} +1 -1
- package/dist/web/assets/{stata-C6a5Ijzq.js → stata-K-dZInUt.js} +1 -1
- package/dist/web/assets/{surrealql-ClplI_HY.js → surrealql-CpNp2sgf.js} +1 -1
- package/dist/web/assets/{svelte-DNm-UNXB.js → svelte-BG6iBA8R.js} +1 -1
- package/dist/web/assets/{switch-DKvKya9y.js → switch-_NcrktLe.js} +1 -1
- package/dist/web/assets/system-monitor-body-D8Z5QXBB.js +2 -0
- package/dist/web/assets/system-monitor-tab-BclYXFTe.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BUqRsPK2.js → system-monitor-window-content-CaOhD0AU.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BnqGPu8a.js → tab-host-window-content-f_Rc5rJK.js} +1 -1
- package/dist/web/assets/{tab-store-CJuJRapl.js → tab-store-tFnyPmm4.js} +1 -1
- package/dist/web/assets/team-member-sheet-QDHoYCs0.js +2 -0
- package/dist/web/assets/{team-member-window-content-T4Q5JI-G.js → team-member-window-content-B1jkKkDi.js} +1 -1
- package/dist/web/assets/{templ-CSdqVobu.js → templ-gxNKXmt3.js} +1 -1
- package/dist/web/assets/{terminal-tab-Bb2rZegQ.js → terminal-tab-UvwM0sty.js} +3 -3
- package/dist/web/assets/{tex-DzSJ4kcZ.js → tex-C0ON27g5.js} +1 -1
- package/dist/web/assets/{tool-cards-lhqhrDTT.js → tool-cards-jB7GT-wj.js} +4 -4
- package/dist/web/assets/treemap-KZPCXAKY-dyrE6SW2.js +1 -0
- package/dist/web/assets/{ts-tags-C28w3bTg.js → ts-tags-BX_CTVgQ.js} +1 -1
- package/dist/web/assets/{twig-i5jY3QXB.js → twig-CwL7XnU0.js} +1 -1
- package/dist/web/assets/{use-blob-url-Dsq9OjTh.js → use-blob-url-DcRcjNTE.js} +1 -1
- package/dist/web/assets/use-coarse-long-press-BmI_GVW5.js +1 -0
- package/dist/web/assets/{use-monaco-theme-BZvlsoBv.js → use-monaco-theme--9tOOkDA.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DkiEjFYE.js → use-open-team-member-1hRsHLKg.js} +1 -1
- package/dist/web/assets/{use-websocket-B8R50f7f.js → use-websocket-rxJLbFqy.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-By4AVvJY.js → vendor-mermaid-CHTa6D69.js} +1 -1
- package/dist/web/assets/video-preview-DMhnf2Gi.js +1 -0
- package/dist/web/assets/{vue-Ceyet_lv.js → vue-Bykm6wSS.js} +1 -1
- package/dist/web/assets/{vue-html-GuU7g_hg.js → vue-html-CETdK_iV.js} +1 -1
- package/dist/web/assets/{vue-vine-CxXoaJr2.js → vue-vine-DiJb6fO5.js} +1 -1
- package/dist/web/assets/{window-store-CsEW6jiE.js → window-store-CQ1lghvv.js} +1 -1
- package/dist/web/assets/{xml-CbqOVhkP.js → xml-BNw8J5YW.js} +1 -1
- package/dist/web/assets/{xsl-KfAmCauy.js → xsl-CsG9L31b.js} +1 -1
- package/dist/web/index.html +46 -45
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +309 -0
- package/docs/architecture/data-and-storage.md +356 -0
- package/docs/architecture/extensions.md +496 -0
- package/docs/architecture/integrations.md +214 -0
- package/docs/architecture/workspace-and-ui.md +391 -0
- package/docs/claude-agent-sdk-reference.md +1 -1
- package/docs/code-patterns.md +361 -0
- package/docs/code-standards.md +8 -358
- package/docs/codebase-subsystems.md +364 -0
- package/docs/codebase-summary.md +34 -379
- package/docs/deployment-guide.md +280 -490
- package/docs/design-guidelines.md +1 -1
- package/docs/lessons-learned.md +27 -10
- package/docs/project-changelog.md +18 -806
- package/docs/project-overview-pdr.md +117 -111
- package/docs/project-roadmap.md +89 -133
- package/docs/system-architecture.md +77 -1826
- package/package.json +2 -2
- package/src/cli/commands/autostart.ts +2 -2
- package/src/cli/commands/config-cmd.ts +12 -3
- package/src/cli/commands/status.ts +3 -2
- package/src/cli/commands/stop.ts +32 -4
- package/src/server/index.ts +17 -3
- package/src/server/routes/chat-unread.ts +36 -0
- package/src/server/routes/named-tunnel.ts +132 -0
- package/src/server/routes/tunnel.ts +13 -0
- package/src/server/ws/chat.ts +5 -2
- package/src/services/autostart-register.ts +47 -29
- package/src/services/cloud.service.ts +26 -1
- package/src/services/config-secret-keys.ts +48 -0
- package/src/services/config.service.ts +1 -1
- package/src/services/db.service.ts +62 -12
- package/src/services/extension-rpc-handlers.ts +9 -1
- package/src/services/file-watcher/watch-tree.ts +67 -33
- package/src/services/file-watcher.service.ts +28 -10
- package/src/services/fs-credential-path-guard.ts +103 -0
- package/src/services/fs-path-guard.service.ts +14 -66
- package/src/services/named-tunnel/cloudflare-dns-api.ts +40 -0
- package/src/services/named-tunnel/cloudflare-zone-api.ts +28 -0
- package/src/services/named-tunnel/cloudflared-cert.ts +95 -0
- package/src/services/named-tunnel/cloudflared-exec.ts +50 -0
- package/src/services/named-tunnel/cloudflared-login-helpers.ts +98 -0
- package/src/services/named-tunnel/cloudflared-login.service.ts +182 -0
- package/src/services/named-tunnel/hostname-rules.ts +58 -0
- package/src/services/named-tunnel/login-output-parser.ts +36 -0
- package/src/services/named-tunnel/named-tunnel-args.ts +110 -0
- package/src/services/named-tunnel/named-tunnel-config.ts +67 -0
- package/src/services/named-tunnel/named-tunnel-probe-state.ts +95 -0
- package/src/services/named-tunnel/named-tunnel-readiness.ts +59 -0
- package/src/services/named-tunnel/named-tunnel-runtime.ts +42 -0
- package/src/services/named-tunnel/named-tunnel-setup-confirm.ts +98 -0
- package/src/services/named-tunnel/named-tunnel-setup.service.ts +191 -0
- package/src/services/supervisor-state.ts +67 -2
- package/src/services/supervisor.ts +454 -157
- package/src/services/tunnel-registry-parse.ts +27 -0
- package/src/services/tunnel-registry.service.ts +2 -15
- package/src/services/tunnel.service.ts +12 -3
- package/src/types/config.ts +31 -0
- package/src/web/app.tsx +5 -0
- package/src/web/components/chat/chat-history-bar.tsx +95 -98
- package/src/web/components/chat/chat-toolbar-overflow-menu.tsx +57 -0
- package/src/web/components/chat/session-debug-dialog.tsx +35 -27
- package/src/web/components/layout/cloud-share-named-tunnel-row.tsx +111 -0
- package/src/web/components/layout/cloud-share-popover.tsx +56 -14
- package/src/web/components/shared/connection-lost-overlay.tsx +44 -5
- package/src/web/components/tunnels/named-tunnel/hostname-validation.ts +14 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +96 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-hostname-field.tsx +51 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-login-step.tsx +105 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +159 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-setup-content.tsx +197 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-setup-popup.tsx +57 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-step-reducer.ts +191 -0
- package/src/web/components/tunnels/named-tunnel/use-named-tunnel-setup.ts +212 -0
- package/src/web/components/tunnels/tunnel-manager-tab.tsx +4 -0
- package/src/web/hooks/use-global-events.ts +3 -1
- package/src/web/lib/api-named-tunnel.ts +54 -0
- package/src/web/lib/last-known-local-url.ts +23 -0
- package/src/web/stores/notification-store.ts +20 -1
- package/.spike/cdp-client.ts +0 -116
- package/.spike/cleanup-ppm-workspace.mjs +0 -44
- package/.spike/group-protected-probe.ts +0 -21
- package/.spike/pip-eval.ts +0 -17
- package/.spike/pip-page-helpers.js +0 -119
- package/.spike/pip-results-editor-glue.json +0 -105
- package/.spike/pip-results-editor.json +0 -158
- package/.spike/pip-results-input-isolation.json +0 -28
- package/.spike/pip-results-key-probe.json +0 -58
- package/.spike/pip-results-listener-survival.json +0 -98
- package/.spike/pip-results-listeners.json +0 -36
- package/.spike/pip-results-pagehide.json +0 -92
- package/.spike/pip-results-resize.json +0 -104
- package/.spike/pip-results-terminal-debug.json +0 -99
- package/.spike/pip-results-terminal-v2.json +0 -191
- package/.spike/pip-results-terminal.json +0 -220
- package/.spike/pip-results-xterm-trace.json +0 -73
- package/.spike/pip-spike-common.ts +0 -83
- package/.spike/pip-spike-editor-glue.ts +0 -82
- package/.spike/pip-spike-editor.ts +0 -129
- package/.spike/pip-spike-input-isolation.ts +0 -38
- package/.spike/pip-spike-key-probe.ts +0 -34
- package/.spike/pip-spike-listener-survival.ts +0 -54
- package/.spike/pip-spike-listeners.ts +0 -19
- package/.spike/pip-spike-pagehide.ts +0 -59
- package/.spike/pip-spike-resize.ts +0 -42
- package/.spike/pip-spike-setup.ts +0 -63
- package/.spike/pip-spike-terminal-debug.ts +0 -81
- package/.spike/pip-spike-terminal-v2.ts +0 -139
- package/.spike/pip-spike-terminal.ts +0 -169
- package/.spike/pip-spike-xterm-trace.ts +0 -36
- package/.spike/probe-drag-memo-error.mjs +0 -67
- package/.spike/read-dev-db.ts +0 -15
- package/.spike/s.err +0 -63
- package/.spike/scratch-editor-target.ts +0 -7
- package/.spike/smoke-unified-chrome-pip.mjs +0 -353
- package/.spike/tunnel.err +0 -63
- package/.spike/vite-restart.err +0 -663
- package/.spike/w.err +0 -571
- package/dist/web/assets/api-client-DTZEgjBQ.js +0 -1
- package/dist/web/assets/api-settings-kd1j4iR8.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-8dCMT9r7.js +0 -1
- package/dist/web/assets/chat-tab-E3bf_V8X.js +0 -15
- package/dist/web/assets/code-editor-BUlM2gXk.js +0 -10
- package/dist/web/assets/explorer-body-9aBmyVpZ.js +0 -2
- package/dist/web/assets/explorer-window-content-DRtqoSPA.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-2e_8dfbs.js +0 -1
- package/dist/web/assets/glide-data-grid-C4_zIneN.js +0 -138
- package/dist/web/assets/index-BImUaigO.js +0 -52
- package/dist/web/assets/index-CSfi6DSU.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BZi1Gha1.js +0 -1
- package/dist/web/assets/keybindings-store-CzV2sPls.js +0 -1
- package/dist/web/assets/markdown-renderer-CcbR33VG.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-SIwQXlwA.js +0 -1
- package/dist/web/assets/notification-store-DV5QtUTg.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-o5htEcin.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BXAIFSAD.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-Dbt2Aebl.js +0 -1
- package/dist/web/assets/settings-tab-Dic6TKkU.js +0 -1
- package/dist/web/assets/system-monitor-body-iwGiCns9.js +0 -2
- package/dist/web/assets/system-monitor-tab-DdXQhEel.js +0 -1
- package/dist/web/assets/team-member-sheet-LkAFSCvb.js +0 -2
- package/dist/web/assets/treemap-KZPCXAKY-DUbnl9dz.js +0 -1
- package/dist/web/assets/video-preview-m0-3p7QZ.js +0 -1
- /package/dist/web/assets/{abap-C6nB04tn.js → abap-DGlrX_8G.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Vx4rw2sC.js → actionscript-3-CUqRtUJE.js} +0 -0
- /package/dist/web/assets/{ada-Ccd8vM20.js → ada-CY3Spa33.js} +0 -0
- /package/dist/web/assets/{andromeeda-DnBX5xMg.js → andromeeda-Lxs2TJn7.js} +0 -0
- /package/dist/web/assets/{apache-B-9_sx0g.js → apache-DfxgGdgM.js} +0 -0
- /package/dist/web/assets/{apex-_wPVBYoL.js → apex-QVDF1dnI.js} +0 -0
- /package/dist/web/assets/{api-client-aQb5hHE5.js → api-client-oY1tC3tM.js} +0 -0
- /package/dist/web/assets/{applescript-CcvD-qr0.js → applescript-pwJ8QXh-.js} +0 -0
- /package/dist/web/assets/{apply-theme-BeLRfsSu.js → apply-theme-B_bMVL6a.js} +0 -0
- /package/dist/web/assets/{ara-C40Ztoms.js → ara-mPjeX5YX.js} +0 -0
- /package/dist/web/assets/{asciidoc-CybYkJV4.js → asciidoc-Bf7LsDkI.js} +0 -0
- /package/dist/web/assets/{asm-9eOKwzR0.js → asm-rRKC1OUx.js} +0 -0
- /package/dist/web/assets/{aurora-x-B5ZH4t7t.js → aurora-x-DwQr0DkG.js} +0 -0
- /package/dist/web/assets/{awk-Dqilob-2.js → awk-Bpt768fN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-CPgRhf5r.js → ayu-dark-D9lwQHMs.js} +0 -0
- /package/dist/web/assets/{ayu-light-Do06Q52f.js → ayu-light-B4-ptrU5.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-_BhxcrxY.js → ayu-mirage-DAyRzH5Q.js} +0 -0
- /package/dist/web/assets/{ballerina-DDwK54QW.js → ballerina-Cgdpj1HC.js} +0 -0
- /package/dist/web/assets/{bat-BGyGlzGk.js → bat-4FrN36S2.js} +0 -0
- /package/dist/web/assets/{beancount-CRWBdapI.js → beancount-DkcxQER3.js} +0 -0
- /package/dist/web/assets/{berry-DbSFn2gO.js → berry-Dnuut-Ml.js} +0 -0
- /package/dist/web/assets/{bibtex-QpsV9gm9.js → bibtex-CCDwmbuA.js} +0 -0
- /package/dist/web/assets/{bicep-CxS8CUve.js → bicep-BC0ryoGM.js} +0 -0
- /package/dist/web/assets/{bird2-CuZhQzYR.js → bird2-C50V_A-0.js} +0 -0
- /package/dist/web/assets/{c-9bnDqE3g.js → c-Ddorhq0U.js} +0 -0
- /package/dist/web/assets/{c3-JMZTrOhA.js → c3-DdIf5r9E.js} +0 -0
- /package/dist/web/assets/{cadence-DsDNayJS.js → cadence-s9pd1Wng.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-BJfpynya.js → can-open-in-ppm-C6hZV1OP.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DYIKXXBe.js → catppuccin-frappe-CzJNTyu-.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-IYYOnUJ8.js → catppuccin-latte-BNpt-m7s.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-BrJ_7mfS.js → catppuccin-macchiato-FYGkfsJ5.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-gzSu7v7-.js → catppuccin-mocha-it2g5D1q.js} +0 -0
- /package/dist/web/assets/{clarity-DLzsC0Hb.js → clarity-BVFBTjMe.js} +0 -0
- /package/dist/web/assets/{clipboard-7fw0Shic.js → clipboard-BWznymz7.js} +0 -0
- /package/dist/web/assets/{clojure-DS1BvoTt.js → clojure-BSYvXMI8.js} +0 -0
- /package/dist/web/assets/{cmake-CDvsJwYp.js → cmake-DGEqH-Np.js} +0 -0
- /package/dist/web/assets/{codeowners-BjsYiflJ.js → codeowners-D8FCOxWg.js} +0 -0
- /package/dist/web/assets/{codeql-CG-7ARat.js → codeql-COV0eluG.js} +0 -0
- /package/dist/web/assets/{common-lisp-DSO1Lbh9.js → common-lisp-xHnZUlgt.js} +0 -0
- /package/dist/web/assets/{coq-DYdh3oP_.js → coq-Cs_2i3Xg.js} +0 -0
- /package/dist/web/assets/{csharp-DxEBz5s0.js → csharp-B1ONB4PT.js} +0 -0
- /package/dist/web/assets/{css-dal3Q-8C.js → css-BgGQbvrZ.js} +0 -0
- /package/dist/web/assets/{csv-CpqjMGiA.js → csv-CNHmQBzs.js} +0 -0
- /package/dist/web/assets/{csv-parser-3umBA4NY.js → csv-parser-DWR24J17.js} +0 -0
- /package/dist/web/assets/{cue-DinoJ_5k.js → cue-ZXAKzwIy.js} +0 -0
- /package/dist/web/assets/{cypher-DzxqmkUW.js → cypher-4PL5bmT5.js} +0 -0
- /package/dist/web/assets/{d-BxNwJuEC.js → d-CEsV3RT2.js} +0 -0
- /package/dist/web/assets/{dark-plus-Clmv3V-0.js → dark-plus-NtFnYSDz.js} +0 -0
- /package/dist/web/assets/{dart-BWVtXD35.js → dart-CKHn4mDf.js} +0 -0
- /package/dist/web/assets/{data-grid-types-B4n51Fck.js → data-grid-types-CxU8oo-j.js} +0 -0
- /package/dist/web/assets/{dax-DsImuWgJ.js → dax-Cmkj1I_M.js} +0 -0
- /package/dist/web/assets/{desktop-B0jJuG2r.js → desktop-DJLalvPQ.js} +0 -0
- /package/dist/web/assets/{diff-CWqAAxoE.js → diff-BnS2wpj6.js} +0 -0
- /package/dist/web/assets/{dist-C0eNNfbt.js → dist-CuIdRyUB.js} +0 -0
- /package/dist/web/assets/{dist-B-EXdSYE.js → dist-uYjs3Wv6.js} +0 -0
- /package/dist/web/assets/{docker-CYFCJdIL.js → docker-CovNy7ml.js} +0 -0
- /package/dist/web/assets/{dotenv-CKDUjURg.js → dotenv-Gh4w3I6B.js} +0 -0
- /package/dist/web/assets/{dracula-CSoPspHE.js → dracula-NF0tAHXl.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BuFjw5L1.js → dracula-soft-Cj_qBurK.js} +0 -0
- /package/dist/web/assets/{dream-maker-B6PdDRZu.js → dream-maker-CRuZPcl7.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CMcXvD0G.js → emacs-lisp-BACmC9qj.js} +0 -0
- /package/dist/web/assets/{everforest-dark-cU8-wYzi.js → everforest-dark-Dd9av4QG.js} +0 -0
- /package/dist/web/assets/{everforest-light-KhorllqK.js → everforest-light-C-jsss-B.js} +0 -0
- /package/dist/web/assets/{external-link-B8BynhvY.js → external-link-B6Hl0U4X.js} +0 -0
- /package/dist/web/assets/{fennel-BCELZK87.js → fennel-BJA9XnL8.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-CZ4DiR75.js → file-exclamation-point-JzhTU2TX.js} +0 -0
- /package/dist/web/assets/{fish-BQc0y_jy.js → fish-CKlhNSc2.js} +0 -0
- /package/dist/web/assets/{fluent-BTINnwF6.js → fluent-wfglRk5D.js} +0 -0
- /package/dist/web/assets/{folder-symlink-B8XrtvQK.js → folder-symlink-Dx1hOk8X.js} +0 -0
- /package/dist/web/assets/{format-bytes-WVcFxJuT.js → format-bytes-utwHMTry.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BQim9wDz.js → fortran-free-form-BlXCdid3.js} +0 -0
- /package/dist/web/assets/{gdscript-03w5u8un.js → gdscript-BwAVG6Et.js} +0 -0
- /package/dist/web/assets/{gdshader-Dp2daNzU.js → gdshader-DPc_T7ib.js} +0 -0
- /package/dist/web/assets/{genie-CoNcjPiD.js → genie-B-zZSLCX.js} +0 -0
- /package/dist/web/assets/{gherkin-NrPOSPQL.js → gherkin-CJ0ajpGs.js} +0 -0
- /package/dist/web/assets/{github-dark-z4RE9ZqA.js → github-dark-Dz8ZkzSP.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DOKw-1cI.js → github-dark-default-CNUYo_kp.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-DJRNMkEA.js → github-dark-dimmed-241z_Ycx.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Bj7jJ_Gs.js → github-dark-high-contrast-7r6yZZYH.js} +0 -0
- /package/dist/web/assets/{github-light-B4k_yMFu.js → github-light-C8gmZn-D.js} +0 -0
- /package/dist/web/assets/{github-light-default-BHc-Uzhs.js → github-light-default-GAEgq1eA.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-CHisIywL.js → github-light-high-contrast-3z91352E.js} +0 -0
- /package/dist/web/assets/{gleam-CfbypZ4q.js → gleam-C7vxW5zd.js} +0 -0
- /package/dist/web/assets/{gn-YoAjyhJa.js → gn-CyJIXwHP.js} +0 -0
- /package/dist/web/assets/{gnuplot-xVkc1f1c.js → gnuplot-CqLbGgPd.js} +0 -0
- /package/dist/web/assets/{go-GPCwP3Gu.js → go-DdDcK9aX.js} +0 -0
- /package/dist/web/assets/{groovy-Crh4Yww9.js → groovy-UYHggEqH.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-BluUFuDS.js → gruvbox-dark-hard-B4ierkJn.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Cxk1FWQp.js → gruvbox-dark-medium-DYwkDSZ6.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CHXMiFmj.js → gruvbox-dark-soft-CVL4P-Lz.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-B6eKPjAD.js → gruvbox-light-hard-BJ0LewUZ.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-pDwMm1ut.js → gruvbox-light-medium-CaqqjeGJ.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-Cxu6kHqK.js → gruvbox-light-soft-ByKMoVNr.js} +0 -0
- /package/dist/web/assets/{haskell-C6JKHjbK.js → haskell-BilYRVPp.js} +0 -0
- /package/dist/web/assets/{haxe-BuBNdF0a.js → haxe-Brs9Bj1s.js} +0 -0
- /package/dist/web/assets/{hcl-DZpDdGj2.js → hcl-ClrZpol-.js} +0 -0
- /package/dist/web/assets/{hjson-CBkhYRuo.js → hjson-9PvHnYMX.js} +0 -0
- /package/dist/web/assets/{hlsl-C5nMxnp9.js → hlsl-BbBk4vqV.js} +0 -0
- /package/dist/web/assets/{horizon-VAFvA6R6.js → horizon-E5Py0cny.js} +0 -0
- /package/dist/web/assets/{horizon-bright-BC0xn7T3.js → horizon-bright-DVCsNGEk.js} +0 -0
- /package/dist/web/assets/{houston-BxrUivmz.js → houston-JdFEax9Y.js} +0 -0
- /package/dist/web/assets/{hy-DLtOXoaH.js → hy-W_W3tLKT.js} +0 -0
- /package/dist/web/assets/{image-gallery-Gnf7TdCR.js → image-gallery-5IURd6db.js} +0 -0
- /package/dist/web/assets/{imba-BdpFXM2W.js → imba-CfAqJz7T.js} +0 -0
- /package/dist/web/assets/{ini-BInJO_T0.js → ini-C_3gvKWs.js} +0 -0
- /package/dist/web/assets/{java-Dxuddnkb.js → java-CKqtXZ-j.js} +0 -0
- /package/dist/web/assets/{javascript-CVamJfLY.js → javascript-C7gSEfae.js} +0 -0
- /package/dist/web/assets/{json-BLZ0ozYk.js → json-BoN6e11T.js} +0 -0
- /package/dist/web/assets/{json5-BnXzmMKs.js → json5-CgE7C8HW.js} +0 -0
- /package/dist/web/assets/{jsonc-CSgkSga7.js → jsonc-DjyoattY.js} +0 -0
- /package/dist/web/assets/{jsonl-CY5W2nF2.js → jsonl-lI82wGSw.js} +0 -0
- /package/dist/web/assets/{jsonnet-CEJYH3sF.js → jsonnet-CszEI8va.js} +0 -0
- /package/dist/web/assets/{jssm-CsJc7Uhk.js → jssm-CZXDlszb.js} +0 -0
- /package/dist/web/assets/{jsx-jCiRaHU7.js → jsx-BeRz0eEz.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-DfoA_GxX.js → kanagawa-dragon-BOmnUiyd.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-BOLYJpWd.js → kanagawa-lotus-ffU_P5w-.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-DfKlbT1G.js → kanagawa-wave-DKTJZrMu.js} +0 -0
- /package/dist/web/assets/{kdl-CmssIFTi.js → kdl-BWECJveH.js} +0 -0
- /package/dist/web/assets/{kotlin-BRsNgp6x.js → kotlin-DpVwBCSt.js} +0 -0
- /package/dist/web/assets/{kusto-T8X9jUab.js → kusto-BgVqjUZA.js} +0 -0
- /package/dist/web/assets/{laserwave-DkJMVJ8Z.js → laserwave-CEVCi8em.js} +0 -0
- /package/dist/web/assets/{lean-C65KGVPq.js → lean-CGyfRhLJ.js} +0 -0
- /package/dist/web/assets/{less-exIPdDlf.js → less-B0_H10Ku.js} +0 -0
- /package/dist/web/assets/{lib-mC1NZfuw.js → lib-hrC3bruJ.js} +0 -0
- /package/dist/web/assets/{libesm-DSutUtl1.js → libesm-DVpFmwJu.js} +0 -0
- /package/dist/web/assets/{light-plus-DiHUXAZt.js → light-plus-DqzMqPID.js} +0 -0
- /package/dist/web/assets/{llvm-CJBnEzFM.js → llvm-9ucnfNiF.js} +0 -0
- /package/dist/web/assets/{loader-circle-gSPVxfvH.js → loader-circle-Dpqcv0eS.js} +0 -0
- /package/dist/web/assets/{lock-Did-87UV.js → lock-DldbT31x.js} +0 -0
- /package/dist/web/assets/{log-DxMyUMuP.js → log-m6A2dOSj.js} +0 -0
- /package/dist/web/assets/{logo-CC9NKzca.js → logo-ByPcsC9P.js} +0 -0
- /package/dist/web/assets/{luau-Bw10I6zf.js → luau-DFCo8nBn.js} +0 -0
- /package/dist/web/assets/{make-C_6M90m6.js → make-C90w3MeA.js} +0 -0
- /package/dist/web/assets/{markdown-bR9_R-LF.js → markdown-DT0uywZG.js} +0 -0
- /package/dist/web/assets/{material-theme-BUx2Qujn.js → material-theme-BVUO93tJ.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-CRJrPgKm.js → material-theme-darker-xXgGDwnF.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-CUAYppPa.js → material-theme-lighter-BjStPzWB.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-DuINF1fg.js → material-theme-ocean-wFmSiKjy.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-W6_icGCj.js → material-theme-palenight-e-0CtRoe.js} +0 -0
- /package/dist/web/assets/{matlab-rfqn_b3z.js → matlab-BNaNQAUH.js} +0 -0
- /package/dist/web/assets/{mdx-Bh1B_YOJ.js → mdx-DfpVWAqO.js} +0 -0
- /package/dist/web/assets/{mermaid-Dxf6Ya2k.js → mermaid-Bpq7wFCP.js} +0 -0
- /package/dist/web/assets/{min-dark-CivZ2L0h.js → min-dark-CL2Y0WH6.js} +0 -0
- /package/dist/web/assets/{min-light-B1Ykqtma.js → min-light-DOgr_IdP.js} +0 -0
- /package/dist/web/assets/{mipsasm-DynHaM0d.js → mipsasm-C9ie2zlB.js} +0 -0
- /package/dist/web/assets/{mojo-Clw5PbGH.js → mojo-Md2A2sJy.js} +0 -0
- /package/dist/web/assets/{monitor-Dv8vn_6O.js → monitor-4vWfqzuN.js} +0 -0
- /package/dist/web/assets/{monokai-DFIooFZT.js → monokai-nbXIEySB.js} +0 -0
- /package/dist/web/assets/{moonbit-DnBSZWGM.js → moonbit-CJ-T-s4A.js} +0 -0
- /package/dist/web/assets/{move-CCcL2PfL.js → move-DwEehLIK.js} +0 -0
- /package/dist/web/assets/{narrat-BDarg5Lk.js → narrat-CuF4sIKr.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-DrZOeCNW.js → nextflow-groovy-1dYhfPfq.js} +0 -0
- /package/dist/web/assets/{night-owl-BnZZCK7v.js → night-owl-gZS5mfzY.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CEwBsVHS.js → night-owl-light-DDAster5.js} +0 -0
- /package/dist/web/assets/{nix-amCzOPMR.js → nix-BlbEYs2c.js} +0 -0
- /package/dist/web/assets/{nord-BIs2BC6q.js → nord-BRiPb8Vy.js} +0 -0
- /package/dist/web/assets/{nushell-Y02fo4yw.js → nushell-DgWicB1i.js} +0 -0
- /package/dist/web/assets/{objective-c-7xOZtWW1.js → objective-c-D8mPZ42Y.js} +0 -0
- /package/dist/web/assets/{objective-cpp-eVtjUvVB.js → objective-cpp-B0pNXzuj.js} +0 -0
- /package/dist/web/assets/{ocaml-Bi6ZwqG4.js → ocaml-lQd7aK5d.js} +0 -0
- /package/dist/web/assets/{odin-8xf1Y0S9.js → odin-CxtIOBjm.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-hVv-_y_b.js → one-dark-pro-T_UFnWfe.js} +0 -0
- /package/dist/web/assets/{one-light-CJJ3MtHt.js → one-light-LBVqwyV-.js} +0 -0
- /package/dist/web/assets/{openscad-B2HcywLk.js → openscad-C5dnS6J7.js} +0 -0
- /package/dist/web/assets/{pascal-C7fllV_7.js → pascal-CmWCWQML.js} +0 -0
- /package/dist/web/assets/{pkl-qvLC1IWP.js → pkl-BWWOtDQk.js} +0 -0
- /package/dist/web/assets/{plastic-BT1wHZv2.js → plastic-ekhupckF.js} +0 -0
- /package/dist/web/assets/{play-4MZi1Axp.js → play-CpCAoQRZ.js} +0 -0
- /package/dist/web/assets/{plsql-1MsmdmJD.js → plsql-BPsDHXx4.js} +0 -0
- /package/dist/web/assets/{plus-BhMZqFsN.js → plus-BR0qewJI.js} +0 -0
- /package/dist/web/assets/{po-BigskytD.js → po-BX2FiuW2.js} +0 -0
- /package/dist/web/assets/{poimandres-DuklniF7.js → poimandres-DzXOdZcN.js} +0 -0
- /package/dist/web/assets/{polar-e2pjN_sp.js → polar-DDRXgJzG.js} +0 -0
- /package/dist/web/assets/{portal-container-context-Cm5HipFr.js → portal-container-context-a399Hd9F.js} +0 -0
- /package/dist/web/assets/{postcss-DtAeYjWw.js → postcss-BHW6XBhx.js} +0 -0
- /package/dist/web/assets/{powerquery-Cr2PtehI.js → powerquery-B89c2NBb.js} +0 -0
- /package/dist/web/assets/{powershell-BxFTGLJ4.js → powershell-Dvxrd3Tu.js} +0 -0
- /package/dist/web/assets/{prisma-COWJiwdZ.js → prisma-lMK8EFLU.js} +0 -0
- /package/dist/web/assets/{prolog-DFvGKRx6.js → prolog-D0YrtMG4.js} +0 -0
- /package/dist/web/assets/{proto-CcK80lFt.js → proto-J0mM25aw.js} +0 -0
- /package/dist/web/assets/{puppet-BF60bZ4F.js → puppet-CUT0xkhh.js} +0 -0
- /package/dist/web/assets/{purescript-zL_69MzU.js → purescript-CoJWSGRp.js} +0 -0
- /package/dist/web/assets/{python-CIwKhfb_.js → python-BBrmuz6P.js} +0 -0
- /package/dist/web/assets/{qmldir-CXNe04Lo.js → qmldir-CUtzs4Q7.js} +0 -0
- /package/dist/web/assets/{qss-C8lAWe6G.js → qss-Dm1RZ8aL.js} +0 -0
- /package/dist/web/assets/{r-CL5ZVL3_.js → r-BGP7qZTe.js} +0 -0
- /package/dist/web/assets/{racket-BBfh0uh0.js → racket-K_6U1lqJ.js} +0 -0
- /package/dist/web/assets/{raku-o6E8R5Q3.js → raku-bNSiZeW_.js} +0 -0
- /package/dist/web/assets/{react-CBTnT4sY.js → react-X4jUntEf.js} +0 -0
- /package/dist/web/assets/{red-GomRF2Xp.js → red-uVeIEF0f.js} +0 -0
- /package/dist/web/assets/{refresh-cw-B06yaBrW.js → refresh-cw-B9iv7csb.js} +0 -0
- /package/dist/web/assets/{reg-_ffDIvx9.js → reg-DSm9GWjh.js} +0 -0
- /package/dist/web/assets/{regexp-Bl5sF65W.js → regexp-CljRm_j_.js} +0 -0
- /package/dist/web/assets/{rel-DgMdqDuj.js → rel-d3JcopZg.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CRt_7wpd.js → resolve-theme-DLKmm-lE.js} +0 -0
- /package/dist/web/assets/{riscv-8b3-Ye4X.js → riscv-CJmaELjT.js} +0 -0
- /package/dist/web/assets/{ron-DrmU1KHP.js → ron-D0g9B1rW.js} +0 -0
- /package/dist/web/assets/{rose-pine-C_-E2tF1.js → rose-pine-DOrD2BSH.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-DEfhNlFH.js → rose-pine-dawn-CzZQ-LCF.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-BdvX3gdn.js → rose-pine-moon-CdmsYp7C.js} +0 -0
- /package/dist/web/assets/{rosmsg-B6GhGpd5.js → rosmsg-CmDth6pO.js} +0 -0
- /package/dist/web/assets/{rotate-ccw-COXu_9oj.js → rotate-ccw-DnuPzJRE.js} +0 -0
- /package/dist/web/assets/{rust-DmC97M1-.js → rust-DGdP6jiR.js} +0 -0
- /package/dist/web/assets/{sass-B0tJOHLM.js → sass-Dkgt2FKZ.js} +0 -0
- /package/dist/web/assets/{save-CtIGOjmx.js → save-DxonD4y1.js} +0 -0
- /package/dist/web/assets/{scala-n8zaFOXv.js → scala-BQLTON6C.js} +0 -0
- /package/dist/web/assets/{scheme-BKD9ztBU.js → scheme-DKlxUGCi.js} +0 -0
- /package/dist/web/assets/{sdbl-D8gB5-rS.js → sdbl-C3iZJQeZ.js} +0 -0
- /package/dist/web/assets/{send-BlyNJAzy.js → send-B-t5Mz26.js} +0 -0
- /package/dist/web/assets/{shallow-DYo6goJ4.js → shallow-jwPzEunk.js} +0 -0
- /package/dist/web/assets/{shellscript-BfXlf_Ao.js → shellscript-BRu-ZQAx.js} +0 -0
- /package/dist/web/assets/{shield-check-B0iGUiOK.js → shield-check-CdTiK1j2.js} +0 -0
- /package/dist/web/assets/{shield-off-BNLWBhcq.js → shield-off-DuetkXKM.js} +0 -0
- /package/dist/web/assets/{slack-dark-CylEddse.js → slack-dark-qkm_u2zz.js} +0 -0
- /package/dist/web/assets/{slack-ochin-w8wg8vuC.js → slack-ochin-BP8_8e4m.js} +0 -0
- /package/dist/web/assets/{smalltalk-hiAXA64u.js → smalltalk-Bnz4gt07.js} +0 -0
- /package/dist/web/assets/{snazzy-light-BwlV3Xty.js → snazzy-light-fbPZg5io.js} +0 -0
- /package/dist/web/assets/{solarized-dark-vmNGdykz.js → solarized-dark-CM0vTRoi.js} +0 -0
- /package/dist/web/assets/{solarized-light-DGv_0JXq.js → solarized-light-C4_W9mvs.js} +0 -0
- /package/dist/web/assets/{solidity-Y7QSH0B1.js → solidity-Bz6v1PoD.js} +0 -0
- /package/dist/web/assets/{splunk-BtgpLjHi.js → splunk-DyNxu-0i.js} +0 -0
- /package/dist/web/assets/{sql-CRHYhOia.js → sql-DXRL1gw1.js} +0 -0
- /package/dist/web/assets/{ssh-config-DOqSs3au.js → ssh-config-2z1QHuOG.js} +0 -0
- /package/dist/web/assets/{stylus-crCAEaBT.js → stylus-D4I-VaeY.js} +0 -0
- /package/dist/web/assets/{swift-eE3rT7TC.js → swift-C_2G-tIc.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BWy14LBX.js → synthwave-84-osBjChRP.js} +0 -0
- /package/dist/web/assets/{system-verilog-DC0-DZM4.js → system-verilog-zQP2qPNU.js} +0 -0
- /package/dist/web/assets/{systemd-to5p6N8I.js → systemd-UGfjRSBM.js} +0 -0
- /package/dist/web/assets/{table-Bbc783ko.js → table-FLU8ei8b.js} +0 -0
- /package/dist/web/assets/{talonscript-DoTcU8wm.js → talonscript-C1XrFPwo.js} +0 -0
- /package/dist/web/assets/{tasl-cNZvUs95.js → tasl-B5raVn43.js} +0 -0
- /package/dist/web/assets/{tcl-mZE8sZH8.js → tcl-3-vbeMIc.js} +0 -0
- /package/dist/web/assets/{terraform-CqHPpIfu.js → terraform-a8VXZDm9.js} +0 -0
- /package/dist/web/assets/{text-wrap-CBT4Gxtg.js → text-wrap-B05VGRa-.js} +0 -0
- /package/dist/web/assets/{tokyo-night-Tp2rTIZd.js → tokyo-night-4SRhf-Hz.js} +0 -0
- /package/dist/web/assets/{toml-CU_vscgn.js → toml-B4N0i8wT.js} +0 -0
- /package/dist/web/assets/{trash-2-Bk4yYlck.js → trash-2-CTXlTflF.js} +0 -0
- /package/dist/web/assets/{triangle-alert-B8EYecpe.js → triangle-alert-BRq0VAfd.js} +0 -0
- /package/dist/web/assets/{tsv-ik7iFB9D.js → tsv-DA5k9Lvw.js} +0 -0
- /package/dist/web/assets/{tsx-BMvvMKiv.js → tsx-D9SZ8owC.js} +0 -0
- /package/dist/web/assets/{turtle-DBqKCq9o.js → turtle-BTHTvyY_.js} +0 -0
- /package/dist/web/assets/{typescript-DXJDmMZZ.js → typescript-DYzV9p5L.js} +0 -0
- /package/dist/web/assets/{typespec-CBseCMtZ.js → typespec-BC15eh7F.js} +0 -0
- /package/dist/web/assets/{typst-4oY25U9y.js → typst-BJUqzA6D.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-D1weanbj.js → use-is-mobile-DFWViA4S.js} +0 -0
- /package/dist/web/assets/{utils-BNRH507E.js → utils-C9OjCrou.js} +0 -0
- /package/dist/web/assets/{v-BXaZ_1oy.js → v-50Lps6oA.js} +0 -0
- /package/dist/web/assets/{vala-BImWJE7z.js → vala-8wiUvvrD.js} +0 -0
- /package/dist/web/assets/{vb-CLfj2_sy.js → vb-BlgPLt6B.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-_yyu5n32.js → vendor-xterm-BZOrwF_F.js} +0 -0
- /package/dist/web/assets/{verilog-BWa8Ed2b.js → verilog-CD9pVW5p.js} +0 -0
- /package/dist/web/assets/{vesper-BGj1cg6m.js → vesper-BrivW4PT.js} +0 -0
- /package/dist/web/assets/{vhdl-mByK7mlx.js → vhdl-Bunxfa7N.js} +0 -0
- /package/dist/web/assets/{viml-CugmlW5g.js → viml-DndUb2qM.js} +0 -0
- /package/dist/web/assets/{vitesse-black-vPdpy9TL.js → vitesse-black-BurZSkxW.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuQK3dUh.js → vitesse-dark-8UShspzC.js} +0 -0
- /package/dist/web/assets/{vitesse-light-B3nz1XL9.js → vitesse-light-DghCAZ90.js} +0 -0
- /package/dist/web/assets/{vyper-DH_GTIHG.js → vyper-b-Clr-8u.js} +0 -0
- /package/dist/web/assets/{wasm-DfVbVJlW.js → wasm-C5v2T06Y.js} +0 -0
- /package/dist/web/assets/{wasm-C-LF9GjX.js → wasm-CpcKStkw.js} +0 -0
- /package/dist/web/assets/{wenyan-CzQkHyHx.js → wenyan-Dz2srLZR.js} +0 -0
- /package/dist/web/assets/{wgsl-Du_M1DW9.js → wgsl-CeD1-dRb.js} +0 -0
- /package/dist/web/assets/{wikitext-BWN_w5S7.js → wikitext-CyeS-9FH.js} +0 -0
- /package/dist/web/assets/{wit-BR13v5HD.js → wit-Kr-q-qBQ.js} +0 -0
- /package/dist/web/assets/{wolfram-BoXxp_Xl.js → wolfram-BKm8bmln.js} +0 -0
- /package/dist/web/assets/{x-nhriOX9o.js → x-YDu4m2Iq.js} +0 -0
- /package/dist/web/assets/{yaml-DpR_EF-d.js → yaml-2XSxomjM.js} +0 -0
- /package/dist/web/assets/{zenscript-C-EdUtUd.js → zenscript-CKIS34vf.js} +0 -0
- /package/dist/web/assets/{zig-BD4eA5nU.js → zig-DdHGNllX.js} +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# AI Chat & Providers
|
|
2
|
+
|
|
3
|
+
> Part of the [PPM system architecture](../system-architecture.md).
|
|
4
|
+
|
|
5
|
+
## Provider Layer (AI Adapters)
|
|
6
|
+
**Component:** Provider interface + implementations
|
|
7
|
+
|
|
8
|
+
**Responsibilities:**
|
|
9
|
+
- Abstract AI model differences behind common interface
|
|
10
|
+
- Stream responses as async generators
|
|
11
|
+
- Handle tool use and approval flows
|
|
12
|
+
- Track token usage
|
|
13
|
+
|
|
14
|
+
**Interface (src/providers/provider.interface.ts):**
|
|
15
|
+
```typescript
|
|
16
|
+
interface AIProvider {
|
|
17
|
+
createSession(): Promise<Session>;
|
|
18
|
+
sendMessage(sessionId: string, message: string, context?: FileContext[]): AsyncIterable<ChatEvent>;
|
|
19
|
+
onToolApproval(sessionId: string, requestId: string, approved: boolean, data?: unknown): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Implementations:**
|
|
24
|
+
- **claude-agent-sdk** (Primary) — @anthropic-ai/claude-agent-sdk, streaming, tool use. Reads model/effort/maxTurns/budget/thinking from config. Settings refreshed per query. Windows CLI fallback for Bun subprocess pipe issues. .env poisoning mitigation. **Multi-account support:** Injects account API token from AccountService instead of relying on ANTHROPIC_API_KEY env var when accounts configured.
|
|
25
|
+
- **mock-provider** (Testing) — Returns canned responses
|
|
26
|
+
- **cursor-cli** (CLI-based) — Spawns `cursor-agent` CLI binary with NDJSON streaming. Extends `CliProvider` base class.
|
|
27
|
+
- **codex/gemini** (Planned) — Pluggable via `CliProvider` extension (~100-150 lines each)
|
|
28
|
+
|
|
29
|
+
### Multi-Provider Architecture (v0.8.61+)
|
|
30
|
+
|
|
31
|
+
PPM supports multiple AI providers through a generic `AIProvider` interface and extensible base classes:
|
|
32
|
+
|
|
33
|
+
**Provider Types:**
|
|
34
|
+
1. **SDK-based** (claude-agent-sdk) — Uses Anthropic SDK for rich features (approvals, thinking blocks)
|
|
35
|
+
2. **CLI-based** (cursor-cli, codex, gemini) — Spawns external binary with NDJSON streaming
|
|
36
|
+
|
|
37
|
+
**Base Classes:**
|
|
38
|
+
- `AIProvider` interface — Defines required methods (createSession, sendMessage) + optional capabilities (abortQuery, getMessages, listSessionsByDir, ensureProjectPath)
|
|
39
|
+
- `CliProvider` abstract class — Shared spawn/parse/abort logic for all CLI-spawning providers
|
|
40
|
+
- Provider-specific subclasses implement: `buildArgs()`, `mapEvent()`, `extractSessionId()`, `isAvailable()`
|
|
41
|
+
|
|
42
|
+
**Streaming Infrastructure:**
|
|
43
|
+
- `parseNdjsonLines()` utility — Async generator that buffers partial TCP packets, yields complete JSON lines
|
|
44
|
+
- `ChatEvent` union type — Normalized event format across all providers (text, tool_use, thinking, approval_request, system, done, error)
|
|
45
|
+
- Event mappers translate provider-specific JSON → ChatEvent (e.g., Cursor's `reasoning` type → `thinking` event)
|
|
46
|
+
|
|
47
|
+
**Provider Registration & Bootstrap:**
|
|
48
|
+
- `ProviderRegistry` maintains active provider instances
|
|
49
|
+
- `bootstrapProviders()` async function checks `isAvailable()` on CLI providers before registering
|
|
50
|
+
- Graceful fallback: if Cursor binary not found, provider skips registration (no crash, logged as info)
|
|
51
|
+
- Config type `AIProviderConfig.type` union: `"agent-sdk" | "cli" | "mock"`
|
|
52
|
+
|
|
53
|
+
**CLI-Provider Features:**
|
|
54
|
+
- **Session capture** — Extract session ID from provider's init event, re-key process tracking
|
|
55
|
+
- **Workspace trust auto-retry** — Detect trust prompts in stderr, retry once with `--trust` flag
|
|
56
|
+
- **Process lifecycle** — Track active processes per session, escalate SIGTERM → SIGKILL on abort
|
|
57
|
+
- **History loading** — Override `listSessions()` to read native provider history (e.g., Cursor SQLite DAG)
|
|
58
|
+
- **Graceful degradation** — Missing binary → provider skipped, not fatal
|
|
59
|
+
|
|
60
|
+
**New Files (v0.8.61):**
|
|
61
|
+
- `src/utils/ndjson-line-parser.ts` — NDJSON streaming parser
|
|
62
|
+
- `src/providers/cli-provider-base.ts` — Abstract base class for CLI providers
|
|
63
|
+
- `src/providers/cursor-cli/cursor-provider.ts` — CursorCliProvider implementation
|
|
64
|
+
- `src/providers/cursor-cli/cursor-event-mapper.ts` — NDJSON → ChatEvent mapping
|
|
65
|
+
- `src/providers/cursor-cli/cursor-history.ts` — SQLite DAG reader for Cursor history
|
|
66
|
+
- `src/web/components/chat/provider-selector.tsx` — UI component for provider selection
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## AI Provider Configuration
|
|
71
|
+
|
|
72
|
+
PPM exposes AI settings as global configuration (not per-session) via REST API and Settings UI. Configuration is stored in SQLite (`~/.ppm/ppm.db`) and read fresh per query.
|
|
73
|
+
|
|
74
|
+
### Configuration Shape
|
|
75
|
+
|
|
76
|
+
Stored as dotted keys in the `config` table; shown here as a tree for readability. The authoritative
|
|
77
|
+
shape is `DEFAULT_CONFIG` / `PpmConfig` in `src/types/config.ts`.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
ai.default_provider claude
|
|
81
|
+
ai.providers.claude.type agent-sdk
|
|
82
|
+
ai.providers.claude.api_key_env ANTHROPIC_API_KEY
|
|
83
|
+
ai.providers.claude.model claude-opus-5
|
|
84
|
+
ai.providers.claude.effort high
|
|
85
|
+
ai.providers.claude.max_turns 1000
|
|
86
|
+
ai.providers.claude.permission_mode bypassPermissions
|
|
87
|
+
ai.providers.claude.inherit_claude_mcp true
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Fields:**
|
|
91
|
+
- `default_provider`: Active provider id (`claude`, `codex`, `cursor`). Falls back to `claude` when the configured id matches no registered provider
|
|
92
|
+
- `type`: Provider type (`agent-sdk` or `mock`)
|
|
93
|
+
- `api_key_env`: Environment variable holding the API key. Not required when the `claude` CLI is logged in
|
|
94
|
+
- `model`: Model ID (e.g. `claude-opus-5`, `claude-sonnet-5`). Default: `claude-opus-5`
|
|
95
|
+
- `effort`: Reasoning level — `low`, `medium`, `high`, `xhigh`, `max` (validated against `VALID_EFFORTS`; an out-of-enum value is rejected rather than passed through)
|
|
96
|
+
- `max_turns`: Maximum interaction turns. Default 1000
|
|
97
|
+
- `permission_mode`: SDK permission mode, default `bypassPermissions`
|
|
98
|
+
- `inherit_claude_mcp`: Reuse MCP servers configured for Claude Code
|
|
99
|
+
- `max_budget_usd`: Spending limit in USD (optional)
|
|
100
|
+
- `thinking_budget_tokens`: Extended thinking, tri-state (optional). Omitted = adaptive (model picks depth, guided by `effort`); `0` = disabled; a positive number = fixed token budget. Per-session Thinking toggle overrides this.
|
|
101
|
+
|
|
102
|
+
### API Endpoints
|
|
103
|
+
|
|
104
|
+
**GET /api/settings/ai** — Fetch current AI config
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"ok": true,
|
|
108
|
+
"data": {
|
|
109
|
+
"default_provider": "claude",
|
|
110
|
+
"providers": { "claude": {...} }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**PUT /api/settings/ai** — Update AI config (shallow merge per provider)
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"providers": {
|
|
119
|
+
"claude": {
|
|
120
|
+
"model": "claude-opus-5",
|
|
121
|
+
"max_turns": 50
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
Returns full updated config. Validates ranges/enums before writing.
|
|
127
|
+
|
|
128
|
+
### How Provider Uses Settings
|
|
129
|
+
|
|
130
|
+
1. **SDK Provider (`sendMessage`)**
|
|
131
|
+
- Calls `getProviderConfig()` to read fresh config from `configService`
|
|
132
|
+
- Maps snake_case config to camelCase SDK options
|
|
133
|
+
- Passes `model`, `effort`, `maxTurns`, `maxBudgetUsd`, `thinkingBudgetTokens` to `query()`
|
|
134
|
+
- Falls back to defaults if fields not set
|
|
135
|
+
|
|
136
|
+
2. **Mock Provider**
|
|
137
|
+
- Ignores AI settings (always returns canned responses for testing)
|
|
138
|
+
|
|
139
|
+
3. **Changes Take Effect**
|
|
140
|
+
- Immediately on next query (config read fresh each time)
|
|
141
|
+
- No active queries affected (config mid-flight not re-evaluated)
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Chat Streaming Flow (Persistent AsyncGenerator Sessions)
|
|
146
|
+
|
|
147
|
+
### Architecture Overview (v0.8.55+)
|
|
148
|
+
|
|
149
|
+
PPM uses a **persistent streaming session** model instead of per-message query execution:
|
|
150
|
+
|
|
151
|
+
**Key Changes:**
|
|
152
|
+
- Provider maintains **long-lived AsyncGenerator streaming input** per chat session (not per message)
|
|
153
|
+
- Follow-up messages **push into the existing generator** instead of abort-and-replace
|
|
154
|
+
- **Single streaming loop** per session decoupled from WebSocket message handler
|
|
155
|
+
- Message priority support: `now` (interrupt current), `next` (queue first), `later` (queue at end)
|
|
156
|
+
- Supports image attachments in messages
|
|
157
|
+
|
|
158
|
+
**Design Benefits:**
|
|
159
|
+
- Continuous context preservation — multi-turn conversations flow naturally
|
|
160
|
+
- No SDK subprocess restarts between messages (faster)
|
|
161
|
+
- Clean separation: BE owns Claude connection, FE disconnect doesn't abort
|
|
162
|
+
- Message buffering on reconnect — clients that lose WS connection sync turn events
|
|
163
|
+
- Tool approvals don't restart the query — integrated into streaming loop
|
|
164
|
+
|
|
165
|
+
### Message Flow
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
User types: "Debug this function"
|
|
169
|
+
↓
|
|
170
|
+
MessageInput.tsx calls useChat.sendMessage()
|
|
171
|
+
↓
|
|
172
|
+
useChat opens WebSocket: WS /ws/project/:name/chat/:sessionId
|
|
173
|
+
↓
|
|
174
|
+
Sends: { type: "message", content: "Debug...", priority?: "now"|"next"|"later" }
|
|
175
|
+
↓
|
|
176
|
+
WS handler in chat.ts receives message
|
|
177
|
+
↓
|
|
178
|
+
If already streaming with different content → abort previous + wait cleanup
|
|
179
|
+
If streaming, new message priority determines queue behavior:
|
|
180
|
+
• priority: "now" → abort current, restart with new content
|
|
181
|
+
• priority: "next" → push into pending queue (higher priority)
|
|
182
|
+
• priority: "later" → push to end of queue (FIFO)
|
|
183
|
+
↓
|
|
184
|
+
runStreamLoop() executes in detached async context
|
|
185
|
+
↓
|
|
186
|
+
ChatService calls provider.sendMessage() (async generator)
|
|
187
|
+
↓
|
|
188
|
+
Provider (Claude SDK) yields events:
|
|
189
|
+
1. { type: "text", content: "Here's what..." }
|
|
190
|
+
2. { type: "text", content: " happens..." }
|
|
191
|
+
3. { type: "tool_use", tool: "read_file", input: {...} }
|
|
192
|
+
↓
|
|
193
|
+
Stream loop buffers + broadcasts to all connected clients:
|
|
194
|
+
{ type: "text", content: "Here's what..." }
|
|
195
|
+
{ type: "text", content: " happens..." }
|
|
196
|
+
{ type: "tool_use", tool: "read_file", input: {...} }
|
|
197
|
+
{ type: "approval_request", requestId, tool, input }
|
|
198
|
+
↓
|
|
199
|
+
Client receives, displays message incrementally
|
|
200
|
+
↓
|
|
201
|
+
User sees tool approval prompt, clicks "Approve"
|
|
202
|
+
↓
|
|
203
|
+
Client sends: { type: "approval_response", requestId, approved: true }
|
|
204
|
+
↓
|
|
205
|
+
Provider continues streaming with tool result (no restart)
|
|
206
|
+
↓
|
|
207
|
+
If multiple messages queued, next message processes after done event
|
|
208
|
+
↓
|
|
209
|
+
Final response streamed, then: { type: "done", sessionId }
|
|
210
|
+
↓
|
|
211
|
+
Phase transitions to idle, clients can send new message
|
|
212
|
+
↓
|
|
213
|
+
useChat saves message to store, displays in chat history
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Session State Management
|
|
217
|
+
|
|
218
|
+
**Session Entry** (BE-owned, persists across FE disconnections):
|
|
219
|
+
```typescript
|
|
220
|
+
interface SessionEntry {
|
|
221
|
+
providerId: string; // Which AI provider (e.g., "claude")
|
|
222
|
+
clients: Set<ChatWsSocket>; // Connected FE clients (may be empty)
|
|
223
|
+
abort?: AbortController; // Current stream abort handle
|
|
224
|
+
projectPath?: string; // Project context
|
|
225
|
+
projectName?: string;
|
|
226
|
+
pingIntervals: Map<...>; // Per-client keepalive
|
|
227
|
+
phase: SessionPhase; // "initializing" | "connecting" | "thinking" | "streaming" | "idle"
|
|
228
|
+
cleanupTimer?: ReturnType<...>; // Auto-cleanup if no FE reconnects (5min)
|
|
229
|
+
pendingApprovalEvent?: {...}; // Current tool approval waiting
|
|
230
|
+
turnEvents: unknown[]; // Buffered events (for reconnect sync)
|
|
231
|
+
streamPromise?: Promise<void>; // Track ongoing runStreamLoop
|
|
232
|
+
permissionMode?: string; // Sticky permission mode for session
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Client Connection States:**
|
|
237
|
+
- **Active streaming + FE connected** → Events broadcast to all clients in real-time
|
|
238
|
+
- **Active streaming + FE disconnected** → Events buffered in turnEvents array, BE stream continues
|
|
239
|
+
- **FE reconnects** → Receive session_state + buffered turnEvents, resync with stream
|
|
240
|
+
- **Idle (no query running)** → Phase is "idle", ready for next message
|
|
241
|
+
- **Idle + no FE for 5min** → Cleanup timer removes session from memory
|
|
242
|
+
|
|
243
|
+
### Follow-up Messages
|
|
244
|
+
|
|
245
|
+
**Abort-and-Replace Pattern:**
|
|
246
|
+
```typescript
|
|
247
|
+
if (entry.phase !== "idle" && entry.abort) {
|
|
248
|
+
console.log(`[chat] aborting current query for new message`);
|
|
249
|
+
entry.abort.abort();
|
|
250
|
+
await entry.streamPromise; // Wait for cleanup
|
|
251
|
+
// Re-fetch entry — may have been mutated during cleanup
|
|
252
|
+
entry = activeSessions.get(sessionId)!;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Multiple Message Queueing:**
|
|
257
|
+
- First message: immediately starts runStreamLoop
|
|
258
|
+
- Second message (while streaming): abort current, wait, start new runStreamLoop
|
|
259
|
+
- Priority modes (future): could queue messages for intelligent interleaving
|
|
260
|
+
|
|
261
|
+
### WebSocket Reconnection Sync
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
FE WebSocket closes (network issue, tab closes)
|
|
265
|
+
↓
|
|
266
|
+
BE keeps session alive, streaming continues
|
|
267
|
+
↓
|
|
268
|
+
FE reconnects: WS /ws/project/:name/chat/:sessionId
|
|
269
|
+
↓
|
|
270
|
+
open() handler checks activeSessions.get(sessionId)
|
|
271
|
+
↓
|
|
272
|
+
If exists (entry found):
|
|
273
|
+
1. Clear cleanup timer (FE is back)
|
|
274
|
+
2. Send session_state with current phase + pendingApproval
|
|
275
|
+
3. If phase !== "idle", send buffered turnEvents
|
|
276
|
+
4. Add WS to clients Set
|
|
277
|
+
↓
|
|
278
|
+
FE processes session_state, renders current phase
|
|
279
|
+
↓
|
|
280
|
+
FE applies buffered events to rebuild turn state
|
|
281
|
+
↓
|
|
282
|
+
FE displays: "reconnected, current phase: streaming" etc.
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Phase Transitions
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
idle → initializing → connecting → thinking/streaming ↔ thinking/streaming → idle
|
|
289
|
+
^ ↑ ↓
|
|
290
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Phase Descriptions:**
|
|
294
|
+
- **idle** — No query running, ready to accept new message
|
|
295
|
+
- **initializing** — Preparing (permission checks, session resume)
|
|
296
|
+
- **connecting** — Waiting for first SDK event (heartbeat: "connecting" with elapsed time every 5s)
|
|
297
|
+
- **thinking** — Receiving thinking content (extended thinking)
|
|
298
|
+
- **streaming** — Receiving text/tool_use content (dynamic switch between thinking/streaming)
|
|
299
|
+
|
|
300
|
+
### Image Attachment Support
|
|
301
|
+
|
|
302
|
+
Messages can now include images:
|
|
303
|
+
```typescript
|
|
304
|
+
type ChatWsClientMessage =
|
|
305
|
+
| { type: "message"; content: string; images?: { id: string; data: string }[]; priority?: string }
|
|
306
|
+
| ...
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Images are passed to provider's message context and included in tool input/output.
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# Data & Storage
|
|
2
|
+
|
|
3
|
+
> Part of the [PPM system architecture](../system-architecture.md).
|
|
4
|
+
|
|
5
|
+
## Group-Chat Data Model (schema v35)
|
|
6
|
+
|
|
7
|
+
Three tables back the native group-chat engine; the message bus is a single
|
|
8
|
+
table keyed by `kind` + JSON `data` (spike-validated) with a monotonic `seq`
|
|
9
|
+
PK for stable ordering, and is the durable source of truth across Stop/Resume:
|
|
10
|
+
|
|
11
|
+
- `chat_groups(id, project_name, project_path, name, leader_session_id, status[active|paused|idle], max_turns=40, max_cost_usd=5.0, created_at)`
|
|
12
|
+
- `chat_group_members(id, group_id→chat_groups, role[leader|member], persona, agent_type, model, session_id, name, color, status, joined_at)`
|
|
13
|
+
- `chat_group_messages(seq PK, id, group_id→chat_groups, from_member, to_member, kind[task|chat|status|completion|final], summary, full_session_ref, data JSON, turn_index, created_at)`
|
|
14
|
+
|
|
15
|
+
Flow: user message → engine runs sequential @mention-driven turns over the bus →
|
|
16
|
+
converges to one `final` → member transcripts archived (Option A+). Stop aborts
|
|
17
|
+
mid-turn (cooperative) and pauses; Resume re-spawns fresh sessions and re-enters
|
|
18
|
+
the loop seeded from the bus (windowed + rolling summary).
|
|
19
|
+
| **TagService** | Session tagging CRUD, bulk operations, tag-session enrichment | seedDefaultTags, getTagsByProject, createTag, updateTag, deleteTag, setSessionTag, bulkSetSessionTag, getSessionTags, getTagSessionCounts |
|
|
20
|
+
| **DraftService** | Chat draft auto-save per session, 50KB cap | get, upsert, delete, deleteOrphaned |
|
|
21
|
+
| **FileFilterService** | Glob pattern matching + precedence-enforced filtering (hardcoded ⊂ global ⊂ project) | mergeFilters, isPathIgnored, matchesPattern |
|
|
22
|
+
| **SystemMetricsService** (`src/services/system-metrics/`) | Whole-machine Task Manager backend: CPU per core + RAM via `node:os`, disk/net/GPU + all processes via per-OS collectors (Linux `/proc`, macOS `ps`, Windows one long-lived PowerShell REPL child, `Win32_Process` + `PerfRawData` per 2 s tick), delta-based CPU%, grouping by app root, aggregate-only 30-min history. Two SSE tiers on `/api/system/resources/stream`: `light` (status bar, no children spawned) and `full` (`?processes=1`, demand-gated collectors, 60 s teardown). Subscriber lease (sid + 10 s ping, 30 s expiry) because Cloudflare tunnel never propagates client disconnects. Guarded `POST /resources/kill`: protected set (PPM server/supervisor/edge/cloudflared, OS-critical names), ancestor/tree-intersection rule, `startedAt` identity re-query → 409, JSON + `X-PPM-Request` header. | subscribe, unsubscribe, ping, getLatest, kill, reapExpired |
|
|
23
|
+
|
|
24
|
+
**Key Files:** `src/services/*.service.ts`, `src/services/tag.service.ts`, `src/services/ppmbot/*.ts`, `src/services/bash-output-spy.ts`, `src/services/system-metrics/system-metrics.service.ts`, `src/services/system-metrics/kill-guard.ts`, `src/services/system-metrics/powershell-session.ts`, `src/services/redact-secrets.ts`, `src/services/file-filter.service.ts`, `src/cli/commands/bot-cmd.ts`
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Database Management (v2.0+)
|
|
29
|
+
|
|
30
|
+
### Architecture Overview
|
|
31
|
+
|
|
32
|
+
PPM now supports managing external databases (SQLite & PostgreSQL) through a unified adapter pattern:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ Web UI (React) │
|
|
37
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
38
|
+
│ │ Database Sidebar │ │
|
|
39
|
+
│ │ • Connection List (with color badges) │ │
|
|
40
|
+
│ │ • Create/Edit Connection Form │ │
|
|
41
|
+
│ │ • Color Picker (WCAG contrast-aware) │ │
|
|
42
|
+
│ │ • Query Execution UI │ │
|
|
43
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
44
|
+
└─────────────────┬───────────────────────────────────────────────┘
|
|
45
|
+
│ HTTP REST / WebSocket
|
|
46
|
+
┌─────────────────┴───────────────────────────────────────────────┐
|
|
47
|
+
│ PPM Server (Hono) │
|
|
48
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
49
|
+
│ │ /api/db Routes │ │
|
|
50
|
+
│ │ • GET /connections → List all connections │ │
|
|
51
|
+
│ │ • POST /connections → Create connection │ │
|
|
52
|
+
│ │ • GET /connections/:id → Get connection (sanitized) │ │
|
|
53
|
+
│ │ • PUT /connections/:id → Update (readonly toggle) │ │
|
|
54
|
+
│ │ • DELETE /connections/:id → Remove connection │ │
|
|
55
|
+
│ │ • GET /connections/:id/tables → List + sync tables │ │
|
|
56
|
+
│ │ • GET /connections/:id/tables/:tbl → Schema + data │ │
|
|
57
|
+
│ │ • POST /connections/:id/query → Execute query │ │
|
|
58
|
+
│ │ • PATCH /connections/:id/cell → Update cell │ │
|
|
59
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
60
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
61
|
+
│ │ Service Layer │ │
|
|
62
|
+
│ │ • DbService (connection CRUD, caching) │ │
|
|
63
|
+
│ │ • TableCacheService (metadata cache, search) │ │
|
|
64
|
+
│ │ • DatabaseAdapterRegistry (extensible) │ │
|
|
65
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
66
|
+
│ ┌──────────────────────────────────────────────────────────┐ │
|
|
67
|
+
│ │ Adapters (Pluggable Pattern) │ │
|
|
68
|
+
│ │ • SQLiteAdapter → Uses `bun:sqlite` for local files │ │
|
|
69
|
+
│ │ • PostgresAdapter → Uses postgres driver for servers │ │
|
|
70
|
+
│ │ • isReadOnlyQuery() → Safety check (CTE-safe regex) │ │
|
|
71
|
+
│ │ • readonly=1 by default (safe-by-default) │ │
|
|
72
|
+
│ └──────────────────────────────────────────────────────────┘ │
|
|
73
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
74
|
+
↓↑
|
|
75
|
+
┌────────────────────────────────────────────┐
|
|
76
|
+
│ External Databases │
|
|
77
|
+
│ • SQLite files (path: /path/to/db.db) │
|
|
78
|
+
│ • PostgreSQL servers (connStr: postgres://)│
|
|
79
|
+
└────────────────────────────────────────────┘
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### DatabaseAdapter Pattern (Extensible)
|
|
83
|
+
|
|
84
|
+
**Interface** (`src/types/database.ts`):
|
|
85
|
+
```typescript
|
|
86
|
+
interface DatabaseAdapter {
|
|
87
|
+
testConnection(config: DbConnectionConfig): Promise<{ ok: boolean; error?: string }>;
|
|
88
|
+
getTables(config: DbConnectionConfig): Promise<DbTableInfo[]>;
|
|
89
|
+
getTableSchema(config: DbConnectionConfig, table: string, schema?: string): Promise<DbColumnInfo[]>;
|
|
90
|
+
getTableData(config: DbConnectionConfig, table: string, opts: {...}): Promise<DbPagedData>;
|
|
91
|
+
executeQuery(config: DbConnectionConfig, sql: string): Promise<DbQueryResult>;
|
|
92
|
+
updateCell(config: DbConnectionConfig, table: string, opts: {...}): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Implementations:**
|
|
97
|
+
1. **SQLiteAdapter** — Local file-based SQLite via `bun:sqlite`
|
|
98
|
+
- testConnection: Opens file, runs pragma check
|
|
99
|
+
- Supports: SELECT, INSERT, UPDATE, DELETE (if writable), CREATE TABLE
|
|
100
|
+
|
|
101
|
+
2. **PostgresAdapter** — Remote PostgreSQL servers via postgres driver
|
|
102
|
+
- testConnection: Attempts connection with credentials
|
|
103
|
+
- Supports: Full SQL except DDL on readonly connections
|
|
104
|
+
|
|
105
|
+
**Registry Pattern** (`src/services/database/adapter-registry.ts`):
|
|
106
|
+
```typescript
|
|
107
|
+
registerAdapter("sqlite", new SQLiteAdapter());
|
|
108
|
+
registerAdapter("postgres", new PostgresAdapter());
|
|
109
|
+
// Can be extended: registerAdapter("mysql", new MysqlAdapter());
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Security Design
|
|
113
|
+
|
|
114
|
+
**Readonly by Default:**
|
|
115
|
+
- All connections created with `readonly = true` in database
|
|
116
|
+
- Default: read-only query execution (safe-by-default)
|
|
117
|
+
- Web UI toggle: Switch to writable (admin decision only)
|
|
118
|
+
- CLI: Cannot disable readonly via command-line (browser only)
|
|
119
|
+
|
|
120
|
+
**Readonly Query Detection:**
|
|
121
|
+
```typescript
|
|
122
|
+
// isReadOnlyQuery() in src/services/database/readonly-check.ts
|
|
123
|
+
// Checks for: SELECT, PRAGMA, EXPLAIN, WITH (CTE)
|
|
124
|
+
// Rejects: INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, etc.
|
|
125
|
+
// CTE-safe: Handles "WITH AS SELECT" (wraps CTE result check)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Credential Handling:**
|
|
129
|
+
- Connection credentials stored in SQLite `connections` table as `connection_config` JSON
|
|
130
|
+
- **NEVER** returned in API responses (stripped by `sanitizeConn()` in routes)
|
|
131
|
+
- Only used internally by adapters when executing queries
|
|
132
|
+
- Frontend never sees passwords/connection strings
|
|
133
|
+
|
|
134
|
+
**API Security:**
|
|
135
|
+
- All `/api/db` requests require valid auth token (middleware checked)
|
|
136
|
+
- Connection IDs are numeric (no enumeration risk)
|
|
137
|
+
- Connection color is user-specific (cosmetic only, not sensitive)
|
|
138
|
+
|
|
139
|
+
### Data Flow: Query Execution
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
User opens Database tab
|
|
143
|
+
↓
|
|
144
|
+
DatabaseSidebar fetches: GET /api/db/connections
|
|
145
|
+
↓
|
|
146
|
+
ConnectionList displays (sanitized, no credentials)
|
|
147
|
+
↓
|
|
148
|
+
User clicks connection → GET /api/db/connections/:id/tables
|
|
149
|
+
↓
|
|
150
|
+
DbService.getConnections() reads from SQLite
|
|
151
|
+
↓
|
|
152
|
+
TableCacheService.syncTables() calls adapter.getTables()
|
|
153
|
+
↓
|
|
154
|
+
SQLiteAdapter/PostgresAdapter queries database
|
|
155
|
+
↓
|
|
156
|
+
Results cached in table_metadata table
|
|
157
|
+
↓
|
|
158
|
+
UI displays table list + schema
|
|
159
|
+
↓
|
|
160
|
+
User selects table → GET /api/db/connections/:id/tables/:table
|
|
161
|
+
↓
|
|
162
|
+
Adapter.getTableData() executes paginated query
|
|
163
|
+
↓
|
|
164
|
+
Results returned: { columns, rows, total, page, limit }
|
|
165
|
+
↓
|
|
166
|
+
UI renders table grid with pagination
|
|
167
|
+
↓
|
|
168
|
+
User executes custom query → POST /api/db/connections/:id/query
|
|
169
|
+
↓
|
|
170
|
+
isReadOnlyQuery() checks SQL (rejects writes if readonly=true)
|
|
171
|
+
↓
|
|
172
|
+
Adapter.executeQuery() runs SQL
|
|
173
|
+
↓
|
|
174
|
+
Results returned: { columns, rows, rowsAffected, changeType }
|
|
175
|
+
↓
|
|
176
|
+
UI displays results (read-only highlight if mutation was blocked)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Connection Storage
|
|
180
|
+
|
|
181
|
+
**SQLite Schema** (in `~/.ppm/ppm.db`):
|
|
182
|
+
```sql
|
|
183
|
+
CREATE TABLE accounts (
|
|
184
|
+
id TEXT PRIMARY KEY,
|
|
185
|
+
account_name TEXT NOT NULL,
|
|
186
|
+
encrypted_api_key TEXT NOT NULL,
|
|
187
|
+
priority INTEGER DEFAULT 0,
|
|
188
|
+
is_active INTEGER DEFAULT 0, -- 1 = active, 0 = inactive
|
|
189
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
190
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
CREATE TABLE connections (
|
|
194
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
195
|
+
type TEXT NOT NULL, -- 'sqlite' | 'postgres'
|
|
196
|
+
name TEXT NOT NULL,
|
|
197
|
+
connection_config TEXT NOT NULL, -- JSON: { path, connectionString, ... }
|
|
198
|
+
readonly INTEGER DEFAULT 1, -- 1 = readonly, 0 = writable (UI-only toggle)
|
|
199
|
+
group_name TEXT,
|
|
200
|
+
color TEXT, -- Optional hex color (#3b82f6)
|
|
201
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
202
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
CREATE TABLE table_metadata (
|
|
206
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
207
|
+
connection_id INTEGER NOT NULL REFERENCES connections(id) ON DELETE CASCADE,
|
|
208
|
+
table_name TEXT NOT NULL,
|
|
209
|
+
schema_name TEXT DEFAULT 'public',
|
|
210
|
+
row_count INTEGER,
|
|
211
|
+
last_synced TEXT,
|
|
212
|
+
UNIQUE(connection_id, table_name, schema_name)
|
|
213
|
+
);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### CLI Support (ppm db)
|
|
217
|
+
|
|
218
|
+
**Commands** (`src/cli/commands/db-cmd.ts`):
|
|
219
|
+
```bash
|
|
220
|
+
ppm db connections # List all connections
|
|
221
|
+
ppm db connect # Add new connection (interactive)
|
|
222
|
+
ppm db remove <name> # Delete connection
|
|
223
|
+
ppm db query <name> <sql> # Execute query (respects readonly)
|
|
224
|
+
ppm db run <name> <file> # Execute SQL file (multi-statement, transactions)
|
|
225
|
+
ppm db tables <name> # List tables
|
|
226
|
+
ppm db schema <name> <table> # Show table schema
|
|
227
|
+
ppm db data <name> <table> # Show table data (paginated)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**CLI Safety:**
|
|
231
|
+
- Always respects readonly flag (cannot override via CLI)
|
|
232
|
+
- Uses same adapter/validation as web UI
|
|
233
|
+
- Table formatting for terminal output
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## MCP Server Management
|
|
238
|
+
|
|
239
|
+
### Overview
|
|
240
|
+
MCP (Model Context Protocol) servers extend Claude with custom tools and resources. PPM manages MCP server configurations via Settings UI, storing them in SQLite and passing them to the Claude Agent SDK.
|
|
241
|
+
|
|
242
|
+
**Features:**
|
|
243
|
+
- **Add/Edit/Delete** MCP servers via Settings UI
|
|
244
|
+
- **Auto-import** from `~/.claude.json` on first access (convenience, no forced import)
|
|
245
|
+
- **Three transport types:** stdio, HTTP, SSE
|
|
246
|
+
- **Validation** on name and config before storage
|
|
247
|
+
- **SDK integration:** Servers passed to `query()` as `mcpServers` object, tools auto-allowed via `mcp__*` wildcard
|
|
248
|
+
|
|
249
|
+
### Storage Schema
|
|
250
|
+
|
|
251
|
+
```sql
|
|
252
|
+
CREATE TABLE mcp_servers (
|
|
253
|
+
name TEXT PRIMARY KEY,
|
|
254
|
+
transport TEXT NOT NULL DEFAULT 'stdio', -- 'stdio' | 'http' | 'sse'
|
|
255
|
+
config TEXT NOT NULL, -- JSON: McpServerConfig
|
|
256
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
257
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
258
|
+
);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Config Format (JSON):**
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"type": "stdio",
|
|
265
|
+
"command": "path/to/server",
|
|
266
|
+
"args": ["--flag"],
|
|
267
|
+
"env": { "VAR": "value" }
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Or HTTP/SSE:
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"type": "http",
|
|
275
|
+
"url": "http://localhost:3000",
|
|
276
|
+
"headers": { "Authorization": "Bearer token" }
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### REST API
|
|
281
|
+
|
|
282
|
+
**Endpoints** (`src/server/routes/mcp.ts`):
|
|
283
|
+
|
|
284
|
+
| Method | Endpoint | Description |
|
|
285
|
+
|--------|----------|-------------|
|
|
286
|
+
| **GET** | `/api/settings/mcp` | List all servers; auto-import on first access |
|
|
287
|
+
| **GET** | `/api/settings/mcp/:name` | Get single server config |
|
|
288
|
+
| **POST** | `/api/settings/mcp` | Add new server (validates name + config) |
|
|
289
|
+
| **PUT** | `/api/settings/mcp/:name` | Update existing server |
|
|
290
|
+
| **DELETE** | `/api/settings/mcp/:name` | Remove server |
|
|
291
|
+
| **GET** | `/api/settings/mcp/import/preview` | Preview servers in `~/.claude.json` |
|
|
292
|
+
| **POST** | `/api/settings/mcp/import` | Bulk import from `~/.claude.json` |
|
|
293
|
+
|
|
294
|
+
**Add Server Example:**
|
|
295
|
+
```bash
|
|
296
|
+
POST /api/settings/mcp
|
|
297
|
+
Content-Type: application/json
|
|
298
|
+
|
|
299
|
+
{
|
|
300
|
+
"name": "file-server",
|
|
301
|
+
"config": {
|
|
302
|
+
"type": "stdio",
|
|
303
|
+
"command": "/usr/local/bin/file-server",
|
|
304
|
+
"args": ["--port", "8000"]
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Service Layer
|
|
310
|
+
|
|
311
|
+
**McpConfigService** (`src/services/mcp-config.service.ts`):
|
|
312
|
+
- `list()` — Record<name, McpServerConfig> (SDK-compatible format)
|
|
313
|
+
- `listWithMeta()` — Array with metadata (for UI)
|
|
314
|
+
- `get(name)` — Single server config
|
|
315
|
+
- `set(name, config)` — Add or update (upsert)
|
|
316
|
+
- `remove(name)` — Delete server
|
|
317
|
+
- `exists(name)` — Check if name exists
|
|
318
|
+
- `bulkImport(servers)` — Transactional import from `~/.claude.json`, skips existing/invalid
|
|
319
|
+
|
|
320
|
+
**Validation:**
|
|
321
|
+
- `validateMcpName(name)` — alphanumeric + hyphens/underscores, max 50 chars
|
|
322
|
+
- `validateMcpConfig(config)` — type-specific checks (command for stdio, url for http/sse)
|
|
323
|
+
|
|
324
|
+
### Frontend Integration
|
|
325
|
+
|
|
326
|
+
**UI Components:**
|
|
327
|
+
- `MCP Settings Section` (`src/web/components/settings/mcp-settings-section.tsx`) — Tab in Settings UI
|
|
328
|
+
- `MCP Server Dialog` (`src/web/components/settings/mcp-server-dialog.tsx`) — Add/Edit modal
|
|
329
|
+
- `API client` (`src/web/lib/api-mcp.ts`) — Fetch/mutate operations
|
|
330
|
+
|
|
331
|
+
**Workflow:**
|
|
332
|
+
1. User opens Settings → MCP tab
|
|
333
|
+
2. **GET** `/api/settings/mcp` (auto-imports on first access)
|
|
334
|
+
3. Display list with transport badge + actions (edit, delete)
|
|
335
|
+
4. Click "Add" → Dialog with name + transport selector + config fields
|
|
336
|
+
5. **POST** to `/api/settings/mcp` or **PUT** to update
|
|
337
|
+
6. On success, list refreshes
|
|
338
|
+
|
|
339
|
+
### SDK Integration
|
|
340
|
+
|
|
341
|
+
**Claude Agent SDK Provider** (`src/providers/claude-agent-sdk.ts`):
|
|
342
|
+
```typescript
|
|
343
|
+
// Line ~574
|
|
344
|
+
const mcpServers = mcpConfigService.list();
|
|
345
|
+
const hasMcp = Object.keys(mcpServers).length > 0;
|
|
346
|
+
|
|
347
|
+
// Line ~589: Pass to query() if servers exist
|
|
348
|
+
const mcpTools = ["mcp__*"];
|
|
349
|
+
const queryConfig = {
|
|
350
|
+
// ... other options
|
|
351
|
+
...(hasMcp && { mcpServers }),
|
|
352
|
+
allowedTools: [...otherTools, ...mcpTools],
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const query = new Query(messages, queryConfig);
|
|
356
|
+
```
|