@hienlh/ppm 0.8.87 → 0.8.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/CHANGELOG.md +75 -40
  2. package/dist/web/assets/{_basePickBy-5PGDJbfF.js → _basePickBy-3Xe18azI.js} +1 -1
  3. package/dist/web/assets/{_baseUniq-BT4Ow4Kk.js → _baseUniq-Yy35llnn.js} +1 -1
  4. package/dist/web/assets/api-settings-Bid0NHuI.js +1 -0
  5. package/dist/web/assets/{arc-BAOivWpI.js → arc-B9n1Gvb5.js} +1 -1
  6. package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +1 -0
  7. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DWBCPMLF.js → architectureDiagram-2XIMDMQ5-DqAZP_F6.js} +1 -1
  8. package/dist/web/assets/{blockDiagram-WCTKOSBZ-TEF8Ally.js → blockDiagram-WCTKOSBZ-h3cDF2vI.js} +1 -1
  9. package/dist/web/assets/{browser-tab-DaHGm_0i.js → browser-tab-DSWumOSG.js} +1 -1
  10. package/dist/web/assets/{c4Diagram-IC4MRINW-dV22iAsY.js → c4Diagram-IC4MRINW--pF1r5lr.js} +1 -1
  11. package/dist/web/assets/channel-C2fMafck.js +1 -0
  12. package/dist/web/assets/chat-tab-Ccwf-c6M.js +8 -0
  13. package/dist/web/assets/{chunk-4BX2VUAB-D4tOov49.js → chunk-4BX2VUAB-C3aZvW7B.js} +1 -1
  14. package/dist/web/assets/{chunk-55IACEB6-DJ6BynZ4.js → chunk-55IACEB6-D5cABeB9.js} +1 -1
  15. package/dist/web/assets/{chunk-7E7YKBS2-CiyUJxNI.js → chunk-7E7YKBS2-CkFGv6Zs.js} +1 -1
  16. package/dist/web/assets/{chunk-7R4GIKGN-BbIFzsIv.js → chunk-7R4GIKGN-Dvbyu4Zw.js} +2 -2
  17. package/dist/web/assets/{chunk-C72U2L5F-D21mS_6G.js → chunk-C72U2L5F-CtqKiH4q.js} +1 -1
  18. package/dist/web/assets/{chunk-EGIJ26TM-DzqmU2Z7.js → chunk-EGIJ26TM-Cpr87sBR.js} +1 -1
  19. package/dist/web/assets/{chunk-FMBD7UC4-DXncblvW.js → chunk-FMBD7UC4-D23YVTOU.js} +1 -1
  20. package/dist/web/assets/{chunk-GEFDOKGD-BbQkJu8C.js → chunk-GEFDOKGD-tDjHsAUs.js} +1 -1
  21. package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +2 -0
  22. package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +1 -0
  23. package/dist/web/assets/{chunk-JSJVCQXG-23tyvw8k.js → chunk-JSJVCQXG-BBmymCjA.js} +1 -1
  24. package/dist/web/assets/{chunk-KX2RTZJC-sQ0o-39C.js → chunk-KX2RTZJC-DP36BDiU.js} +1 -1
  25. package/dist/web/assets/{chunk-KYZI473N-BcUZNnwd.js → chunk-KYZI473N-Djw13C-3.js} +1 -1
  26. package/dist/web/assets/{chunk-L3YUKLVL-C7qGJrfV.js → chunk-L3YUKLVL-HG_eMj_C.js} +1 -1
  27. package/dist/web/assets/{chunk-MX3YWQON-BpS_PtKp.js → chunk-MX3YWQON-C2UEioMs.js} +1 -1
  28. package/dist/web/assets/{chunk-NQ4KR5QH-wMgTlP7f.js → chunk-NQ4KR5QH-DXUTQ-BL.js} +1 -1
  29. package/dist/web/assets/{chunk-O4XLMI2P-JC6EGoUz.js → chunk-O4XLMI2P-BsUWb9d0.js} +1 -1
  30. package/dist/web/assets/{chunk-OZEHJAEY-BXhYx3nO.js → chunk-OZEHJAEY-rG0P22U9.js} +1 -1
  31. package/dist/web/assets/{chunk-PQ6SQG4A-D6BTbCQw.js → chunk-PQ6SQG4A-DX0xW7kO.js} +1 -1
  32. package/dist/web/assets/{chunk-PU5JKC2W-Dw8ClWch.js → chunk-PU5JKC2W-C7Gry6md.js} +1 -1
  33. package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +1 -0
  34. package/dist/web/assets/{chunk-R5LLSJPH-CFwSJijQ.js → chunk-R5LLSJPH-CMY0PkRK.js} +1 -1
  35. package/dist/web/assets/{chunk-WL4C6EOR-DfofndiH.js → chunk-WL4C6EOR-CXuQvlyu.js} +1 -1
  36. package/dist/web/assets/{chunk-XIRO2GV7-Djlmrely.js → chunk-XIRO2GV7-DRJEb7Zb.js} +1 -1
  37. package/dist/web/assets/{chunk-XPW4576I-BPQQBakK.js → chunk-XPW4576I-BPEX8KhL.js} +1 -1
  38. package/dist/web/assets/{chunk-XZSTWKYB-DxAOx4hG.js → chunk-XZSTWKYB-Cb0iqycX.js} +1 -1
  39. package/dist/web/assets/{chunk-YBOYWFTD-CeU4Q-xC.js → chunk-YBOYWFTD-av5aeHLq.js} +1 -1
  40. package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +1 -0
  41. package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +1 -0
  42. package/dist/web/assets/clone-B2hUek6n.js +1 -0
  43. package/dist/web/assets/code-editor-DLTcPb55.js +2 -0
  44. package/dist/web/assets/{cose-bilkent-S5V4N54A-B_AWZsOP.js → cose-bilkent-S5V4N54A-qudEiMCT.js} +1 -1
  45. package/dist/web/assets/{csv-preview-DLqYtXxt.js → csv-preview-DUbHtTAS.js} +1 -1
  46. package/dist/web/assets/{dagre-Dbb5k38K.js → dagre-BFcnKyBF.js} +1 -1
  47. package/dist/web/assets/{dagre-KLK3FWXG-BH7aWGRP.js → dagre-KLK3FWXG-C3O-MTLf.js} +1 -1
  48. package/dist/web/assets/{database-viewer-DXk79Nel.js → database-viewer-BrpPlYG7.js} +1 -1
  49. package/dist/web/assets/{diagram-E7M64L7V-B1Qz70Do.js → diagram-E7M64L7V-DxPjK7_c.js} +1 -1
  50. package/dist/web/assets/{diagram-IFDJBPK2-k55eVqVU.js → diagram-IFDJBPK2-sqTog_XV.js} +1 -1
  51. package/dist/web/assets/{diagram-P4PSJMXO-BkfNRc9U.js → diagram-P4PSJMXO-hzmp0GHK.js} +1 -1
  52. package/dist/web/assets/diff-viewer-Dx96kcTu.js +4 -0
  53. package/dist/web/assets/{erDiagram-INFDFZHY-CKzVujYI.js → erDiagram-INFDFZHY-DLeYhAAT.js} +1 -1
  54. package/dist/web/assets/{flowDiagram-PKNHOUZH-DIqcTrDV.js → flowDiagram-PKNHOUZH-CRxlE9Sr.js} +1 -1
  55. package/dist/web/assets/{ganttDiagram-A5KZAMGK-D4v7ZbVE.js → ganttDiagram-A5KZAMGK-BdjmoMLS.js} +1 -1
  56. package/dist/web/assets/git-graph-CoN6voTp.js +1 -0
  57. package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +1 -0
  58. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BTXo57mF.js → gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js} +1 -1
  59. package/dist/web/assets/{graphlib-BcsNnGcW.js → graphlib-Duh_bWLa.js} +1 -1
  60. package/dist/web/assets/index-CtbNK_ih.css +2 -0
  61. package/dist/web/assets/index-DRdx_Wqn.js +37 -0
  62. package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +1 -0
  63. package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +2 -0
  64. package/dist/web/assets/{isEmpty-bnrF3Qbc.js → isEmpty-B9L-Ge-H.js} +1 -1
  65. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-BOyvKMmB.js → ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js} +1 -1
  66. package/dist/web/assets/{journeyDiagram-4ABVD52K-ufoasAy6.js → journeyDiagram-4ABVD52K-CgDI-UG4.js} +1 -1
  67. package/dist/web/assets/{kanban-definition-K7BYSVSG-Bi0UTUeN.js → kanban-definition-K7BYSVSG-h4g10UHL.js} +1 -1
  68. package/dist/web/assets/keybindings-store-DHGoLYnP.js +1 -0
  69. package/dist/web/assets/{line-B78g-52T.js → line-B75-Rx70.js} +1 -1
  70. package/dist/web/assets/{linear-DP4mkX3m.js → linear-Bcjv9FQt.js} +1 -1
  71. package/dist/web/assets/{markdown-renderer-Brj8_LQM.js → markdown-renderer-BqsXIW9n.js} +5 -5
  72. package/dist/web/assets/{mermaid-parser.core-DMIWdgEW.js → mermaid-parser.core-8u2leTXI.js} +2 -2
  73. package/dist/web/assets/{mindmap-definition-YRQLILUH-BsfWvIoO.js → mindmap-definition-YRQLILUH-BaOBwb-W.js} +1 -1
  74. package/dist/web/assets/{ordinal-_K3x1fkz.js → ordinal-LFEjVtwQ.js} +1 -1
  75. package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +1 -0
  76. package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +1 -0
  77. package/dist/web/assets/{pieDiagram-SKSYHLDU-WP0XXw51.js → pieDiagram-SKSYHLDU-At5Kz0KK.js} +1 -1
  78. package/dist/web/assets/{postgres-viewer-CwkTGmqy.js → postgres-viewer-Lw8xaGfc.js} +1 -1
  79. package/dist/web/assets/{quadrantDiagram-337W2JSQ-FHMogtsh.js → quadrantDiagram-337W2JSQ-CdjGIDfw.js} +1 -1
  80. package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +1 -0
  81. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-BatTxyWb.js → requirementDiagram-Z7DCOOCP-B9F_Cx_p.js} +1 -1
  82. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-ClJuW3Hv.js → sankeyDiagram-WA2Y5GQK-RolPi8bU.js} +1 -1
  83. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-ByxQqGgs.js → sequenceDiagram-2WXFIKYE-DM-tMAhx.js} +1 -1
  84. package/dist/web/assets/settings-tab-DDCC58we.js +1 -0
  85. package/dist/web/assets/{sqlite-viewer-CFYTwgA8.js → sqlite-viewer-DECA802J.js} +1 -1
  86. package/dist/web/assets/{stateDiagram-RAJIS63D-f8opcZNY.js → stateDiagram-RAJIS63D-C4EMl6jf.js} +1 -1
  87. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +1 -0
  88. package/dist/web/assets/{tab-store-BJw7OCmy.js → tab-store--SlERlDs.js} +1 -1
  89. package/dist/web/assets/{terminal-tab-CCDLZA5Y.js → terminal-tab-DneNM6WP.js} +2 -2
  90. package/dist/web/assets/{timeline-definition-YZTLITO2-58BlOSf9.js → timeline-definition-YZTLITO2-A4PN_Efm.js} +1 -1
  91. package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +1 -0
  92. package/dist/web/assets/{use-monaco-theme-CNzekTN3.js → use-monaco-theme-CrtYAJMR.js} +1 -1
  93. package/dist/web/assets/{vennDiagram-LZ73GAT5-BOSy9ma9.js → vennDiagram-LZ73GAT5-ywK7LMaH.js} +1 -1
  94. package/dist/web/assets/{xychartDiagram-JWTSCODW-z5MVJauZ.js → xychartDiagram-JWTSCODW-DylHYNtJ.js} +1 -1
  95. package/dist/web/index.html +10 -11
  96. package/dist/web/sw.js +1 -1
  97. package/docs/code-standards.md +155 -0
  98. package/docs/codebase-summary.md +261 -95
  99. package/docs/project-changelog.md +38 -3
  100. package/docs/project-roadmap.md +2 -2
  101. package/docs/streaming-input-guide.md +267 -0
  102. package/docs/system-architecture.md +151 -0
  103. package/package.json +1 -1
  104. package/snapshot-state.md +1526 -0
  105. package/src/providers/claude-agent-sdk.ts +244 -102
  106. package/src/providers/cli-provider-base.ts +238 -0
  107. package/src/providers/cursor-cli/cursor-event-mapper.ts +85 -0
  108. package/src/providers/cursor-cli/cursor-history.ts +207 -0
  109. package/src/providers/cursor-cli/cursor-provider.ts +146 -0
  110. package/src/providers/mock-provider.ts +1 -1
  111. package/src/providers/provider.interface.ts +1 -0
  112. package/src/providers/registry.ts +43 -4
  113. package/src/server/index.ts +6 -0
  114. package/src/server/routes/chat.ts +14 -3
  115. package/src/server/routes/mcp.ts +84 -0
  116. package/src/server/routes/settings.ts +14 -0
  117. package/src/server/ws/chat.ts +127 -81
  118. package/src/services/account.service.ts +2 -2
  119. package/src/services/chat.service.ts +10 -15
  120. package/src/services/claude-usage.service.ts +2 -7
  121. package/src/services/db.service.ts +8 -0
  122. package/src/services/mcp-config.service.ts +111 -0
  123. package/src/types/api.ts +1 -1
  124. package/src/types/chat.ts +23 -2
  125. package/src/types/config.ts +33 -11
  126. package/src/types/mcp.ts +47 -0
  127. package/src/utils/ndjson-line-parser.ts +36 -0
  128. package/src/web/components/chat/chat-history-bar.tsx +48 -29
  129. package/src/web/components/chat/chat-tab.tsx +29 -24
  130. package/src/web/components/chat/message-input.tsx +64 -5
  131. package/src/web/components/chat/provider-selector.tsx +150 -0
  132. package/src/web/components/chat/session-picker.tsx +3 -1
  133. package/src/web/components/chat/usage-badge.tsx +58 -8
  134. package/src/web/components/settings/ai-settings-section.tsx +196 -137
  135. package/src/web/components/settings/mcp-server-dialog.tsx +208 -0
  136. package/src/web/components/settings/mcp-settings-section.tsx +143 -0
  137. package/src/web/components/settings/settings-tab.tsx +5 -2
  138. package/src/web/hooks/use-chat.ts +32 -15
  139. package/src/web/lib/api-mcp.ts +38 -0
  140. package/test-tokens.mjs +212 -0
  141. package/.claude.bak/agent-memory/tester/MEMORY.md +0 -3
  142. package/.claude.bak/agent-memory/tester/project-ppm-test-conventions.md +0 -32
  143. package/dist/web/assets/api-settings-Bx1GaNmQ.js +0 -1
  144. package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +0 -1
  145. package/dist/web/assets/channel-wrd-NHWf.js +0 -1
  146. package/dist/web/assets/chat-tab-BDYE0KHF.js +0 -8
  147. package/dist/web/assets/chunk-GLR3WWYH-CzYx4w-r.js +0 -2
  148. package/dist/web/assets/chunk-HHEYEP7N-HRhYy3kG.js +0 -1
  149. package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +0 -1
  150. package/dist/web/assets/classDiagram-VBA2DB6C-lse8oZoJ.js +0 -1
  151. package/dist/web/assets/classDiagram-v2-RAHNMMFH-CxkwuInd.js +0 -1
  152. package/dist/web/assets/clone-LRxlvnMj.js +0 -1
  153. package/dist/web/assets/code-editor-DTA3c9Y8.js +0 -2
  154. package/dist/web/assets/diff-viewer-HhIcsOQE.js +0 -4
  155. package/dist/web/assets/git-graph-CQtWu8yE.js +0 -1
  156. package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +0 -1
  157. package/dist/web/assets/index-CgQXpBb_.css +0 -2
  158. package/dist/web/assets/index-DEeeRoka.js +0 -37
  159. package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +0 -1
  160. package/dist/web/assets/infoDiagram-LFFYTUFH-B1CX0pbC.js +0 -2
  161. package/dist/web/assets/input-BglMT33g.js +0 -1
  162. package/dist/web/assets/keybindings-store-1CJ7VX57.js +0 -1
  163. package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +0 -1
  164. package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +0 -1
  165. package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +0 -1
  166. package/dist/web/assets/settings-tab-BDE1MsIh.js +0 -1
  167. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DrxVDY9q.js +0 -1
  168. package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +0 -1
  169. /package/dist/web/assets/{api-client-BfBM3I7n.js → api-client-BKIT_Qeg.js} +0 -0
  170. /package/dist/web/assets/{array-B9UHiPd-.js → array-DqLCdDFv.js} +0 -0
  171. /package/dist/web/assets/{chevron-right-DeV0ehiG.js → chevron-right-CHnjJt4E.js} +0 -0
  172. /package/dist/web/assets/{columns-2-DpsNbZOc.js → columns-2-DbesTfa7.js} +0 -0
  173. /package/dist/web/assets/{cytoscape.esm-BW-DbntU.js → cytoscape.esm-CWPXKqbJ.js} +0 -0
  174. /package/dist/web/assets/{defaultLocale-5eAKkKJC.js → defaultLocale-CrJzLgRD.js} +0 -0
  175. /package/dist/web/assets/{dist-lF8CoYII.js → dist-CALwEtco.js} +0 -0
  176. /package/dist/web/assets/{dist-CSJdAyA9.js → dist-Cep75xXf.js} +0 -0
  177. /package/dist/web/assets/{dist-DylI9XxN.js → dist-DGDPTxs1.js} +0 -0
  178. /package/dist/web/assets/{init-DlZdxViB.js → init-C0r9Gk5G.js} +0 -0
  179. /package/dist/web/assets/{isArrayLikeObject-B_v2FtYn.js → isArrayLikeObject-CGBoxvCD.js} +0 -0
  180. /package/dist/web/assets/{katex-Bqvo_ZG0.js → katex-DzXRfQ_m.js} +0 -0
  181. /package/dist/web/assets/{lib-BQ34Db2e.js → lib-BeaDXEkP.js} +0 -0
  182. /package/dist/web/assets/{math-069Z4SuC.js → math-y9zN1W-N.js} +0 -0
  183. /package/dist/web/assets/{path-6uRLdFF7.js → path-DIKpVbHL.js} +0 -0
  184. /package/dist/web/assets/{preload-helper-uTix4PVD.js → preload-helper-Bf_JiD2A.js} +0 -0
  185. /package/dist/web/assets/{react-ER-4DN55.js → react-SKk5z-bm.js} +0 -0
  186. /package/dist/web/assets/{rough.esm-JX0wREDd.js → rough.esm-nHaDi0Kw.js} +0 -0
  187. /package/dist/web/assets/{src-BqX54PbV.js → src-Dw4QhedI.js} +0 -0
  188. /package/dist/web/assets/{table-C7X5UAEI.js → table-CQVQM2SB.js} +0 -0
  189. /package/dist/web/assets/{tag-CCtdV063.js → tag-Q2dZiSPX.js} +0 -0
  190. /package/dist/web/assets/{utils-BNytJOb1.js → utils-DMiycH3O.js} +0 -0
