@hienlh/ppm 0.8.86 → 0.8.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +193 -5
- package/bun.lock +5 -0
- package/dist/web/assets/{_basePickBy-5eBmZ_lt.js → _basePickBy-5PGDJbfF.js} +1 -1
- package/dist/web/assets/{_baseUniq-DimLlN0y.js → _baseUniq-BT4Ow4Kk.js} +1 -1
- package/dist/web/assets/api-settings-Bx1GaNmQ.js +1 -0
- package/dist/web/assets/{arc-D4SasZrA.js → arc-BAOivWpI.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-nv0WbM7d.js → architectureDiagram-2XIMDMQ5-DWBCPMLF.js} +1 -1
- package/dist/web/assets/arrow-up--LjUXLEt.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-C1XvYrb8.js → blockDiagram-WCTKOSBZ-TEF8Ally.js} +1 -1
- package/dist/web/assets/browser-tab-DaHGm_0i.js +1 -0
- package/dist/web/assets/{c4Diagram-IC4MRINW-CygDrbWJ.js → c4Diagram-IC4MRINW-dV22iAsY.js} +1 -1
- package/dist/web/assets/channel-wrd-NHWf.js +1 -0
- package/dist/web/assets/chat-tab-BDYE0KHF.js +8 -0
- package/dist/web/assets/chevron-right-DeV0ehiG.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-C2FDgsgT.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-jF4w6cat.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-BVCECZFi.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-DXTbeu5d.js → chunk-7R4GIKGN-BbIFzsIv.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-BaZqOsTs.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-Bky2tcH7.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-Cp4BK9A8.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-BosFEH7G.js → chunk-GEFDOKGD-BbQkJu8C.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-CzYx4w-r.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-HRhYy3kG.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-H5Gbjsbr.js → chunk-JSJVCQXG-23tyvw8k.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-CWerSUwS.js → chunk-KX2RTZJC-sQ0o-39C.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-FvwP7jUy.js → chunk-KYZI473N-BcUZNnwd.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL-D1PI_ORP.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-C7Vzk_AI.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-BceYBGYX.js → chunk-NQ4KR5QH-wMgTlP7f.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-WPtzgxql.js → chunk-O4XLMI2P-JC6EGoUz.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-DlHXDeLY.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-Ci_Prygb.js → chunk-PQ6SQG4A-D6BTbCQw.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-CO0zMN-z.js → chunk-PU5JKC2W-Dw8ClWch.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-IAEEzfpM.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-BLXalOgc.js → chunk-WL4C6EOR-DfofndiH.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-Dx1Ri_p2.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-m9pPGKn7.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-B_08ExbI.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-DqSOVcYe.js → chunk-YBOYWFTD-CeU4Q-xC.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-lse8oZoJ.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-CxkwuInd.js +1 -0
- package/dist/web/assets/clone-LRxlvnMj.js +1 -0
- package/dist/web/assets/code-editor-DTA3c9Y8.js +2 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-DlL82QHu.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
- package/dist/web/assets/csv-preview-DLqYtXxt.js +10 -0
- package/dist/web/assets/{dagre-BmVoh2At.js → dagre-Dbb5k38K.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-sDrRW9MQ.js → dagre-KLK3FWXG-BH7aWGRP.js} +1 -1
- package/dist/web/assets/database-viewer-DXk79Nel.js +1 -0
- package/dist/web/assets/{diagram-E7M64L7V-ChnAhgni.js → diagram-E7M64L7V-B1Qz70Do.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-DW1J1uJd.js → diagram-IFDJBPK2-k55eVqVU.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-CQ32hyG_.js → diagram-P4PSJMXO-BkfNRc9U.js} +1 -1
- package/dist/web/assets/diff-viewer-HhIcsOQE.js +4 -0
- package/dist/web/assets/dist-DylI9XxN.js +13 -0
- package/dist/web/assets/dist-lF8CoYII.js +41 -0
- package/dist/web/assets/{erDiagram-INFDFZHY-6CHo6nOw.js → erDiagram-INFDFZHY-CKzVujYI.js} +1 -1
- package/dist/web/assets/{flowDiagram-PKNHOUZH-DroDiNT0.js → flowDiagram-PKNHOUZH-DIqcTrDV.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-DP0QBh8w.js → ganttDiagram-A5KZAMGK-D4v7ZbVE.js} +1 -1
- package/dist/web/assets/git-graph-CQtWu8yE.js +1 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-DvU3JGZn.js → gitGraphDiagram-K3NZZRJ6-BTXo57mF.js} +1 -1
- package/dist/web/assets/{graphlib-CQBb2thr.js → graphlib-BcsNnGcW.js} +1 -1
- package/dist/web/assets/index-CgQXpBb_.css +2 -0
- package/dist/web/assets/index-DEeeRoka.js +37 -0
- package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-B1CX0pbC.js +2 -0
- package/dist/web/assets/input-BglMT33g.js +1 -0
- package/dist/web/assets/{isEmpty-B4kqZBtn.js → isEmpty-bnrF3Qbc.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-46yibrV5.js → ishikawaDiagram-PHBUUO56-BOyvKMmB.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-BcmRwjK-.js → journeyDiagram-4ABVD52K-ufoasAy6.js} +1 -1
- package/dist/web/assets/{kanban-definition-K7BYSVSG-B619K53y.js → kanban-definition-K7BYSVSG-Bi0UTUeN.js} +1 -1
- package/dist/web/assets/keybindings-store-1CJ7VX57.js +1 -0
- package/dist/web/assets/lib-BQ34Db2e.js +4 -0
- package/dist/web/assets/{line-1gcO63_w.js → line-B78g-52T.js} +1 -1
- package/dist/web/assets/{linear-DfRqDoVd.js → linear-DP4mkX3m.js} +1 -1
- package/dist/web/assets/markdown-renderer-Brj8_LQM.js +69 -0
- package/dist/web/assets/{mermaid-parser.core-XtjZQOeM.js → mermaid-parser.core-DMIWdgEW.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-CifOFo_q.js → mindmap-definition-YRQLILUH-BsfWvIoO.js} +1 -1
- package/dist/web/assets/{ordinal-BJYw-iDX.js → ordinal-_K3x1fkz.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-BuHUh_fO.js → pieDiagram-SKSYHLDU-WP0XXw51.js} +1 -1
- package/dist/web/assets/postgres-viewer-CwkTGmqy.js +1 -0
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-Bau_hj6Z.js → quadrantDiagram-337W2JSQ-FHMogtsh.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
- package/dist/web/assets/react-dom-Bpkvzu3U.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-Cq2b-uwp.js → requirementDiagram-Z7DCOOCP-BatTxyWb.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DrdGQxWQ.js → sankeyDiagram-WA2Y5GQK-ClJuW3Hv.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-qPxiTUcS.js → sequenceDiagram-2WXFIKYE-ByxQqGgs.js} +1 -1
- package/dist/web/assets/settings-tab-BDE1MsIh.js +1 -0
- package/dist/web/assets/sqlite-viewer-CFYTwgA8.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-Dulj2oa8.js → stateDiagram-RAJIS63D-f8opcZNY.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DrxVDY9q.js +1 -0
- package/dist/web/assets/tab-store-BJw7OCmy.js +1 -0
- package/dist/web/assets/{terminal-tab-wKgpSPAT.js → terminal-tab-CCDLZA5Y.js} +2 -2
- package/dist/web/assets/{timeline-definition-YZTLITO2-BWyDnCYq.js → timeline-definition-YZTLITO2-58BlOSf9.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
- package/dist/web/assets/use-monaco-theme-CNzekTN3.js +11 -0
- package/dist/web/assets/{vennDiagram-LZ73GAT5-B9Iv2bNV.js → vennDiagram-LZ73GAT5-BOSy9ma9.js} +1 -1
- package/dist/web/assets/{xychartDiagram-JWTSCODW-ChXcMzBQ.js → xychartDiagram-JWTSCODW-z5MVJauZ.js} +1 -1
- package/dist/web/index.html +12 -11
- package/dist/web/sw.js +1 -1
- package/docs/code-standards.md +232 -7
- package/docs/codebase-summary.md +9 -3
- package/docs/design-guidelines.md +21 -0
- package/docs/project-changelog.md +115 -1
- package/docs/project-roadmap.md +41 -19
- package/docs/system-architecture.md +212 -15
- package/package.json +3 -2
- package/src/cli/commands/autostart.ts +1 -1
- package/src/cli/commands/restart.ts +9 -1
- package/src/cli/commands/status.ts +19 -0
- package/src/index.ts +2 -3
- package/src/providers/claude-agent-sdk.ts +92 -25
- package/src/providers/mock-provider.ts +6 -1
- package/src/server/index.ts +38 -166
- package/src/server/routes/browser-preview.ts +159 -0
- package/src/server/routes/chat.ts +52 -3
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/proxy.ts +46 -53
- package/src/server/routes/tunnel.ts +0 -32
- package/src/server/routes/workspace.ts +35 -0
- package/src/server/ws/chat.ts +207 -146
- package/src/services/account-selector.service.ts +16 -8
- package/src/services/account.service.ts +19 -13
- package/src/services/claude-usage.service.ts +48 -11
- package/src/services/cloud-ws.service.ts +227 -0
- package/src/services/cloud.service.ts +10 -6
- package/src/services/db.service.ts +111 -6
- package/src/services/proxy.service.ts +4 -19
- package/src/services/supervisor.ts +285 -25
- package/src/types/api.ts +9 -1
- package/src/types/chat.ts +3 -1
- package/src/web/app.tsx +41 -35
- package/src/web/components/browser/browser-tab.tsx +106 -97
- package/src/web/components/chat/account-rotation-settings.tsx +163 -0
- package/src/web/components/chat/chat-history-bar.tsx +72 -6
- package/src/web/components/chat/chat-tab.tsx +32 -16
- package/src/web/components/chat/chat-welcome.tsx +148 -0
- package/src/web/components/chat/message-input.tsx +107 -13
- package/src/web/components/chat/message-list.tsx +27 -15
- package/src/web/components/chat/session-picker.tsx +78 -31
- package/src/web/components/chat/usage-badge.tsx +11 -1
- package/src/web/components/editor/code-editor.tsx +36 -26
- package/src/web/components/editor/csv-preview.tsx +228 -0
- package/src/web/components/editor/editor-breadcrumb.tsx +216 -0
- package/src/web/components/editor/editor-toolbar.tsx +74 -0
- package/src/web/components/layout/command-palette.tsx +3 -1
- package/src/web/components/layout/editor-panel.tsx +162 -18
- package/src/web/components/layout/panel-layout.tsx +17 -1
- package/src/web/components/settings/proxy-settings-section.tsx +40 -42
- package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
- package/src/web/hooks/use-chat.ts +211 -201
- package/src/web/hooks/use-global-keybindings.ts +25 -2
- package/src/web/hooks/use-server-reload.ts +9 -0
- package/src/web/hooks/use-url-sync.ts +173 -21
- package/src/web/hooks/use-voice-input.ts +111 -0
- package/src/web/lib/csv-parser.ts +134 -0
- package/src/web/stores/connection-store.ts +39 -0
- package/src/web/stores/keybindings-store.ts +1 -0
- package/src/web/stores/panel-store.ts +73 -19
- package/src/web/stores/panel-utils.ts +145 -3
- package/dist/web/assets/api-settings-CFw-lh5k.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-CJupe6q_.js +0 -1
- package/dist/web/assets/browser-tab-CmsL5eny.js +0 -1
- package/dist/web/assets/channel-DmKoFTd_.js +0 -1
- package/dist/web/assets/chat-tab-CFWsf13Z.js +0 -7
- package/dist/web/assets/chunk-GLR3WWYH-BnP-hOp6.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-DKDPTPEZ.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-C_wpI9wz.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-B1T5uY-F.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-xs5vI3xC.js +0 -1
- package/dist/web/assets/clone-CijCFRT5.js +0 -1
- package/dist/web/assets/code-editor-H_dAh_fJ.js +0 -1
- package/dist/web/assets/database-viewer-DBzsgEJ8.js +0 -1
- package/dist/web/assets/diff-viewer-DzS-OnAR.js +0 -4
- package/dist/web/assets/dist-0Va_2L7G.js +0 -16
- package/dist/web/assets/dist-D9irYETY.js +0 -41
- package/dist/web/assets/git-graph-D3C7F8o3.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-B0KvGQG8.js +0 -1
- package/dist/web/assets/index-CIkjfera.js +0 -31
- package/dist/web/assets/index-WKLuYsBY.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-1uJ6_hCm.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-DLA5Q-3y.js +0 -2
- package/dist/web/assets/input-CGp1nFIg.js +0 -1
- package/dist/web/assets/keybindings-store-BdaoLwSo.js +0 -1
- package/dist/web/assets/markdown-renderer-DH49Zag7.js +0 -69
- package/dist/web/assets/packet-RMMSAZCW-34C4o9yj.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-D9ekKlh9.js +0 -1
- package/dist/web/assets/postgres-viewer-B9FYk8sD.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-DEuXOXSD.js +0 -1
- package/dist/web/assets/settings-store-DWXGVHsE.js +0 -2
- package/dist/web/assets/settings-tab-D-q8pd-5.js +0 -1
- package/dist/web/assets/sqlite-viewer-CDqcTePw.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-CAkzLlhk.js +0 -1
- package/dist/web/assets/tab-store-BPeiymiH.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-nc7a1Ia1.js +0 -1
- package/dist/web/assets/use-monaco-theme-CCBTQ0S3.js +0 -11
- package/src/services/port-tunnel.service.ts +0 -97
- /package/dist/web/assets/{api-client-DOElml5u.js → api-client-BfBM3I7n.js} +0 -0
- /package/dist/web/assets/{array-CYkMkqnU.js → array-B9UHiPd-.js} +0 -0
- /package/dist/web/assets/{columns-2-ChOTgl3e.js → columns-2-DpsNbZOc.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-HeHO0VhB.js → cytoscape.esm-BW-DbntU.js} +0 -0
- /package/dist/web/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/web/assets/{dist-BUYzeuKe.js → dist-CSJdAyA9.js} +0 -0
- /package/dist/web/assets/{init-Rr1s_RiX.js → init-DlZdxViB.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-BB-mzMLb.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
- /package/dist/web/assets/{katex-CKoArbIw.js → katex-Bqvo_ZG0.js} +0 -0
- /package/dist/web/assets/{math-B7b0HgJF.js → math-069Z4SuC.js} +0 -0
- /package/dist/web/assets/{path-BAQ3hXlG.js → path-6uRLdFF7.js} +0 -0
- /package/dist/web/assets/{preload-helper-DeiOTZKJ.js → preload-helper-uTix4PVD.js} +0 -0
- /package/dist/web/assets/{react-Dev-wu-s.js → react-ER-4DN55.js} +0 -0
- /package/dist/web/assets/{rough.esm-Dwml_la6.js → rough.esm-JX0wREDd.js} +0 -0
- /package/dist/web/assets/{src-B_cC68fH.js → src-BqX54PbV.js} +0 -0
- /package/dist/web/assets/{table-COiJDPRA.js → table-C7X5UAEI.js} +0 -0
- /package/dist/web/assets/{tag-LMq02LfE.js → tag-CCtdV063.js} +0 -0
- /package/dist/web/assets/{utils-btZ8C8-R.js → utils-BNytJOb1.js} +0 -0
package/docs/code-standards.md
CHANGED
|
@@ -86,6 +86,31 @@ async *streamMessages(input: string) {
|
|
|
86
86
|
provider.sendMessage(input).then(...)
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
### Long-Lived Streaming Sessions
|
|
90
|
+
|
|
91
|
+
For chat-like features, maintain persistent streaming sessions instead of per-query execution:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Good: Session-scoped streaming (v0.8.55+)
|
|
95
|
+
// Provider maintains AsyncGenerator per session
|
|
96
|
+
const streaming = provider.sendMessage(sessionId, content);
|
|
97
|
+
for await (const event of streaming) {
|
|
98
|
+
// Handle event (can receive multiple messages from same generator)
|
|
99
|
+
yield event;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Avoid: Per-message query execution
|
|
103
|
+
for (const message of messages) {
|
|
104
|
+
const result = await query(message); // Restarts SDK each time
|
|
105
|
+
yield result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Key Pattern: Decoupled streaming loop
|
|
109
|
+
// Store streaming generator in session entry
|
|
110
|
+
// Follow-up messages push into existing generator
|
|
111
|
+
// FE disconnect ≠ abort (BE owns the connection)
|
|
112
|
+
```
|
|
113
|
+
|
|
89
114
|
### Error Handling
|
|
90
115
|
Use try-catch for async operations. Throw structured errors:
|
|
91
116
|
|
|
@@ -221,6 +246,63 @@ try {
|
|
|
221
246
|
}
|
|
222
247
|
```
|
|
223
248
|
|
|
249
|
+
### Provider Interface Pattern (Multi-Provider)
|
|
250
|
+
|
|
251
|
+
PPM supports multiple AI providers through an extensible interface pattern:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// Core interface defines required methods + optional capability methods
|
|
255
|
+
interface AIProvider {
|
|
256
|
+
id: string;
|
|
257
|
+
name: string;
|
|
258
|
+
|
|
259
|
+
// Required: All providers must implement these
|
|
260
|
+
createSession(config: SessionConfig): Promise<Session>;
|
|
261
|
+
resumeSession(sessionId: string): Promise<Session>;
|
|
262
|
+
listSessions(): Promise<SessionInfo[]>;
|
|
263
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
264
|
+
sendMessage(sessionId: string, message: string, opts?: SendMessageOpts): AsyncIterable<ChatEvent>;
|
|
265
|
+
|
|
266
|
+
// Optional: Providers implement what they support
|
|
267
|
+
abortQuery?(sessionId: string): void;
|
|
268
|
+
getMessages?(sessionId: string): Promise<ChatMessage[]>;
|
|
269
|
+
listSessionsByDir?(dir: string): Promise<SessionInfo[]>;
|
|
270
|
+
ensureProjectPath?(sessionId: string, path: string): void;
|
|
271
|
+
isAvailable?(): Promise<boolean>;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**SDK-based Providers (Claude):**
|
|
276
|
+
- Use Anthropic SDK for advanced features
|
|
277
|
+
- Implement all optional methods
|
|
278
|
+
- Streaming via SDK async generators
|
|
279
|
+
|
|
280
|
+
**CLI-based Providers (Cursor, Codex, Gemini):**
|
|
281
|
+
- Extend `CliProvider` abstract class
|
|
282
|
+
- Implement: `buildArgs()`, `mapEvent()`, `extractSessionId()`, `isAvailable()`
|
|
283
|
+
- Shared: spawn, parse NDJSON, abort, cleanup
|
|
284
|
+
- Override `spawnProcess()` for provider-specific quirks (e.g., workspace trust)
|
|
285
|
+
- Override `listSessions()` to read native provider history (e.g., SQLite)
|
|
286
|
+
|
|
287
|
+
**Usage Pattern (Type-Safe Optional Calls):**
|
|
288
|
+
```typescript
|
|
289
|
+
// Good: Optional chaining for capabilities
|
|
290
|
+
await provider.abortQuery?.(sessionId); // Silently skips if not implemented
|
|
291
|
+
const messages = await provider.getMessages?.(sessionId) ?? [];
|
|
292
|
+
|
|
293
|
+
// Avoid: Duck-typing checks (old pattern)
|
|
294
|
+
if ("abortQuery" in provider) {
|
|
295
|
+
(provider as any).abortQuery(sessionId); // Not type-safe
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Adding New Providers:**
|
|
300
|
+
1. Create provider class implementing/extending AIProvider or CliProvider
|
|
301
|
+
2. Implement `isAvailable()` for optional registration
|
|
302
|
+
3. Register in `ProviderRegistry` via `bootstrapProviders()` if binary available
|
|
303
|
+
4. Add event mapper if CLI-based (NDJSON → ChatEvent)
|
|
304
|
+
5. ~100-150 lines per provider (extends CliProvider)
|
|
305
|
+
|
|
224
306
|
## API Conventions
|
|
225
307
|
|
|
226
308
|
### Response Envelope
|
|
@@ -252,18 +334,35 @@ GET /api/project/:name/files/... # Files (project-scoped)
|
|
|
252
334
|
Structure WebSocket messages as typed JSON objects:
|
|
253
335
|
|
|
254
336
|
```typescript
|
|
255
|
-
// Client -> Server (chat)
|
|
256
|
-
{ type: "message"; content: string }
|
|
337
|
+
// Client -> Server (chat, v0.8.55+)
|
|
338
|
+
{ type: "message"; content: string; priority?: "now"|"next"|"later"; images?: {id: string; data: string}[] }
|
|
257
339
|
{ type: "cancel" }
|
|
258
|
-
{ type: "approval_response"; requestId: string; approved: boolean }
|
|
340
|
+
{ type: "approval_response"; requestId: string; approved: boolean; reason?: string; data?: unknown }
|
|
341
|
+
{ type: "ready" } // FE handshake after WS open
|
|
259
342
|
|
|
260
343
|
// Server -> Client (chat)
|
|
261
|
-
{ type: "text"; content: string }
|
|
262
|
-
{ type: "
|
|
344
|
+
{ type: "text"; content: string; parentToolUseId?: string }
|
|
345
|
+
{ type: "thinking"; content: string; parentToolUseId?: string }
|
|
346
|
+
{ type: "tool_use"; tool: string; input: unknown; toolUseId?: string; parentToolUseId?: string }
|
|
347
|
+
{ type: "tool_result"; output: string; isError?: boolean; toolUseId?: string; parentToolUseId?: string }
|
|
263
348
|
{ type: "approval_request"; requestId: string; tool: string; input: unknown }
|
|
264
|
-
{ type: "done"; sessionId: string }
|
|
349
|
+
{ type: "done"; sessionId: string; contextWindowPct?: number; resultSubtype?: string }
|
|
265
350
|
{ type: "error"; message: string }
|
|
266
|
-
|
|
351
|
+
{ type: "account_info"; accountId: string; accountLabel: string }
|
|
352
|
+
{ type: "phase_changed"; phase: SessionPhase; elapsed?: number }
|
|
353
|
+
{ type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: {...}|null; sessionTitle: string|null }
|
|
354
|
+
{ type: "turn_events"; events: unknown[] } // Buffered events on reconnect
|
|
355
|
+
{ type: "title_updated"; title: string }
|
|
356
|
+
{ type: "ping" } // Server keepalive
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**New Fields (v0.8.55+):**
|
|
360
|
+
- `priority` — Message priority for queue ordering (future: "now" interrupts, "next" queues first, "later" queues at end)
|
|
361
|
+
- `images` — Image attachments sent with message
|
|
362
|
+
- `phase_changed` — Phase transitions (initializing → connecting → thinking/streaming → idle)
|
|
363
|
+
- `session_state` — Session state snapshot on WS open/ready (includes current phase and pending approval)
|
|
364
|
+
- `turn_events` — Buffered events sent on reconnection for sync
|
|
365
|
+
- `parentToolUseId` — Hierarchical tool call support (nested tool results)
|
|
267
366
|
|
|
268
367
|
### Status Codes
|
|
269
368
|
Use standard HTTP status codes:
|
|
@@ -646,3 +745,129 @@ if (options.share) {
|
|
|
646
745
|
|
|
647
746
|
This keeps startup fast when features aren't used.
|
|
648
747
|
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## Tab ID & URL Conventions (v0.8.77+)
|
|
751
|
+
|
|
752
|
+
### Deterministic Tab IDs
|
|
753
|
+
|
|
754
|
+
Tab IDs are derived from type + metadata, not random:
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
// Format: "{type}:{identifier}" for most tabs, "{type}" for singletons
|
|
758
|
+
editor:src/index.ts // Editor tab with file path
|
|
759
|
+
chat:claude/abc123 // Chat with provider/session ID
|
|
760
|
+
terminal:1 // Terminal tab index
|
|
761
|
+
database:conn-1/users // Database: connection ID / table
|
|
762
|
+
git-graph // Singleton: no identifier
|
|
763
|
+
settings // Singleton: no identifier
|
|
764
|
+
|
|
765
|
+
// Derivation in panel-utils.ts
|
|
766
|
+
export function deriveTabId(type: TabType, metadata?: Record<string, unknown>): string {
|
|
767
|
+
switch (type) {
|
|
768
|
+
case "editor":
|
|
769
|
+
return `editor:${metadata?.filePath ?? "untitled"}`;
|
|
770
|
+
case "chat": {
|
|
771
|
+
const provider = metadata?.providerId ?? "default";
|
|
772
|
+
return `chat:${provider}/${metadata?.sessionId ?? randomId()}`;
|
|
773
|
+
}
|
|
774
|
+
case "terminal":
|
|
775
|
+
return `terminal:${metadata?.terminalIndex ?? 1}`;
|
|
776
|
+
case "git-graph":
|
|
777
|
+
return "git-graph"; // Singleton
|
|
778
|
+
// ... other cases
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### URL Format
|
|
784
|
+
|
|
785
|
+
URLs are built from project name + deterministic tab ID:
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
// Format: /project/{projectName}/{type}/{identifier}
|
|
789
|
+
/project/ppm // Project root (no active tab)
|
|
790
|
+
/project/ppm/editor/src/index.ts // Open editor
|
|
791
|
+
/project/ppm/chat/claude/abc123 // Open chat
|
|
792
|
+
/project/ppm/terminal/1 // Open terminal
|
|
793
|
+
/project/ppm/database/conn-1/users // Open database
|
|
794
|
+
/project/ppm/git-graph // Git history (singleton)
|
|
795
|
+
|
|
796
|
+
// URL building in use-url-sync.ts
|
|
797
|
+
export function buildUrl(projectName: string, tabId: string | null): string {
|
|
798
|
+
const colonIdx = tabId?.indexOf(":");
|
|
799
|
+
if (colonIdx === -1) {
|
|
800
|
+
// Singleton
|
|
801
|
+
url += `/${tabId}`;
|
|
802
|
+
} else {
|
|
803
|
+
const [type, identifier] = tabId.split(":", 1);
|
|
804
|
+
url += `/${type}/${identifier}`;
|
|
805
|
+
}
|
|
806
|
+
return url;
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
### Deep Linking
|
|
811
|
+
|
|
812
|
+
When user navigates to a URL, parse it and auto-create tabs:
|
|
813
|
+
|
|
814
|
+
```typescript
|
|
815
|
+
// In use-url-sync hook
|
|
816
|
+
export function parseUrlState(): UrlState {
|
|
817
|
+
const match = path.match(/^\/project\/([^/]+)(?:\/([^/]+)(\/.*)?)?/);
|
|
818
|
+
const [, projectName, tabType, tabIdentifierPath] = match;
|
|
819
|
+
|
|
820
|
+
// Build tab metadata from URL
|
|
821
|
+
const metadata = buildMetadataFromUrl(tabType, tabIdentifier, projectName);
|
|
822
|
+
|
|
823
|
+
// Auto-open tab if it doesn't exist
|
|
824
|
+
if (metadata) {
|
|
825
|
+
panelStore.openTab({
|
|
826
|
+
type: tabType,
|
|
827
|
+
title: getTabTitle(tabType, metadata),
|
|
828
|
+
metadata,
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Server-Side Workspace Persistence
|
|
835
|
+
|
|
836
|
+
Tab layouts are persisted in the `workspace_state` SQLite table:
|
|
837
|
+
|
|
838
|
+
```typescript
|
|
839
|
+
// workspace.ts routes
|
|
840
|
+
GET /api/project/:name/workspace → { layout: PanelLayout, updatedAt: string }
|
|
841
|
+
PUT /api/project/:name/workspace → { updatedAt: string }
|
|
842
|
+
|
|
843
|
+
// PanelLayout structure
|
|
844
|
+
interface PanelLayout {
|
|
845
|
+
panels: Record<string, Panel>; // panelId → { tabs, activeTabId }
|
|
846
|
+
grid: string[][]; // Row/column grid of panel IDs
|
|
847
|
+
focusedPanelId: string;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Sync is debounced (1.5s) client-side after layout changes
|
|
851
|
+
// Latest-wins: server timestamp compared with client localStorage
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
### Migration from Random IDs
|
|
855
|
+
|
|
856
|
+
Old tab IDs (tab-xxxx) are automatically migrated to deterministic format:
|
|
857
|
+
|
|
858
|
+
```typescript
|
|
859
|
+
// In panel-utils.ts
|
|
860
|
+
export function migrateTabIdToDeterministic(tab: Tab): Tab {
|
|
861
|
+
if (tab.id.startsWith("tab-")) {
|
|
862
|
+
// Convert to deterministic ID
|
|
863
|
+
return {
|
|
864
|
+
...tab,
|
|
865
|
+
id: deriveTabId(tab.type, tab.metadata),
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
return tab;
|
|
869
|
+
}
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
This happens on first load per project; old URLs redirect to project root.
|
|
873
|
+
|
package/docs/codebase-summary.md
CHANGED
|
@@ -104,7 +104,7 @@ ppm/
|
|
|
104
104
|
│ │ ├── use-health-check.ts # Detect server crashes/restarts via health endpoint
|
|
105
105
|
│ │ ├── use-usage.ts # Fetch token usage from backend
|
|
106
106
|
│ │ └── use-push-notification.ts # Web push notifications via Service Worker
|
|
107
|
-
│ ├── lib/ # Utilities (
|
|
107
|
+
│ ├── lib/ # Utilities (12 files)
|
|
108
108
|
│ │ ├── api-client.ts # Fetch wrapper with auth token, envelope unwrapping
|
|
109
109
|
│ │ ├── api-settings.ts # AI settings API client (GET/PUT /api/settings/ai)
|
|
110
110
|
│ │ ├── ws-client.ts # WebSocket with exponential backoff + Cloudflare handshake
|
|
@@ -113,6 +113,7 @@ ppm/
|
|
|
113
113
|
│ │ ├── project-palette.ts # 12-color palette for project avatars
|
|
114
114
|
│ │ ├── use-monaco-theme.ts # Sync Monaco Editor theme with app theme
|
|
115
115
|
│ │ ├── color-utils.ts # WCAG color contrast helper
|
|
116
|
+
│ │ ├── csv-parser.ts # CSV state-machine parser/serializer
|
|
116
117
|
│ │ └── utils.ts # Helpers (cn, randomId, basename, etc.)
|
|
117
118
|
│ ├── styles/
|
|
118
119
|
│ │ └── globals.css # Tailwind directives, custom CSS
|
|
@@ -131,9 +132,12 @@ ppm/
|
|
|
131
132
|
│ │ ├── usage-badge.tsx # Token usage display
|
|
132
133
|
│ │ ├── attachment-chips.tsx # Display attached files
|
|
133
134
|
│ │ └── chat-placeholder.tsx # Empty state
|
|
134
|
-
│ ├── editor/ # Code editor (
|
|
135
|
+
│ ├── editor/ # Code editor (800+ LOC, 6 files)
|
|
135
136
|
│ │ ├── code-editor.tsx # Monaco Editor integration (@monaco-editor/react, v2.0+)
|
|
136
137
|
│ │ ├── diff-viewer.tsx # Monaco diff viewer for git diffs (v2.0+)
|
|
138
|
+
│ │ ├── editor-breadcrumb.tsx # VSCode-style breadcrumb with nested dropdown
|
|
139
|
+
│ │ ├── editor-toolbar.tsx # File-type contextual toolbar
|
|
140
|
+
│ │ ├── csv-preview.tsx # CSV table viewer with @tanstack/react-table
|
|
137
141
|
│ │ └── editor-placeholder.tsx
|
|
138
142
|
│ ├── explorer/ # File tree (489 LOC, 2 files)
|
|
139
143
|
│ │ ├── file-tree.tsx # Directory tree view
|
|
@@ -346,11 +350,13 @@ UI updates staged/unstaged lists
|
|
|
346
350
|
| @monaco-editor/react | Code editor | 4.7.0 |
|
|
347
351
|
| xterm | Terminal emulator | 6.0 |
|
|
348
352
|
| zustand | State management | 5.0.11 |
|
|
349
|
-
| @anthropic-ai/claude-agent-sdk | AI provider | 0.2.
|
|
353
|
+
| @anthropic-ai/claude-agent-sdk | AI provider | 0.2.81 |
|
|
350
354
|
| vite | Frontend bundler | 8.0 |
|
|
351
355
|
| tailwindcss | Utility CSS | 4.2 |
|
|
352
356
|
| radix-ui | Accessible components | 1.4.3 |
|
|
353
357
|
| next-themes | Theme switcher | 0.4.6 |
|
|
358
|
+
| @tanstack/react-table | Table library | 8.21.3 |
|
|
359
|
+
| @tanstack/react-virtual | Virtual scrolling | 3.13.23 |
|
|
354
360
|
|
|
355
361
|
## Build Output
|
|
356
362
|
|
|
@@ -393,6 +393,27 @@ screens: {
|
|
|
393
393
|
// - Word wrap toggle (Alt+Z)
|
|
394
394
|
// - Monaco diff viewer for git diffs
|
|
395
395
|
// - Theme sync with app dark/light mode
|
|
396
|
+
// - VSCode-style breadcrumb navigation (EditorBreadcrumb)
|
|
397
|
+
// - File-type contextual toolbar (EditorToolbar)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### CSV Preview
|
|
401
|
+
|
|
402
|
+
```tsx
|
|
403
|
+
// src/web/components/editor/csv-preview.tsx
|
|
404
|
+
// Table viewer for CSV files with sorting and editing
|
|
405
|
+
|
|
406
|
+
<CsvPreview
|
|
407
|
+
content={csvString}
|
|
408
|
+
onContentChange={setCsvString}
|
|
409
|
+
/>
|
|
410
|
+
|
|
411
|
+
// Features:
|
|
412
|
+
// - State-machine CSV parser (handles quoted fields, embedded commas/newlines)
|
|
413
|
+
// - Virtual scrolling (@tanstack/react-virtual) for large files
|
|
414
|
+
// - Column sorting via @tanstack/react-table
|
|
415
|
+
// - Inline cell editing with live serialization
|
|
416
|
+
// - Mobile-friendly table layout
|
|
396
417
|
```
|
|
397
418
|
|
|
398
419
|
### Terminal Component
|
|
@@ -2,7 +2,121 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to PPM are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|
4
4
|
|
|
5
|
-
**Current Version:** v0.8.
|
|
5
|
+
**Current Version:** v0.8.76
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## [0.8.77] — 2026-04-01 (Planned)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Deterministic Tab URLs + Backend Workspace Sync**
|
|
13
|
+
- Tab IDs now deterministic ({type}:{identifier}) instead of random (tab-xxxx)
|
|
14
|
+
- URL format changed: `/project/{name}/{tabType}/{identifier}` (e.g., `/project/ppm/editor/src/index.ts`)
|
|
15
|
+
- New `workspace_state` database table (schema v10) persists tab layout per project
|
|
16
|
+
- GET/PUT `/api/project/:name/workspace` endpoints for server-side persistence
|
|
17
|
+
- Frontend syncs workspace layout to server with 1.5s debounce
|
|
18
|
+
- Deep linking from URL auto-creates tabs if missing
|
|
19
|
+
- Latest-wins conflict resolution: server timestamp > client localStorage
|
|
20
|
+
- Tab ID patterns standardized: `editor:path`, `chat:provider/sessionId`, `terminal:index`, etc.
|
|
21
|
+
- Migration from random IDs to deterministic IDs on first load
|
|
22
|
+
|
|
23
|
+
### Technical Details
|
|
24
|
+
- **Files Created:**
|
|
25
|
+
- `src/server/routes/workspace.ts` — Workspace GET/PUT endpoints
|
|
26
|
+
- `src/web/hooks/use-workspace-sync.ts` — Server sync orchestration with debounce
|
|
27
|
+
- **Files Modified:**
|
|
28
|
+
- `src/services/db.service.ts` — Added workspace_state table (schema v10), helper functions
|
|
29
|
+
- `src/web/stores/panel-utils.ts` — Deterministic tab ID derivation logic
|
|
30
|
+
- `src/web/hooks/use-url-sync.ts` — URL parsing/building with new format
|
|
31
|
+
- `src/web/stores/panel-store.ts` — Load workspace from server on project switch
|
|
32
|
+
- `src/web/stores/tab-store.ts` — Tab interface updated with metadata
|
|
33
|
+
- **Breaking Changes:** URLs changed; old `/project/{name}/tab/{id}` URLs redirected or ignored (fallback to project root)
|
|
34
|
+
- **Migration:** Client-side: old random tab IDs migrated to deterministic format on first load
|
|
35
|
+
|
|
36
|
+
### Benefits
|
|
37
|
+
- **Shareable deep links** — URLs point to specific files/chats (e.g., `/project/ppm/editor/src/index.ts`)
|
|
38
|
+
- **Cross-device persistence** — Workspace layout saved on server, restored on any device
|
|
39
|
+
- **URL-driven navigation** — Paste URL to recreate workspace state
|
|
40
|
+
- **Conflict-free sync** — Latest timestamp wins; no manual merge dialogs
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## [0.8.61] — 2026-03-26 (Beta)
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- **Multi-Provider Architecture** — Generic AI provider system supporting Claude (SDK-based) and CLI-spawning providers
|
|
48
|
+
- `AIProvider` interface with optional capability methods (`abortQuery?`, `getMessages?`, `listSessionsByDir?`)
|
|
49
|
+
- `CliProvider` abstract base class for CLI-spawning providers (Cursor, Codex, Gemini)
|
|
50
|
+
- `CursorCliProvider` implementation — spawns `cursor-agent` with NDJSON streaming
|
|
51
|
+
- NDJSON line parser utility for TCP packet boundary handling
|
|
52
|
+
- Cursor event mapper — normalizes Cursor NDJSON → standard ChatEvent union
|
|
53
|
+
- Cursor history reader — loads sessions from `~/.cursor/chats/` SQLite DAG
|
|
54
|
+
- Provider selector UI component — users can choose provider when creating chat
|
|
55
|
+
- Async provider bootstrap — checks binary availability, registers only if available
|
|
56
|
+
- Workspace trust auto-retry — detects trust prompts, retries with `--trust` flag
|
|
57
|
+
- Process lifecycle management — SIGTERM → SIGKILL escalation, orphan cleanup
|
|
58
|
+
|
|
59
|
+
### Technical Details
|
|
60
|
+
- **Files Created:**
|
|
61
|
+
- `src/utils/ndjson-line-parser.ts` — NDJSON streaming parser
|
|
62
|
+
- `src/providers/cli-provider-base.ts` — Abstract CliProvider base class
|
|
63
|
+
- `src/providers/cursor-cli/cursor-provider.ts` — CursorCliProvider
|
|
64
|
+
- `src/providers/cursor-cli/cursor-event-mapper.ts` — Event mapping
|
|
65
|
+
- `src/providers/cursor-cli/cursor-history.ts` — SQLite history reader
|
|
66
|
+
- `src/web/components/chat/provider-selector.tsx` — Provider selection UI
|
|
67
|
+
- `tests/unit/ndjson-line-parser.test.ts` — Parser tests
|
|
68
|
+
- `tests/unit/cursor-event-mapper.test.ts` — Mapper tests
|
|
69
|
+
- `tests/integration/cursor-provider.test.ts` — Integration tests
|
|
70
|
+
- `tests/integration/chat-service-multi-provider.test.ts` — Service tests
|
|
71
|
+
- **Files Modified:**
|
|
72
|
+
- `src/types/chat.ts` — Added optional capability methods to AIProvider, added `system` event type
|
|
73
|
+
- `src/types/config.ts` — Added `"cli"` type, `cli_command` field to AIProviderConfig
|
|
74
|
+
- `src/providers/registry.ts` — Added async `bootstrapProviders()` for conditional registration
|
|
75
|
+
- `src/server/ws/chat.ts` — Removed `as any` casts, use optional chaining for capabilities
|
|
76
|
+
- `src/services/chat.service.ts` — Use optional methods instead of duck-typing
|
|
77
|
+
- `src/web/components/chat/session-picker.tsx` — Integrated provider selector
|
|
78
|
+
- **Breaking Changes:** None (backward compatible, all tests passing)
|
|
79
|
+
- **Architecture:** All phases complete (6/6), 479 tests passing
|
|
80
|
+
|
|
81
|
+
### Benefits
|
|
82
|
+
- Extensible foundation for Codex, Gemini, and future providers (~100-150 lines each)
|
|
83
|
+
- No more `as any` casts for provider methods — type-safe optional capability pattern
|
|
84
|
+
- CLI providers can override session history reading (e.g., Cursor SQLite DAG)
|
|
85
|
+
- Graceful degradation — missing CLI binary doesn't crash, logs info, skips provider
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## [0.8.55] — 2026-03-26
|
|
90
|
+
|
|
91
|
+
### Added
|
|
92
|
+
- **Streaming Input Migration** — Persistent AsyncGenerator session model for chat
|
|
93
|
+
- Provider maintains long-lived streaming input per session (not per message)
|
|
94
|
+
- Follow-up messages push into existing generator instead of abort-and-replace
|
|
95
|
+
- Single streaming loop decoupled from WebSocket message handler
|
|
96
|
+
- Message priority support (`now`/`next`/`later`) for intelligent message ordering
|
|
97
|
+
- Image attachment support in message sending
|
|
98
|
+
- Session state persistence across FE disconnections (5-minute cleanup timeout)
|
|
99
|
+
- Event buffering on reconnect: clients receive buffered turn events after reconnection
|
|
100
|
+
- Phase transitions: `idle` → `initializing` → `connecting` → `thinking`/`streaming` → `idle`
|
|
101
|
+
|
|
102
|
+
### Technical Details
|
|
103
|
+
- **Provider Layer** (`src/providers/claude-agent-sdk.ts`): Maintains streaming session per sessionId
|
|
104
|
+
- **WebSocket Handler** (`src/server/ws/chat.ts`):
|
|
105
|
+
- `runStreamLoop()` executes independently (detached from WS scope)
|
|
106
|
+
- `activeSessions` map persists session state across FE disconnections
|
|
107
|
+
- `startSessionConsumer()` pattern replaces per-message `runStreamLoop()` calls
|
|
108
|
+
- Event buffering (turnEvents array, max 10k events) for reconnect sync
|
|
109
|
+
- Per-client ping intervals (15s keepalive)
|
|
110
|
+
- **Types** (`src/types/api.ts`, `src/types/chat.ts`):
|
|
111
|
+
- `ChatWsClientMessage` extended with `priority?: string` and optional `images`
|
|
112
|
+
- `SessionPhase` = "initializing" | "connecting" | "thinking" | "streaming" | "idle"
|
|
113
|
+
- `SessionEntry` tracks clients, abort handle, phase, pending approvals, buffered events
|
|
114
|
+
- **Frontend** (`src/web/components/chat/message-input.tsx`): PriorityToggle component (visible during streaming)
|
|
115
|
+
- **Benefits**:
|
|
116
|
+
- No SDK subprocess restarts between messages (faster context preservation)
|
|
117
|
+
- Tool approvals integrated into streaming (no query restart)
|
|
118
|
+
- Message buffering prevents loss on FE reconnection
|
|
119
|
+
- Cleaner architecture: BE owns Claude connection, FE disconnect ≠ abort
|
|
6
120
|
|
|
7
121
|
---
|
|
8
122
|
|
package/docs/project-roadmap.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PPM Project Roadmap
|
|
2
2
|
|
|
3
|
-
**Last Updated:** March
|
|
3
|
+
**Last Updated:** March 27, 2026
|
|
4
4
|
|
|
5
5
|
## Vision
|
|
6
6
|
|
|
@@ -56,19 +56,26 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
56
56
|
|
|
57
57
|
### v0.9.0 — "Open Platform" (Q2–Q3 2026)
|
|
58
58
|
|
|
59
|
-
**Theme:** Multi-provider AI + extension system.
|
|
59
|
+
**Theme:** Multi-provider AI (Claude + Cursor) + extension system. Ship a focused release, expand providers later.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|---------|----------|-------------|
|
|
63
|
-
| **Multi-provider AI** | Critical | Refactor `ProviderInterface` for clean provider abstraction. Tiered support: Tier 1 (full agentic) = Claude Agent SDK; Tier 2 (chat + tools) = Gemini CLI, OpenAI Codex; Tier 3 (chat-only) = any OpenAI-compatible API. Clean base code for future Chinese providers (DeepSeek, Qwen). |
|
|
64
|
-
| **Extension architecture** | High | Dynamic extension loading system. Extensions = npm packages exporting skills + optional UI panels. First extension: extract DB viewer from core. Extension API: register routes, UI panels, sidebar tabs, skills. Config: `"extensions": ["@ppm/ext-database", "@ppm/ext-docker"]`. |
|
|
65
|
-
| **MCP Management** | Medium | UI to add/remove/configure MCP servers. Test connection. Per-project MCP overrides. Store in SQLite. Pass to Agent SDK via `mcpServers`. |
|
|
61
|
+
**Overall progress: ~40%** (1/3 features complete, merge all at 100%)
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
| Feature | Priority | Status | Description |
|
|
64
|
+
|---------|----------|--------|-------------|
|
|
65
|
+
| **Multi-provider AI** | Critical | ✅ Done | ProviderInterface, registry, Cursor CLI, CLI provider base, UI provider/model selector, permission mode selector, system prompt customization, comprehensive tests — all on beta branch. |
|
|
66
|
+
| **Extension architecture** | High | 🔴 0% | Dynamic extension loading. Extensions = npm packages. First extension: extract DB viewer from core. Extension API: register routes, UI panels, sidebar tabs, skills. Config: `"extensions": ["@ppm/ext-database", "@ppm/ext-docker"]`. |
|
|
67
|
+
| **MCP Management** | Medium | 🔴 0% | UI to add/remove/configure MCP servers. Test connection. Per-project MCP overrides. Store in SQLite. Pass to Agent SDK via `mcpServers`. |
|
|
68
|
+
|
|
69
|
+
**Multi-provider — v0.9 scope (reduced):**
|
|
68
70
|
- Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
|
|
69
|
-
- Tier 2 (
|
|
70
|
-
-
|
|
71
|
-
|
|
71
|
+
- Tier 2 (agentic CLI): Cursor — agentic via its own tool system
|
|
72
|
+
- Provider interface is clean enough to add more providers later without refactor
|
|
73
|
+
|
|
74
|
+
**Deferred to v0.9.5+:**
|
|
75
|
+
- Gemini CLI (Tier 2)
|
|
76
|
+
- OpenAI Codex (Tier 2)
|
|
77
|
+
- Tier 3 (chat-only): Any OpenAI-compatible API
|
|
78
|
+
- Chinese providers (DeepSeek, Qwen) — v1.0+
|
|
72
79
|
|
|
73
80
|
**Extension architecture — design principles:**
|
|
74
81
|
- Extensions are npm packages: `ppm ext install @ppm/ext-database`
|
|
@@ -79,15 +86,27 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
79
86
|
|
|
80
87
|
---
|
|
81
88
|
|
|
82
|
-
### v0.10.0 — "
|
|
89
|
+
### v0.10.0 — "Enhanced Workflow" (Q3 2026)
|
|
83
90
|
|
|
84
|
-
**Theme:**
|
|
91
|
+
**Theme:** Chat UX upgrade + git workflow. High-impact, independent features that ship fast.
|
|
85
92
|
|
|
86
93
|
| Feature | Priority | Description |
|
|
87
94
|
|---------|----------|-------------|
|
|
95
|
+
| **Chat history graph** | High | Visual branching tree of chat sessions. Fork conversations, navigate history graph. Game-changer for AI chat UX. |
|
|
96
|
+
| **Worktree management** | Medium | UI to create/switch/delete git worktrees. Use different providers on different branches. Integrated with project switcher. |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### v0.11.0 — "Intelligence" (Q3–Q4 2026)
|
|
101
|
+
|
|
102
|
+
**Theme:** Event system + PPM's own AI layer. Hooks → Skills API → Clawbot dependency chain.
|
|
103
|
+
|
|
104
|
+
| Feature | Priority | Description |
|
|
105
|
+
|---------|----------|-------------|
|
|
106
|
+
| **Hooks system** | High | Event hooks for PPM lifecycle (file save, git commit, chat message, etc.). Foundation for Skills API and deeper extension integration. |
|
|
88
107
|
| **PPM Skills API** | High | Stable internal API for AI to control PPM: file.read/write/search, terminal.run, git.status/commit/diff, db.query, editor.open/goto, project.switch. Skills are the bridge between AI and PPM features. |
|
|
89
108
|
| **Built-in Clawbot** | High | Lightweight AI agent built into PPM using Anthropic Messages API (not Agent SDK). Uses Skills API + MCP tools. Instant response, no external CLI deps. For quick tasks: file search, code explanation, simple refactors. |
|
|
90
|
-
| **
|
|
109
|
+
| **More providers** | Medium | Gemini CLI (Tier 2), OpenAI Codex (Tier 2), Tier 3 chat-only (any OpenAI-compatible API). Provider interface already clean from v0.9. |
|
|
91
110
|
|
|
92
111
|
**Built-in Clawbot — why it matters:**
|
|
93
112
|
- Claude Agent SDK spawns subprocess — heavy, slow startup, requires CLI installed
|
|
@@ -106,6 +125,7 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
106
125
|
| **Self-hosted PPM Cloud** | High | Docker image of PPM Cloud for enterprise/team. Same codebase, self-hosted config flag. `docker-compose up` and it works. LDAP/SSO integration. |
|
|
107
126
|
| **PPM Marketplace** | High | Publish/install/update extensions. Browse community extensions. Revenue sharing for paid extensions. Clawbot can create extension → test → publish in minutes. |
|
|
108
127
|
| **Stability & hardening** | Critical | Security audit, performance optimization, comprehensive test coverage (>80%), documentation for contributors, CI/CD pipeline. |
|
|
128
|
+
| **Inline SQL** | Medium | Select text in Monaco → run as SQL. Connection picker in editor context menu. Results panel below editor. Leverages existing DB service. |
|
|
109
129
|
|
|
110
130
|
---
|
|
111
131
|
|
|
@@ -130,6 +150,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
130
150
|
| **Cross-platform binaries** | Distribution | Compile macOS/Linux/Windows binaries via `bun build --compile`. `npx ppm` without Bun. |
|
|
131
151
|
| **OLED dark mode** | UX | True black background for OLED screens. |
|
|
132
152
|
| **Collaborative editing** | Social | Real-time multi-user file editing with CRDT (yjs/automerge). |
|
|
153
|
+
| **Custom domain** | Cloud | Map custom domain to PPM Cloud tunnel URL. DNS CNAME + SSL via Let's Encrypt or Cloudflare. Access PPM at `code.yourdomain.com`. |
|
|
133
154
|
|
|
134
155
|
---
|
|
135
156
|
|
|
@@ -139,9 +160,10 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
139
160
|
|---------|-------|-------------|--------|
|
|
140
161
|
| **v0.7** | Multi-Account & Mobile | Account management, usage tracking, mobile UX | ✅ Current |
|
|
141
162
|
| **v0.8** | Always On | PPM Cloud, auto-start, AI chat enhancements | Q2 2026 |
|
|
142
|
-
| **v0.9** | Open Platform | Multi-provider
|
|
143
|
-
| **v0.10** |
|
|
144
|
-
| **
|
|
163
|
+
| **v0.9** | Open Platform | Multi-provider (Claude + Cursor), extension architecture, MCP | Q2–Q3 2026 |
|
|
164
|
+
| **v0.10** | Enhanced Workflow | Chat history graph, worktree management | Q3 2026 |
|
|
165
|
+
| **v0.11** | Intelligence | Hooks, Skills API, Clawbot, more providers (Gemini/Codex/Tier 3) | Q3–Q4 2026 |
|
|
166
|
+
| **v1.0** | Production Ready | Self-hosted Cloud, Marketplace, stability, inline SQL | Q4 2026 |
|
|
145
167
|
|
|
146
168
|
---
|
|
147
169
|
|
|
@@ -149,7 +171,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
149
171
|
|
|
150
172
|
1. **Own "phone to code"** — PPM wins on multi-device access. Don't chase Cursor/Windsurf feature parity.
|
|
151
173
|
2. **PPM Cloud stays razor-thin** — Device registry + tunnel URLs only. No code storage. No cloud execution.
|
|
152
|
-
3. **Multi-provider is tiered** —
|
|
174
|
+
3. **Multi-provider is tiered** — v0.9: Claude SDK (Tier 1) + Cursor (Tier 2). v0.11: Gemini, Codex, Tier 3. Clean interface for future providers.
|
|
153
175
|
4. **Extensions keep core lightweight** — Features are opt-in. DB viewer, future tools = extensions. Core stays fast.
|
|
154
176
|
5. **Clawbot enables the ecosystem** — Users create extensions with AI, publish to Marketplace. Zero-friction.
|
|
155
177
|
6. **Self-hosted first, always** — Cloud is optional convenience. PPM works 100% offline/local.
|
|
@@ -160,7 +182,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
160
182
|
|
|
161
183
|
| Item | Priority | Notes |
|
|
162
184
|
|------|----------|-------|
|
|
163
|
-
| Refactor ProviderInterface for multi-provider | High |
|
|
185
|
+
| ~~Refactor ProviderInterface for multi-provider~~ | ~~High~~ | ✅ Done on beta branch (v0.9.0-beta.5) |
|
|
164
186
|
| Simplify ChatService streaming | Medium | Reduce async generator complexity |
|
|
165
187
|
| Extract WebSocket common logic | Low | DRY for chat/terminal WS |
|
|
166
188
|
| Round-robin cursor bug in AccountSelector | Medium | Positional cursor not advancing correctly |
|