@hienlh/ppm 0.1.0

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 (159) hide show
  1. package/.claude/agent-memory/tester/MEMORY.md +3 -0
  2. package/.claude/agent-memory/tester/project-ppm-test-conventions.md +32 -0
  3. package/.env.example +1 -0
  4. package/.github/workflows/release.yml +46 -0
  5. package/README.md +349 -0
  6. package/bun.lock +1217 -0
  7. package/components.json +21 -0
  8. package/docs/code-standards.md +574 -0
  9. package/docs/codebase-summary.md +294 -0
  10. package/docs/deployment-guide.md +631 -0
  11. package/docs/design-guidelines.md +661 -0
  12. package/docs/project-overview-pdr.md +142 -0
  13. package/docs/project-roadmap.md +400 -0
  14. package/docs/system-architecture.md +459 -0
  15. package/package.json +68 -0
  16. package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +81 -0
  17. package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +148 -0
  18. package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +256 -0
  19. package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +120 -0
  20. package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +174 -0
  21. package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +244 -0
  22. package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +242 -0
  23. package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +143 -0
  24. package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +209 -0
  25. package/plans/260314-2009-ppm-implementation/phase-10-testing.md +311 -0
  26. package/plans/260314-2009-ppm-implementation/plan.md +202 -0
  27. package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +145 -0
  28. package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +107 -0
  29. package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +100 -0
  30. package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +66 -0
  31. package/plans/260315-0356-project-scoped-api-refactor/plan.md +87 -0
  32. package/plans/reports/brainstorm-260314-1938-final-techstack.md +342 -0
  33. package/plans/reports/docs-manager-260315-1314-documentation-creation.md +386 -0
  34. package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +57 -0
  35. package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +70 -0
  36. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +49 -0
  37. package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +52 -0
  38. package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +58 -0
  39. package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +33 -0
  40. package/plans/reports/research-260314-1911-ppm-tech-stack.md +318 -0
  41. package/plans/reports/research-260314-1930-claude-code-integration.md +293 -0
  42. package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +305 -0
  43. package/plans/reports/researcher-260314-2232-ui-style.md +942 -0
  44. package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +745 -0
  45. package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +742 -0
  46. package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +423 -0
  47. package/plans/reports/tester-260314-2053-initial-test-suite.md +81 -0
  48. package/ppm.example.yaml +14 -0
  49. package/repomix-output.xml +23745 -0
  50. package/scripts/build.ts +13 -0
  51. package/src/cli/commands/chat-cmd.ts +259 -0
  52. package/src/cli/commands/config-cmd.ts +121 -0
  53. package/src/cli/commands/git-cmd.ts +315 -0
  54. package/src/cli/commands/init.ts +57 -0
  55. package/src/cli/commands/open.ts +19 -0
  56. package/src/cli/commands/projects.ts +100 -0
  57. package/src/cli/commands/start.ts +3 -0
  58. package/src/cli/commands/stop.ts +33 -0
  59. package/src/cli/utils/project-resolver.ts +27 -0
  60. package/src/index.ts +59 -0
  61. package/src/providers/claude-agent-sdk.ts +499 -0
  62. package/src/providers/claude-binary-finder.ts +256 -0
  63. package/src/providers/claude-code-cli.ts +413 -0
  64. package/src/providers/claude-process-registry.ts +106 -0
  65. package/src/providers/mock-provider.ts +171 -0
  66. package/src/providers/provider.interface.ts +10 -0
  67. package/src/providers/registry.ts +45 -0
  68. package/src/server/helpers/resolve-project.ts +22 -0
  69. package/src/server/index.ts +181 -0
  70. package/src/server/middleware/auth.ts +30 -0
  71. package/src/server/routes/chat.ts +153 -0
  72. package/src/server/routes/files.ts +168 -0
  73. package/src/server/routes/git.ts +261 -0
  74. package/src/server/routes/project-scoped.ts +27 -0
  75. package/src/server/routes/projects.ts +57 -0
  76. package/src/server/routes/static.ts +26 -0
  77. package/src/server/ws/chat.ts +130 -0
  78. package/src/server/ws/terminal.ts +89 -0
  79. package/src/services/chat.service.ts +110 -0
  80. package/src/services/claude-usage.service.ts +113 -0
  81. package/src/services/config.service.ts +90 -0
  82. package/src/services/file.service.ts +261 -0
  83. package/src/services/git-dirs.service.ts +112 -0
  84. package/src/services/git.service.ts +372 -0
  85. package/src/services/project.service.ts +107 -0
  86. package/src/services/slash-items.service.ts +184 -0
  87. package/src/services/terminal.service.ts +212 -0
  88. package/src/types/api.ts +37 -0
  89. package/src/types/chat.ts +92 -0
  90. package/src/types/config.ts +41 -0
  91. package/src/types/git.ts +50 -0
  92. package/src/types/project.ts +18 -0
  93. package/src/types/terminal.ts +20 -0
  94. package/src/web/app.tsx +168 -0
  95. package/src/web/components/auth/login-screen.tsx +88 -0
  96. package/src/web/components/chat/attachment-chips.tsx +55 -0
  97. package/src/web/components/chat/chat-placeholder.tsx +10 -0
  98. package/src/web/components/chat/chat-tab.tsx +301 -0
  99. package/src/web/components/chat/file-picker.tsx +126 -0
  100. package/src/web/components/chat/message-input.tsx +420 -0
  101. package/src/web/components/chat/message-list.tsx +838 -0
  102. package/src/web/components/chat/session-picker.tsx +139 -0
  103. package/src/web/components/chat/slash-command-picker.tsx +135 -0
  104. package/src/web/components/chat/usage-badge.tsx +186 -0
  105. package/src/web/components/editor/code-editor.tsx +329 -0
  106. package/src/web/components/editor/diff-viewer.tsx +276 -0
  107. package/src/web/components/editor/editor-placeholder.tsx +10 -0
  108. package/src/web/components/explorer/file-actions.tsx +191 -0
  109. package/src/web/components/explorer/file-tree.tsx +298 -0
  110. package/src/web/components/git/git-graph.tsx +727 -0
  111. package/src/web/components/git/git-placeholder.tsx +55 -0
  112. package/src/web/components/git/git-status-panel.tsx +850 -0
  113. package/src/web/components/layout/mobile-drawer.tsx +137 -0
  114. package/src/web/components/layout/mobile-nav.tsx +103 -0
  115. package/src/web/components/layout/sidebar.tsx +90 -0
  116. package/src/web/components/layout/tab-bar.tsx +152 -0
  117. package/src/web/components/layout/tab-content.tsx +85 -0
  118. package/src/web/components/projects/dir-suggest.tsx +152 -0
  119. package/src/web/components/projects/project-list.tsx +187 -0
  120. package/src/web/components/settings/settings-tab.tsx +57 -0
  121. package/src/web/components/terminal/terminal-placeholder.tsx +10 -0
  122. package/src/web/components/terminal/terminal-tab.tsx +133 -0
  123. package/src/web/components/ui/button.tsx +64 -0
  124. package/src/web/components/ui/context-menu.tsx +250 -0
  125. package/src/web/components/ui/dialog.tsx +156 -0
  126. package/src/web/components/ui/dropdown-menu.tsx +257 -0
  127. package/src/web/components/ui/input.tsx +21 -0
  128. package/src/web/components/ui/scroll-area.tsx +56 -0
  129. package/src/web/components/ui/separator.tsx +26 -0
  130. package/src/web/components/ui/sonner.tsx +40 -0
  131. package/src/web/components/ui/tabs.tsx +91 -0
  132. package/src/web/components/ui/tooltip.tsx +57 -0
  133. package/src/web/hooks/use-chat.ts +420 -0
  134. package/src/web/hooks/use-terminal.ts +182 -0
  135. package/src/web/hooks/use-url-sync.ts +66 -0
  136. package/src/web/hooks/use-websocket.ts +48 -0
  137. package/src/web/index.html +16 -0
  138. package/src/web/lib/api-client.ts +90 -0
  139. package/src/web/lib/file-support.ts +68 -0
  140. package/src/web/lib/utils.ts +6 -0
  141. package/src/web/lib/ws-client.ts +100 -0
  142. package/src/web/main.tsx +10 -0
  143. package/src/web/public/icon-192.svg +5 -0
  144. package/src/web/public/icon-512.svg +5 -0
  145. package/src/web/stores/file-store.ts +81 -0
  146. package/src/web/stores/project-store.ts +50 -0
  147. package/src/web/stores/settings-store.ts +65 -0
  148. package/src/web/stores/tab-store.ts +187 -0
  149. package/src/web/styles/globals.css +227 -0
  150. package/src/web/vite-env.d.ts +1 -0
  151. package/tests/integration/api/chat-routes.test.ts +95 -0
  152. package/tests/integration/claude-agent-sdk-integration.test.ts +228 -0
  153. package/tests/integration/ws/chat-websocket.test.ts +312 -0
  154. package/tests/test-setup.ts +5 -0
  155. package/tests/unit/providers/claude-agent-sdk.test.ts +339 -0
  156. package/tests/unit/providers/mock-provider.test.ts +143 -0
  157. package/tests/unit/services/chat-service.test.ts +100 -0
  158. package/tsconfig.json +32 -0
  159. package/vite.config.ts +62 -0