@@ -0,0 +1,267 @@
1
+ # Streaming Input Migration Quick Reference (v0.8.55+)
2
+
3
+ ## What Changed?
4
+
5
+ **Before (v0.8.54):** Each message triggered a new SDK query
6
+ ```
7
+ Message 1 → SDK subprocess spawn → generate response → close
8
+ Message 2 → SDK subprocess spawn → generate response → close
9
+ (Slow, context resets between messages)
10
+ ```
11
+
12
+ **After (v0.8.55):** Single persistent streaming session
13
+ ```
14
+ Session created → AsyncGenerator streaming input opened
15
+ Message 1 → Push into generator → process events
16
+ Message 2 → Push into same generator → continue streaming
17
+ (Fast, continuous context, no SDK restarts)
18
+ ```
19
+
20
+ ## Key Concepts
21
+
22
+ ### Session State (BE-Owned)
23
+ The backend maintains a `SessionEntry` per chat session:
24
+ - Tracks connected clients (can be zero if FE disconnected)
25
+ - Maintains streaming phase (idle, connecting, thinking, streaming)
26
+ - Buffers events for reconnection sync
27
+ - Auto-cleans after 5 minutes of FE inactivity
28
+
29
+ ### Message Priority (v0.8.55+)
30
+ ```typescript
31
+ // Send message with priority
32
+ ws.send({
33
+ type: "message",
34
+ content: "Debug this code",
35
+ priority: "now" // "now" | "next" | "later"
36
+ })
37
+ ```
38
+ - **"now"** — Abort current query, restart with this message
39
+ - **"next"** — Queue after current, run next
40
+ - **"later"** — Append to queue, run last
41
+
42
+ ### Event Buffering on Reconnect
43
+ When FE WS reconnects after disconnect:
44
+ 1. BE sends `session_state` with current phase + pending approval
45
+ 2. BE sends `turn_events` with all buffered events since last connection
46
+ 3. FE rebuilds chat UI state from buffered events
47
+ 4. No message loss (unless session cleaned up after 5min)
48
+
49
+ ## Common Patterns
50
+
51
+ ### Frontend: Send Message
52
+ ```typescript
53
+ // In useChat hook or message input handler
54
+ ws.send(JSON.stringify({
55
+ type: "message",
56
+ content: userInput,
57
+ priority: "now", // Optional
58
+ images: [{ id: "img1", data: "base64..." }] // Optional
59
+ }));
60
+ ```
61
+
62
+ ### Frontend: Handle Reconnection
63
+ ```typescript
64
+ function handleReconnect() {
65
+ // 1. WS open fires
66
+ // 2. Server sends session_state
67
+ const sessionState = JSON.parse(msg);
68
+ // 3. Server sends turn_events
69
+ const turnEvents = JSON.parse(msg);
70
+
71
+ // 4. FE rebuilds state from buffered events
72
+ turnEvents.events.forEach(event => {
73
+ chatStore.addEvent(event);
74
+ });
75
+
76
+ // 5. FE is now synced with BE
77
+ }
78
+ ```
79
+
80
+ ### Backend: Session Lifecycle
81
+ ```typescript
82
+ // 1. FE connects
83
+ open(ws) {
84
+ const entry = activeSessions.get(sessionId);
85
+ if (!entry) {
86
+ // Create new session entry
87
+ activeSessions.set(sessionId, {
88
+ phase: "idle",
89
+ clients: new Set([ws]),
90
+ turnEvents: []
91
+ });
92
+ } else {
93
+ // Reconnect: clear cleanup timer, add client
94
+ entry.clients.add(ws);
95
+ }
96
+ }
97
+
98
+ // 2. FE sends message
99
+ message(ws, data) {
100
+ const parsed = JSON.parse(data);
101
+ if (parsed.type === "message") {
102
+ // Abort current if streaming, wait for cleanup
103
+ if (entry.phase !== "idle") {
104
+ entry.abort.abort();
105
+ await entry.streamPromise;
106
+ }
107
+ // Start new streaming loop (detached)
108
+ entry.streamPromise = runStreamLoop(...);
109
+ }
110
+ }
111
+
112
+ // 3. Streaming loop runs independently
113
+ async function runStreamLoop() {
114
+ for await (const event of chatService.sendMessage(...)) {
115
+ bufferAndBroadcast(sessionId, event); // To all connected clients
116
+ }
117
+ setPhase(sessionId, "idle"); // Back to idle when done
118
+ if (entry.clients.size === 0) {
119
+ startCleanupTimer(sessionId); // 5-min cleanup
120
+ }
121
+ }
122
+
123
+ // 4. FE disconnects
124
+ close(ws) {
125
+ entry.clients.delete(ws);
126
+ // Stream continues! (BE owns the connection)
127
+ // Timer started if no more clients
128
+ }
129
+ ```
130
+
131
+ ## Phase State Machine
132
+
133
+ ```
134
+ ┌─ initializing (setup, session resume)
135
+
136
+ idle ←→ connecting (waiting for first SDK event, heartbeat)
137
+ ↑ ↓
138
+ │ ┌──→ thinking (extended thinking)
139
+ │ ↓ ↓
140
+ └─── streaming (text/tool_use content)
141
+ ↑ ↓
142
+ └─────┘ (dynamic switch)
143
+ ```
144
+
145
+ **Transitions:**
146
+ - Heartbeat: `connecting` → (5s elapsed updates) → `thinking` (when content arrives)
147
+ - Content: `thinking` → `streaming` (first text event)
148
+ - Dynamic: `streaming` ↔ `thinking` (based on event types)
149
+ - Done: Any → `idle` (stream complete, ready for next message)
150
+
151
+ ## WebSocket Messages (v0.8.55+)
152
+
153
+ ### Client → Server
154
+ ```typescript
155
+ // Send message
156
+ { type: "message"; content: string; priority?: string; images?: {...}[] }
157
+
158
+ // Approve tool
159
+ { type: "approval_response"; requestId: string; approved: boolean }
160
+
161
+ // Cancel current
162
+ { type: "cancel" }
163
+
164
+ // Handshake after open
165
+ { type: "ready" }
166
+ ```
167
+
168
+ ### Server → Client
169
+ ```typescript
170
+ // Content
171
+ { type: "text"; content: string }
172
+ { type: "thinking"; content: string }
173
+
174
+ // Tool execution
175
+ { type: "tool_use"; tool: string; input: unknown }
176
+ { type: "tool_result"; output: string; isError?: boolean }
177
+
178
+ // User approval request
179
+ { type: "approval_request"; requestId: string; tool: string; input: unknown }
180
+
181
+ // Session state (sent on open/ready)
182
+ { type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: {...} | null }
183
+
184
+ // Buffered events (on reconnect)
185
+ { type: "turn_events"; events: unknown[] }
186
+
187
+ // Metadata
188
+ { type: "account_info"; accountId: string; accountLabel: string }
189
+ { type: "phase_changed"; phase: SessionPhase; elapsed?: number }
190
+ { type: "title_updated"; title: string }
191
+
192
+ // Completion
193
+ { type: "done"; sessionId: string; contextWindowPct?: number }
194
+
195
+ // Error
196
+ { type: "error"; message: string }
197
+
198
+ // Keepalive
199
+ { type: "ping" }
200
+ ```
201
+
202
+ ## Benefits
203
+
204
+ | Aspect | Before (v0.8.54) | After (v0.8.55) |
205
+ |--------|------------------|-----------------|
206
+ | **SDK Restarts** | Per message | Once per session |
207
+ | **Context** | Resets between messages | Persistent |
208
+ | **Startup Time** | 2-5s per message | Instant follow-ups |
209
+ | **Reconnection** | Message loss | Event buffering ensures sync |
210
+ | **Concurrency** | N/A | Multiple clients per session |
211
+ | **Tool Approvals** | Restarts query | Integrated in stream |
212
+
213
+ ## Troubleshooting
214
+
215
+ ### Session Cleaned Up (No Longer Exists)
216
+ **Cause:** FE disconnected for >5 minutes
217
+ **Solution:** Create new session, FE reconnects with new sessionId
218
+
219
+ ### Events Missing After Reconnect
220
+ **Cause:** Server-side event buffer (10k event limit) overflowed
221
+ **Solution:** Flush buffer periodically or increase limit if needed
222
+
223
+ ### Phase Stuck in "Connecting"
224
+ **Cause:** SDK subprocess not responding (120s timeout)
225
+ **Solution:** Check environment (ANTHROPIC_API_KEY, network), see error message for hints
226
+
227
+ ### Multiple Clients Out of Sync
228
+ **Cause:** Broadcast failed for one client, others ahead
229
+ **Solution:** Evicted client will reconnect and re-sync from buffered events
230
+
231
+ ## Debugging
232
+
233
+ ### Enable Logging
234
+ ```bash
235
+ # Check server logs for session lifecycle
236
+ [chat] session=abc123 phase → connecting
237
+ [chat] session=abc123 first SDK event after 1250ms: type=text
238
+ [chat] session=abc123 stream completed (45 events)
239
+ [chat] session=abc123 phase → idle
240
+ ```
241
+
242
+ ### Check Session State
243
+ ```typescript
244
+ // On WS message handler
245
+ console.log(`Session entry:`, activeSessions.get(sessionId));
246
+ // Outputs: { phase, clients.size, pendingApprovalEvent, turnEvents.length }
247
+ ```
248
+
249
+ ### Monitor Reconnections
250
+ ```typescript
251
+ // In WS open handler
252
+ console.log(`FE reconnected (phase=${existing.phase}, clients=${existing.clients.size})`);
253
+ // Tells you: active streaming, how many clients connected
254
+ ```
255
+
256
+ ## Performance Notes
257
+
258
+ - **No SDK overhead:** Persistent streaming eliminates subprocess spawn overhead
259
+ - **Event buffering:** Clients see all events after reconnect (max 10k events per turn)
260
+ - **Memory:** Session entries cleaned after 5min (bounded memory usage)
261
+ - **Latency:** Follow-up messages start immediately (no SDK init)
262
+
263
+ ---
264
+
265
+ **For detailed architecture:** See `docs/system-architecture.md` → "Chat Streaming Flow" section
266
+ **For API types:** See `src/types/api.ts` and `src/types/chat.ts`
267
+ **For implementation:** See `src/server/ws/chat.ts` and `src/providers/claude-agent-sdk.ts`
@@ -977,6 +977,157 @@ ppm db data <name> <table> # Show table data (paginated)
977
977
 
978
978
  ---
979
979
 
980
+ ## MCP Server Management
981
+
982
+ ### Overview
983
+ 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.
984
+
985
+ **Features:**
986
+ - **Add/Edit/Delete** MCP servers via Settings UI
987
+ - **Auto-import** from `~/.claude.json` on first access (convenience, no forced import)
988
+ - **Three transport types:** stdio, HTTP, SSE
989
+ - **Validation** on name and config before storage
990
+ - **SDK integration:** Servers passed to `query()` as `mcpServers` object, tools auto-allowed via `mcp__*` wildcard
991
+
992
+ ### Storage Schema
993
+
994
+ ```sql
995
+ CREATE TABLE mcp_servers (
996
+ name TEXT PRIMARY KEY,
997
+ transport TEXT NOT NULL DEFAULT 'stdio', -- 'stdio' | 'http' | 'sse'
998
+ config TEXT NOT NULL, -- JSON: McpServerConfig
999
+ created_at TEXT DEFAULT (datetime('now')),
1000
+ updated_at TEXT DEFAULT (datetime('now'))
1001
+ );
1002
+ ```
1003
+
1004
+ **Config Format (JSON):**
1005
+ ```json
1006
+ {
1007
+ "type": "stdio",
1008
+ "command": "path/to/server",
1009
+ "args": ["--flag"],
1010
+ "env": { "VAR": "value" }
1011
+ }
1012
+ ```
1013
+
1014
+ Or HTTP/SSE:
1015
+ ```json
1016
+ {
1017
+ "type": "http",
1018
+ "url": "http://localhost:3000",
1019
+ "headers": { "Authorization": "Bearer token" }
1020
+ }
1021
+ ```
1022
+
1023
+ ### REST API
1024
+
1025
+ **Endpoints** (`src/server/routes/mcp.ts`):
1026
+
1027
+ | Method | Endpoint | Description |
1028
+ |--------|----------|-------------|
1029
+ | **GET** | `/api/settings/mcp` | List all servers; auto-import on first access |
1030
+ | **GET** | `/api/settings/mcp/:name` | Get single server config |
1031
+ | **POST** | `/api/settings/mcp` | Add new server (validates name + config) |
1032
+ | **PUT** | `/api/settings/mcp/:name` | Update existing server |
1033
+ | **DELETE** | `/api/settings/mcp/:name` | Remove server |
1034
+ | **GET** | `/api/settings/mcp/import/preview` | Preview servers in `~/.claude.json` |
1035
+ | **POST** | `/api/settings/mcp/import` | Bulk import from `~/.claude.json` |
1036
+
1037
+ **Add Server Example:**
1038
+ ```bash
1039
+ POST /api/settings/mcp
1040
+ Content-Type: application/json
1041
+
1042
+ {
1043
+ "name": "file-server",
1044
+ "config": {
1045
+ "type": "stdio",
1046
+ "command": "/usr/local/bin/file-server",
1047
+ "args": ["--port", "8000"]
1048
+ }
1049
+ }
1050
+ ```
1051
+
1052
+ ### Service Layer
1053
+
1054
+ **McpConfigService** (`src/services/mcp-config.service.ts`):
1055
+ - `list()` — Record<name, McpServerConfig> (SDK-compatible format)
1056
+ - `listWithMeta()` — Array with metadata (for UI)
1057
+ - `get(name)` — Single server config
1058
+ - `set(name, config)` — Add or update (upsert)
1059
+ - `remove(name)` — Delete server
1060
+ - `exists(name)` — Check if name exists
1061
+ - `bulkImport(servers)` — Transactional import from `~/.claude.json`, skips existing/invalid
1062
+
1063
+ **Validation:**
1064
+ - `validateMcpName(name)` — alphanumeric + hyphens/underscores, max 50 chars
1065
+ - `validateMcpConfig(config)` — type-specific checks (command for stdio, url for http/sse)
1066
+
1067
+ ### Frontend Integration
1068
+
1069
+ **UI Components:**
1070
+ - `MCP Settings Section` (`src/web/components/settings/mcp-settings-section.tsx`) — Tab in Settings UI
1071
+ - `MCP Server Dialog` (`src/web/components/settings/mcp-server-dialog.tsx`) — Add/Edit modal
1072
+ - `API client` (`src/web/lib/api-mcp.ts`) — Fetch/mutate operations
1073
+
1074
+ **Workflow:**
1075
+ 1. User opens Settings → MCP tab
1076
+ 2. **GET** `/api/settings/mcp` (auto-imports on first access)
1077
+ 3. Display list with transport badge + actions (edit, delete)
1078
+ 4. Click "Add" → Dialog with name + transport selector + config fields
1079
+ 5. **POST** to `/api/settings/mcp` or **PUT** to update
1080
+ 6. On success, list refreshes
1081
+
1082
+ ### SDK Integration
1083
+
1084
+ **Claude Agent SDK Provider** (`src/providers/claude-agent-sdk.ts`):
1085
+ ```typescript
1086
+ // Line ~574
1087
+ const mcpServers = mcpConfigService.list();
1088
+ const hasMcp = Object.keys(mcpServers).length > 0;
1089
+
1090
+ // Line ~589: Pass to query() if servers exist
1091
+ const mcpTools = ["mcp__*"];
1092
+ const queryConfig = {
1093
+ // ... other options
1094
+ ...(hasMcp && { mcpServers }),
1095
+ allowedTools: [...otherTools, ...mcpTools],
1096
+ };
1097
+
1098
+ const query = new Query(messages, queryConfig);
1099
+ ```
1100
+
1101
+ **Tool Allow List:**
1102
+ - All MCP tools automatically allowed via wildcard `mcp__*`
1103
+ - MCP server connection failures don't block chat (logged as warning)
1104
+
1105
+ ### Import Flow
1106
+
1107
+ **Auto-import on first access:**
1108
+ 1. GET `/api/settings/mcp` called
1109
+ 2. If table is empty, read `~/.claude.json`
1110
+ 3. If `mcpServers` key exists, bulk import (validate + skip duplicates)
1111
+ 4. Return populated list
1112
+
1113
+ **Manual import:**
1114
+ 1. GET `/api/settings/mcp/import/preview` — show what's available
1115
+ 2. POST `/api/settings/mcp/import` — import validated servers
1116
+ 3. Returns `{ imported: N, skipped: M }`
1117
+
1118
+ ### Error Handling
1119
+
1120
+ | Scenario | Response |
1121
+ |----------|----------|
1122
+ | Invalid name (non-alphanumeric) | 400 Bad Request |
1123
+ | Invalid config (missing required fields) | 400 Bad Request |
1124
+ | Duplicate name | 409 Conflict |
1125
+ | Server not found (GET/:name, PUT/:name, DELETE/:name) | 404 Not Found |
1126
+ | `~/.claude.json` not found (import) | 404 Not Found |
1127
+ | Corrupt config JSON (recovery) | Log warning, skip entry, continue |
1128
+
1129
+ ---
1130
+
980
1131
  ## Deployment Architecture
981
1132
 
982
1133
  ### Single-Machine Deployment (Current)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.8.87",
3
+ "version": "0.8.89",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",