@@ -0,0 +1,742 @@
1
+ # Opcode Deep Analysis: Claude Code CLI Integration & Architecture
2
+
3
+ **Date:** 2026-03-15
4
+ **Scope:** Codebase architecture, Claude Code CLI integration, agent system, web server design
5
+ **Status:** Complete
6
+
7
+ ---
8
+
9
+ ## Executive Summary
10
+
11
+ Opcode is a sophisticated desktop/web application (Tauri + React + Rust) that provides multi-modal access to Claude Code CLI. Key architectural patterns worth adopting for PPM:
12
+
13
+ 1. **Robust Binary Discovery** - 19-point fallback chain for locating claude CLI across macOS/Windows/Linux
14
+ 2. **Session-Scoped Event Architecture** - Proper isolation of concurrent sessions using namespaced DOM events
15
+ 3. **Stream-JSON Parsing** - Clean abstraction for parsing Claude's output format
16
+ 4. **Agent System with Database Persistence** - CRUD ops for reusable agents with hooks and capabilities
17
+ 5. **Dual-Mode Execution** - Desktop (Tauri IPC) + Web (REST API + WebSocket) with clean provider pattern
18
+
19
+ ---
20
+
21
+ ## 1. Architecture Overview
22
+
23
+ ### 1.1 Tech Stack
24
+ - **Frontend:** React 18.3, TypeScript, Zustand (state), Tauri v2 / Web mode
25
+ - **Backend:** Rust (Tauri commands), Tokio async runtime, Axum web server
26
+ - **Data:** SQLite for agents DB, JSONL for session history
27
+ - **Build:** Vite, Bun, Cargo
28
+ - **Process Management:** tokio::process for subprocess spawning
29
+
30
+ ### 1.2 Directory Structure
31
+ ```
32
+ opcode/
33
+ ├── src/ # React/TS frontend
34
+ │ ├── components/
35
+ │ │ ├── ClaudeCodeSession.tsx
36
+ │ │ └── claude-code-session/useClaudeMessages.ts
37
+ │ ├── lib/
38
+ │ │ └── apiAdapter.ts # Environment detection & WebSocket client
39
+ │ └── stores/
40
+ ├── src-tauri/src/
41
+ │ ├── main.rs # Tauri setup
42
+ │ ├── lib.rs
43
+ │ ├── claude_binary.rs # Binary discovery (694 lines)
44
+ │ ├── commands/
45
+ │ │ ├── claude.rs # Session execution (2342 lines) ✨ CRITICAL
46
+ │ │ ├── agents.rs # Agent management (1996 lines)
47
+ │ │ ├── mcp.rs
48
+ │ │ ├── proxy.rs
49
+ │ │ ├── storage.rs
50
+ │ │ └── slash_commands.rs
51
+ │ ├── checkpoint/ # Session checkpoints (restore to prior state)
52
+ │ ├── process/
53
+ │ │ └── registry.rs # Process lifecycle tracking
54
+ │ └── web_server.rs # HTTP/WS server (Axum)
55
+ ├── cc_agents/ # Pre-built agent configs
56
+ │ ├── unit-tests-bot.opcode.json
57
+ │ ├── git-commit-bot.opcode.json
58
+ │ └── security-scanner.opcode.json
59
+ └── web_server.design.md # Design doc with known issues ⚠️
60
+ ```
61
+
62
+ ---
63
+
64
+ ## 2. Claude Code CLI Integration (CRITICAL FOR PPM)
65
+
66
+ ### 2.1 Binary Discovery Pattern
67
+
68
+ **File:** `src-tauri/src/claude_binary.rs` (694 lines)
69
+
70
+ **Pattern:** 19-point fallback discovery chain:
71
+
72
+ ```rust
73
+ pub fn find_claude_binary(app_handle: &tauri::AppHandle) -> Result<String, String> {
74
+ // 1. Check database for stored path + user preference
75
+ // 2. Discover all system installations
76
+ // 3. Sort by version + source preference
77
+ // 4. Select best installation
78
+ }
79
+
80
+ fn discover_system_installations() -> Vec<ClaudeInstallation> {
81
+ // Tries in order:
82
+ // 1. which command (Unix) / where (Windows)
83
+ // 2. NVM_BIN env var (active NVM)
84
+ // 3. ~/.nvm/versions/node/*/bin/claude
85
+ // 4. /usr/local/bin/claude
86
+ // 5. /opt/homebrew/bin/claude
87
+ // 6. ~/.claude/local/claude
88
+ // 7. ~/.local/bin/claude
89
+ // 8. ~/.npm-global/bin/claude
90
+ // 9. ~/.yarn/bin/claude
91
+ // 10. ~/.bun/bin/claude
92
+ // 11. ~/bin/claude
93
+ // 12. ~/.config/yarn/global/node_modules/.bin/claude
94
+ // 13. PATH lookup
95
+ }
96
+ ```
97
+
98
+ **Key Features:**
99
+ - Version detection via `claude --version`
100
+ - Semantic version comparison for selection
101
+ - Handles shell aliases (parses "aliased to /path" output)
102
+ - Persists user selection to SQLite
103
+ - Cross-platform (macOS, Linux, Windows)
104
+
105
+ **PPM Opportunity:** Replace simple `which claude` with this robust discovery.
106
+
107
+ ### 2.2 Process Execution & Streaming
108
+
109
+ **File:** `src-tauri/src/commands/claude.rs` (2342 lines)
110
+
111
+ **Main execution functions:**
112
+
113
+ ```rust
114
+ #[tauri::command]
115
+ pub async fn execute_claude_code(
116
+ app: AppHandle,
117
+ project_path: String,
118
+ prompt: String,
119
+ model: String,
120
+ ) -> Result<(), String> {
121
+ let claude_path = find_claude_binary(&app)?;
122
+
123
+ let args = vec![
124
+ "-p".to_string(), prompt,
125
+ "--model".to_string(), model,
126
+ "--output-format".to_string(), "stream-json".to_string(),
127
+ "--verbose".to_string(),
128
+ "--dangerously-skip-permissions".to_string(),
129
+ ];
130
+
131
+ let cmd = create_system_command(&claude_path, args, &project_path);
132
+ spawn_claude_process(app, cmd, prompt, model, project_path).await
133
+ }
134
+ ```
135
+
136
+ **Key Args:**
137
+ - `-p <prompt>` - User message
138
+ - `--model sonnet|opus` - Model selection
139
+ - `--output-format stream-json` - Structured JSON output (CRITICAL)
140
+ - `--verbose` - Detailed logging
141
+ - `--dangerously-skip-permissions` - Web/API mode only
142
+ - `-c` - Continue existing session
143
+ - `--resume <session_id>` - Resume from checkpoint
144
+
145
+ **Output Format (stream-json):**
146
+
147
+ Each line is a JSON object:
148
+
149
+ ```json
150
+ { "type": "system", "subtype": "init", "session_id": "uuid-here", ... }
151
+ { "type": "text", "content": "Hello" }
152
+ { "type": "tool_use", "tool": "bash", "input": { "command": "ls" } }
153
+ { "type": "response", "message": { "usage": { "input_tokens": 100, "output_tokens": 50 } } }
154
+ ```
155
+
156
+ ### 2.3 Session-Scoped Event Architecture
157
+
158
+ **Problem in opcode:** Generic events cause cross-session interference
159
+
160
+ **Solution in Tauri (working):**
161
+
162
+ ```rust
163
+ // In claude.rs spawn_claude_process()
164
+ if let Some(ref session_id) = *session_id_holder.lock().unwrap() {
165
+ // BOTH generic + session-scoped events for compatibility
166
+ let _ = app.emit(&format!("claude-output:{}", session_id), &line);
167
+ let _ = app.emit("claude-output", &line);
168
+ }
169
+ ```
170
+
171
+ **Frontend (useClaudeMessages.ts):**
172
+
173
+ ```typescript
174
+ // Listens on session-scoped events
175
+ await listen(`claude-output:${sessionId}`, handleOutput);
176
+ ```
177
+
178
+ **Critical Issue:** Web server (apiAdapter.ts) only dispatches generic DOM events → all sessions interfere.
179
+
180
+ **PPM Fix:**
181
+
182
+ ```typescript
183
+ // In web mode, dispatch both:
184
+ window.dispatchEvent(new CustomEvent('claude-output', { detail }));
185
+ window.dispatchEvent(new CustomEvent(`claude-output:${sessionId}`, { detail }));
186
+ ```
187
+
188
+ ### 2.4 Process Registry & Lifecycle Management
189
+
190
+ **File:** `src-tauri/src/process/registry.rs`
191
+
192
+ Tracks concurrent processes:
193
+
194
+ ```rust
195
+ pub struct ProcessRegistry {
196
+ processes: Arc<Mutex<HashMap<i64, ProcessHandle>>>,
197
+ }
198
+
199
+ pub struct ProcessInfo {
200
+ run_id: i64,
201
+ process_type: ProcessType, // AgentRun | ClaudeSession
202
+ pid: u32,
203
+ started_at: DateTime<Utc>,
204
+ project_path: String,
205
+ }
206
+ ```
207
+
208
+ **Usage in claude.rs:**
209
+
210
+ ```rust
211
+ // Register session after init message arrives
212
+ registry.register_claude_session(
213
+ claude_session_id,
214
+ pid,
215
+ project_path,
216
+ prompt,
217
+ model
218
+ )?;
219
+
220
+ // Unregister on completion
221
+ registry.unregister_process(run_id);
222
+ ```
223
+
224
+ **Killing processes:**
225
+
226
+ ```rust
227
+ pub async fn cancel_claude_execution() {
228
+ // Method 1: Find in registry by session ID
229
+ let process_info = registry.get_claude_session_by_id(&session_id)?;
230
+ registry.kill_process(process_info.run_id).await?;
231
+
232
+ // Method 2: Legacy via ClaudeProcessState
233
+ let mut child = claude_state.current_process.lock().await;
234
+ child.kill().await?;
235
+
236
+ // Method 3: System fallback
237
+ std::process::Command::new("kill").args(["-KILL", &pid.to_string()]).output();
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## 3. Agent System Design
244
+
245
+ **File:** `src-tauri/src/commands/agents.rs` (1996 lines)
246
+
247
+ ### 3.1 Agent Structure
248
+
249
+ ```rust
250
+ pub struct Agent {
251
+ pub id: Option<i64>,
252
+ pub name: String,
253
+ pub icon: String,
254
+ pub system_prompt: String,
255
+ pub default_task: Option<String>,
256
+ pub model: String,
257
+ pub enable_file_read: bool,
258
+ pub enable_file_write: bool,
259
+ pub enable_network: bool,
260
+ pub hooks: Option<String>, // JSON serialized
261
+ pub created_at: String,
262
+ pub updated_at: String,
263
+ }
264
+ ```
265
+
266
+ ### 3.2 Agent Execution Run
267
+
268
+ ```rust
269
+ pub struct AgentRun {
270
+ pub id: Option<i64>,
271
+ pub agent_id: i64,
272
+ pub agent_name: String,
273
+ pub task: String,
274
+ pub model: String,
275
+ pub project_path: String,
276
+ pub session_id: String, // UUID from Claude Code
277
+ pub status: String, // 'pending', 'running', 'completed', 'failed'
278
+ pub pid: Option<u32>,
279
+ pub process_started_at: Option<String>,
280
+ pub created_at: String,
281
+ pub completed_at: Option<String>,
282
+ }
283
+ ```
284
+
285
+ ### 3.3 Metrics from JSONL
286
+
287
+ ```rust
288
+ pub struct AgentRunMetrics {
289
+ pub duration_ms: Option<i64>,
290
+ pub total_tokens: Option<i64>,
291
+ pub cost_usd: Option<f64>,
292
+ pub message_count: Option<i64>,
293
+ }
294
+
295
+ impl AgentRunMetrics {
296
+ pub fn from_jsonl(jsonl_content: &str) -> Self {
297
+ // Parse JSONL file from ~/.claude/projects/{encoded_path}/{session_id}.jsonl
298
+ // Extract:
299
+ // - timestamps → duration_ms
300
+ // - usage.input_tokens + usage.output_tokens → total_tokens
301
+ // - cost field → cost_usd
302
+ // - line count → message_count
303
+ }
304
+ }
305
+ ```
306
+
307
+ ### 3.4 .opcode.json Agent Config Format
308
+
309
+ **Example:** `cc_agents/unit-tests-bot.opcode.json`
310
+
311
+ ```json
312
+ {
313
+ "version": 1,
314
+ "agent": {
315
+ "name": "Unit Tests Bot",
316
+ "icon": "code",
317
+ "model": "opus",
318
+ "default_task": "Generate unit tests for this codebase.",
319
+ "system_prompt": "# System Prompt with multi-agent workflow...",
320
+ "enable_file_read": true,
321
+ "enable_file_write": true,
322
+ "enable_network": false,
323
+ "hooks": null
324
+ },
325
+ "exported_at": "2025-06-23T14:29:51.009370+00:00"
326
+ }
327
+ ```
328
+
329
+ **Agents are fully customizable templates** - users can create agents with:
330
+ - Custom system prompts (multi-agent orchestration patterns)
331
+ - Capability toggles (file access, network, etc.)
332
+ - Model selection (sonnet vs opus for cost/quality tradeoff)
333
+ - Task templates
334
+ - Hook configurations
335
+
336
+ ---
337
+
338
+ ## 4. Web Server Architecture (Axum + WebSocket)
339
+
340
+ **File:** `src-tauri/src/web_server.rs`
341
+
342
+ ### 4.1 Dual-Mode Execution
343
+
344
+ **Tauri Desktop Mode:**
345
+ - Direct process spawning via `std::process::Command`
346
+ - IPC events via Tauri's event system
347
+ - Session isolation via namespaced events
348
+
349
+ **Web Server Mode:**
350
+ - HTTP/WS via Axum server
351
+ - DOM events as fallback
352
+ - Subprocess execution in separate tokio runtime
353
+
354
+ ### 4.2 WebSocket Flow
355
+
356
+ ```typescript
357
+ // Frontend request
358
+ const req = {
359
+ command_type: "execute|continue|resume",
360
+ project_path: "/path/to/project",
361
+ prompt: "user message",
362
+ model: "sonnet",
363
+ session_id: "uuid-for-resume"
364
+ };
365
+
366
+ // Backend streams responses as newline-delimited JSON
367
+ // Each line is a claude stream-json message:
368
+ { "type": "system", "subtype": "init", "session_id": "..." }
369
+ { "type": "text", "content": "response text" }
370
+ ```
371
+
372
+ ### 4.3 Critical Issues in Web Server (⚠️ DOCUMENTED IN web_server.design.md)
373
+
374
+ **1. Session-Scoped Event Dispatching (CRITICAL)**
375
+ - Backend only dispatches generic `claude-output` events
376
+ - Frontend expects session-scoped `claude-output:${sessionId}` events
377
+ - **Result:** Multiple concurrent sessions interfere with each other
378
+
379
+ **2. Process Management (CRITICAL)**
380
+ - `cancel_claude_execution` endpoint is a stub
381
+ - Doesn't actually terminate processes
382
+ - No process handle tracking
383
+
384
+ **3. stderr Handling (MEDIUM)**
385
+ - Only captures stdout
386
+ - Errors not displayed to user
387
+
388
+ **4. Missing claude-cancelled Events (MEDIUM)**
389
+ - No cancellation event support
390
+
391
+ ---
392
+
393
+ ## 5. Comparison: Opcode vs PPM Approach
394
+
395
+ | Aspect | Opcode | PPM (current) | PPM (proposed) |
396
+ |--------|--------|---------------|----------------|
397
+ | **Binary Discovery** | 19-point chain with version detection | `which claude` | Adopt opcode's robust chain |
398
+ | **Session Management** | Database + Process Registry | Map<sessionId, stream> | Registry + namespaced events |
399
+ | **Event Architecture** | Tauri IPC + DOM events (session-scoped) | Generic chat events | Adopt session-scoped pattern |
400
+ | **Stream-JSON Parsing** | Full line-by-line parser | Chunks/text accumulation | Structured JSON line parsing |
401
+ | **Agent System** | Full CRUD with DB persistence | Not yet | Design doc exists |
402
+ | **Process Termination** | Kill via registry + system fallback | Not yet | Implement registry pattern |
403
+ | **Web Mode** | Axum + WebSocket (has known issues) | HTTP streaming | Fix session isolation issues |
404
+
405
+ ---
406
+
407
+ ## 6. PPM Integration Recommendations
408
+
409
+ ### 6.1 Phase 1: Binary Discovery (HIGH PRIORITY)
410
+
411
+ **Adopt opcode's claude_binary.rs pattern:**
412
+
413
+ ```typescript
414
+ // PPM: src/services/claude-binary.ts
415
+ export async function findClaudeBinary(): Promise<string> {
416
+ // 1. Check stored path in IndexedDB
417
+ // 2. Run 'which claude'
418
+ // 3. Check standard paths (Homebrew, NVM, etc.)
419
+ // 4. Return highest version found
420
+ }
421
+ ```
422
+
423
+ **Why:** Current PPM likely uses simple shell command, fails on non-standard installations.
424
+
425
+ ### 6.2 Phase 2: Session-Scoped Events (HIGH PRIORITY)
426
+
427
+ **Current issue in PPM:** All sessions share same event stream
428
+
429
+ **Fix (for web mode):**
430
+
431
+ ```typescript
432
+ // src/server/routes/chat.ts
433
+ ws.on('message', async (msg) => {
434
+ const { sessionId, prompt } = JSON.parse(msg);
435
+
436
+ for await (const event of executeClaudeCode(prompt)) {
437
+ // DISPATCH BOTH generic + scoped events
438
+ const jsonLine = JSON.stringify(event);
439
+
440
+ // Generic for backward compat
441
+ ws.send(jsonLine);
442
+
443
+ // Scoped for proper isolation
444
+ const scopedEvent = JSON.stringify({
445
+ ...event,
446
+ _sessionId: sessionId
447
+ });
448
+ ws.send(scopedEvent);
449
+ }
450
+ });
451
+ ```
452
+
453
+ **Frontend listening:**
454
+
455
+ ```typescript
456
+ const handleStreamMessage = (event) => {
457
+ if (event._sessionId === currentSessionId) {
458
+ // Only process messages for this session
459
+ updateMessages(event);
460
+ }
461
+ };
462
+ ```
463
+
464
+ ### 6.3 Phase 3: Stream-JSON Parsing (HIGH PRIORITY)
465
+
466
+ **Structure output properly:**
467
+
468
+ ```typescript
469
+ export interface ClaudeStreamMessage {
470
+ type: "system" | "text" | "tool_use" | "response" | "error";
471
+
472
+ // Common fields
473
+ timestamp?: string;
474
+
475
+ // For "system"
476
+ subtype?: "init" | "update";
477
+ session_id?: string;
478
+
479
+ // For "text"
480
+ content?: string;
481
+
482
+ // For "tool_use"
483
+ tool?: string;
484
+ input?: Record<string, unknown>;
485
+
486
+ // For "response"
487
+ message?: {
488
+ usage?: {
489
+ input_tokens: number;
490
+ output_tokens: number;
491
+ };
492
+ };
493
+
494
+ // For "error"
495
+ error?: string;
496
+ }
497
+ ```
498
+
499
+ ### 6.4 Phase 4: Process Registry (MEDIUM PRIORITY)
500
+
501
+ **Implement kill support:**
502
+
503
+ ```typescript
504
+ // src/server/ws/chat.ts
505
+ const activeProcesses = new Map<string, Child>();
506
+
507
+ export async function executeClaudeCode(sessionId, prompt) {
508
+ const proc = Bun.spawn(['claude', ...args], { stdout: 'pipe', stderr: 'pipe' });
509
+ activeProcesses.set(sessionId, proc);
510
+
511
+ // ... stream processing ...
512
+
513
+ activeProcesses.delete(sessionId);
514
+ }
515
+
516
+ export async function cancelExecution(sessionId: string) {
517
+ const proc = activeProcesses.get(sessionId);
518
+ if (proc) {
519
+ proc.kill();
520
+ activeProcesses.delete(sessionId);
521
+ }
522
+ }
523
+ ```
524
+
525
+ ### 6.5 Phase 5: Agent System (LOWER PRIORITY)
526
+
527
+ **PPM doesn't need full CRUD yet, but consider:**
528
+
529
+ - Store reusable system prompts in database
530
+ - Version agent configs
531
+ - Allow import/export of agent definitions
532
+ - Track agent execution metrics
533
+
534
+ ---
535
+
536
+ ## 7. Code Patterns Worth Adopting
537
+
538
+ ### 7.1 Environment Detection
539
+
540
+ **File:** `src/lib/apiAdapter.ts`
541
+
542
+ ```typescript
543
+ export function getEnvironmentInfo() {
544
+ return {
545
+ isTauri: !!(window as any).__TAURI__,
546
+ isWeb: typeof window !== 'undefined',
547
+ isNode: typeof process !== 'undefined' && process.versions?.node,
548
+ };
549
+ }
550
+ ```
551
+
552
+ **PPM Use:** Detect execution context for proper event handling.
553
+
554
+ ### 7.2 Async Generator Pattern for Streaming
555
+
556
+ **From claude-code-cli.ts:**
557
+
558
+ ```typescript
559
+ async *sendMessage(sessionId: string, message: string): AsyncIterable<ChatEvent> {
560
+ const proc = Bun.spawn(['claude', ...], { stdout: 'pipe', stderr: 'pipe' });
561
+
562
+ try {
563
+ const reader = proc.stdout?.getReader();
564
+ const decoder = new TextDecoder();
565
+ let buffer = "";
566
+
567
+ while (true) {
568
+ const { done, value } = await reader.read();
569
+ if (done) break;
570
+
571
+ buffer += decoder.decode(value, { stream: true });
572
+ const lines = buffer.split("\n");
573
+ buffer = lines.pop() ?? "";
574
+
575
+ for (const line of lines) {
576
+ const event = JSON.parse(line);
577
+ yield mapCliEventToChatEvent(event);
578
+ }
579
+ }
580
+ } catch (e) {
581
+ yield { type: "error", message: String(e) };
582
+ }
583
+ }
584
+ ```
585
+
586
+ **PPM Use:** Clean streaming abstraction, works with both CLI and SDK.
587
+
588
+ ### 7.3 Provider Pattern
589
+
590
+ **From src/providers/registry.ts:**
591
+
592
+ ```typescript
593
+ interface AIProvider {
594
+ createSession(config: SessionConfig): Promise<Session>;
595
+ resumeSession(sessionId: string): Promise<Session>;
596
+ sendMessage(sessionId: string, message: string): AsyncIterable<ChatEvent>;
597
+ listSessions(): Promise<SessionInfo[]>;
598
+ deleteSession(sessionId: string): Promise<void>;
599
+ }
600
+
601
+ class ProviderRegistry {
602
+ get(id: string): AIProvider | undefined;
603
+ list(): ProviderInfo[];
604
+ }
605
+
606
+ // Multiple implementations:
607
+ // - ClaudeCodeCliProvider (spawns CLI)
608
+ // - ClaudeAgentSdkProvider (uses SDK)
609
+ // - MockProvider (for testing)
610
+ ```
611
+
612
+ **PPM Use:** Allow swapping providers without UI changes.
613
+
614
+ ---
615
+
616
+ ## 8. Lessons from Known Issues
617
+
618
+ ### 8.1 Web Server Critical Issues (from web_server.design.md)
619
+
620
+ **Issue 1: Session-Scoped Events**
621
+
622
+ > "The UI expects session-specific events like `claude-output:${sessionId}` but the backend only dispatches generic events like `claude-output`. Impact: Session isolation doesn't work — all sessions receive all events."
623
+
624
+ **Lesson:** Always namespace state/events by logical grouping (sessionId, agentId, etc.)
625
+
626
+ **Issue 2: Process Management**
627
+
628
+ > "The cancel endpoint is just a stub that doesn't actually terminate running Claude processes."
629
+
630
+ **Lesson:** Don't mock critical functionality; implement fully or fail clearly.
631
+
632
+ **Issue 3: stderr Handling**
633
+
634
+ > "Claude processes can write errors to stderr, but the web server only captures stdout."
635
+
636
+ **Lesson:** Always capture both stdout + stderr; both contain actionable information.
637
+
638
+ ---
639
+
640
+ ## 9. File-by-File Reference
641
+
642
+ ### Essential Files for PPM
643
+
644
+ | File | Lines | Purpose | Priority |
645
+ |------|-------|---------|----------|
646
+ | `claude_binary.rs` | 694 | Binary discovery algorithm | HIGH |
647
+ | `commands/claude.rs` | 2342 | Execution, streaming, cancellation | HIGH |
648
+ | `process/registry.rs` | ~200 | Process lifecycle tracking | MEDIUM |
649
+ | `web_server.design.md` | - | Known issues + architecture | HIGH (reference) |
650
+ | `src/providers/claude-code-cli.ts` | ~180 | CLI streaming implementation | MEDIUM |
651
+ | `src/providers/claude-agent-sdk.ts` | ~195 | SDK provider pattern | LOW |
652
+ | `cc_agents/*.opcode.json` | - | Agent config format | REFERENCE |
653
+
654
+ ### Code Snippets Worth Copy-Pasting
655
+
656
+ 1. **Binary discovery fallback loop** (lines 156-166 in claude_binary.rs)
657
+ 2. **Stream-JSON line parser** (lines 1230-1280 in claude.rs)
658
+ 3. **Session registration on init** (lines 1244-1260 in claude.rs)
659
+ 4. **Async generator stream processing** (claude-code-cli.ts lines 108-150)
660
+
661
+ ---
662
+
663
+ ## 10. Unresolved Questions & Clarifications Needed
664
+
665
+ 1. **Session Persistence:** Does opcode persist session history across restarts? (Appears to via JSONL files in ~/.claude/projects/)
666
+
667
+ 2. **Checkpoint System:** What's the use case for checkpoints? (Allow branching/forking sessions to different states)
668
+
669
+ 3. **Hooks Configuration:** What do agent hooks do? (JSON field exists but not documented in code)
670
+
671
+ 4. **Cost Calculation:** Where does `cost` field in JSONL come from? (Not obvious from stream-json parsing)
672
+
673
+ 5. **Model Fallback:** If user specifies "opus" but only "sonnet" CLI is available, what happens? (Likely CLI error)
674
+
675
+ 6. **Web Server Status:** Is web_server.rs actively maintained? (design.md marked as having critical issues)
676
+
677
+ 7. **CLAUDE.md Discovery:** How is CLAUDE.md path resolved in multi-root workspaces? (Searches recursively, stops at first found)
678
+
679
+ ---
680
+
681
+ ## 11. Summary & Next Steps
682
+
683
+ ### Key Takeaways for PPM
684
+
685
+ 1. **Adopt robust binary discovery** - 19-point chain beats simple `which`
686
+ 2. **Use session-scoped events** - Prevents multi-session interference
687
+ 3. **Parse stream-json line-by-line** - Cleaner than chunk accumulation
688
+ 4. **Implement process registry** - Track/cancel/monitor executions
689
+ 5. **Support both CLI & SDK** - Provider pattern allows flexibility
690
+ 6. **Document known limitations** - opcode's web_server.design.md is honest about gaps
691
+
692
+ ### Recommended Implementation Order
693
+
694
+ 1. **Phase 1:** Adopt claude_binary.rs pattern
695
+ 2. **Phase 2:** Implement session-scoped events for web mode
696
+ 3. **Phase 3:** Full stream-json parsing
697
+ 4. **Phase 4:** Process registry for termination
698
+ 5. **Phase 5:** Agent system (lower urgency)
699
+
700
+ ### Files to Reference During Implementation
701
+
702
+ - Copy binary discovery logic from `claude_binary.rs`
703
+ - Study `commands/claude.rs` for execution patterns
704
+ - Review `web_server.design.md` section 4 for event architecture
705
+ - Use `providers/claude-code-cli.ts` as streaming template
706
+
707
+ ---
708
+
709
+ ## Appendix: Quick Reference
710
+
711
+ **Stream-JSON Message Types:**
712
+ ```
713
+ system → init message, session_id first
714
+ text → assistant response chunks
715
+ tool_use → tool invocation
716
+ response → final message with usage/cost
717
+ error → execution error
718
+ ```
719
+
720
+ **Claude CLI Common Args:**
721
+ ```bash
722
+ claude -p "prompt text" --model opus --output-format stream-json
723
+ claude -c -p "follow-up" --model opus --output-format stream-json
724
+ claude --resume <session_id> -p "continuation" --output-format stream-json
725
+ claude --cancel <session_id> # Not available in recent versions
726
+ ```
727
+
728
+ **Session File Locations:**
729
+ ```
730
+ ~/.claude/projects/{encoded_path}/{session_id}.jsonl
731
+ ~/.claude/sessions/ # Deprecated?
732
+ ~/.claude/CLAUDE.md
733
+ ~/.claude/settings.json
734
+ ```
735
+
736
+ **Database Schema (agents.db):**
737
+ ```sql
738
+ agents (id, name, icon, system_prompt, default_task, model,
739
+ enable_file_read, enable_file_write, enable_network, hooks, created_at, updated_at)
740
+ agent_runs (id, agent_id, task, model, project_path, session_id, status, pid, ...)
741
+ app_settings (key, value, created_at, updated_at)
742
+ ```