@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.
- package/.claude/agent-memory/tester/MEMORY.md +3 -0
- package/.claude/agent-memory/tester/project-ppm-test-conventions.md +32 -0
- package/.env.example +1 -0
- package/.github/workflows/release.yml +46 -0
- package/README.md +349 -0
- package/bun.lock +1217 -0
- package/components.json +21 -0
- package/docs/code-standards.md +574 -0
- package/docs/codebase-summary.md +294 -0
- package/docs/deployment-guide.md +631 -0
- package/docs/design-guidelines.md +661 -0
- package/docs/project-overview-pdr.md +142 -0
- package/docs/project-roadmap.md +400 -0
- package/docs/system-architecture.md +459 -0
- package/package.json +68 -0
- package/plans/260314-2009-ppm-implementation/phase-01-project-skeleton.md +81 -0
- package/plans/260314-2009-ppm-implementation/phase-02-backend-core.md +148 -0
- package/plans/260314-2009-ppm-implementation/phase-03-frontend-shell.md +256 -0
- package/plans/260314-2009-ppm-implementation/phase-04-file-explorer-editor.md +120 -0
- package/plans/260314-2009-ppm-implementation/phase-05-web-terminal.md +174 -0
- package/plans/260314-2009-ppm-implementation/phase-06-git-integration.md +244 -0
- package/plans/260314-2009-ppm-implementation/phase-07-ai-chat.md +242 -0
- package/plans/260314-2009-ppm-implementation/phase-08-cli-commands.md +143 -0
- package/plans/260314-2009-ppm-implementation/phase-09-pwa-build-deploy.md +209 -0
- package/plans/260314-2009-ppm-implementation/phase-10-testing.md +311 -0
- package/plans/260314-2009-ppm-implementation/plan.md +202 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-01-backend-project-router.md +145 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-02-frontend-api-migration.md +107 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-03-per-project-tabs.md +100 -0
- package/plans/260315-0356-project-scoped-api-refactor/phase-04-websocket-migration.md +66 -0
- package/plans/260315-0356-project-scoped-api-refactor/plan.md +87 -0
- package/plans/reports/brainstorm-260314-1938-final-techstack.md +342 -0
- package/plans/reports/docs-manager-260315-1314-documentation-creation.md +386 -0
- package/plans/reports/fullstack-developer-260314-2252-phase-02-backend-core.md +57 -0
- package/plans/reports/fullstack-developer-260314-2253-phase-03-frontend-shell.md +70 -0
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-api-terminal-ws.md +49 -0
- package/plans/reports/fullstack-developer-260314-2300-phase-04-05-file-explorer-editor-terminal.md +52 -0
- package/plans/reports/fullstack-developer-260314-2307-ai-chat-phase7.md +58 -0
- package/plans/reports/fullstack-developer-260314-2307-phase-06-git-integration.md +33 -0
- package/plans/reports/research-260314-1911-ppm-tech-stack.md +318 -0
- package/plans/reports/research-260314-1930-claude-code-integration.md +293 -0
- package/plans/reports/researcher-260314-2232-node-pty-bun-crash-analysis.md +305 -0
- package/plans/reports/researcher-260314-2232-ui-style.md +942 -0
- package/plans/reports/researcher-260315-0300-opcode-claude-interaction.md +745 -0
- package/plans/reports/researcher-260315-0303-opcode-deep-analysis.md +742 -0
- package/plans/reports/researcher-260315-0305-claude-agent-sdk-github-research.md +423 -0
- package/plans/reports/tester-260314-2053-initial-test-suite.md +81 -0
- package/ppm.example.yaml +14 -0
- package/repomix-output.xml +23745 -0
- package/scripts/build.ts +13 -0
- package/src/cli/commands/chat-cmd.ts +259 -0
- package/src/cli/commands/config-cmd.ts +121 -0
- package/src/cli/commands/git-cmd.ts +315 -0
- package/src/cli/commands/init.ts +57 -0
- package/src/cli/commands/open.ts +19 -0
- package/src/cli/commands/projects.ts +100 -0
- package/src/cli/commands/start.ts +3 -0
- package/src/cli/commands/stop.ts +33 -0
- package/src/cli/utils/project-resolver.ts +27 -0
- package/src/index.ts +59 -0
- package/src/providers/claude-agent-sdk.ts +499 -0
- package/src/providers/claude-binary-finder.ts +256 -0
- package/src/providers/claude-code-cli.ts +413 -0
- package/src/providers/claude-process-registry.ts +106 -0
- package/src/providers/mock-provider.ts +171 -0
- package/src/providers/provider.interface.ts +10 -0
- package/src/providers/registry.ts +45 -0
- package/src/server/helpers/resolve-project.ts +22 -0
- package/src/server/index.ts +181 -0
- package/src/server/middleware/auth.ts +30 -0
- package/src/server/routes/chat.ts +153 -0
- package/src/server/routes/files.ts +168 -0
- package/src/server/routes/git.ts +261 -0
- package/src/server/routes/project-scoped.ts +27 -0
- package/src/server/routes/projects.ts +57 -0
- package/src/server/routes/static.ts +26 -0
- package/src/server/ws/chat.ts +130 -0
- package/src/server/ws/terminal.ts +89 -0
- package/src/services/chat.service.ts +110 -0
- package/src/services/claude-usage.service.ts +113 -0
- package/src/services/config.service.ts +90 -0
- package/src/services/file.service.ts +261 -0
- package/src/services/git-dirs.service.ts +112 -0
- package/src/services/git.service.ts +372 -0
- package/src/services/project.service.ts +107 -0
- package/src/services/slash-items.service.ts +184 -0
- package/src/services/terminal.service.ts +212 -0
- package/src/types/api.ts +37 -0
- package/src/types/chat.ts +92 -0
- package/src/types/config.ts +41 -0
- package/src/types/git.ts +50 -0
- package/src/types/project.ts +18 -0
- package/src/types/terminal.ts +20 -0
- package/src/web/app.tsx +168 -0
- package/src/web/components/auth/login-screen.tsx +88 -0
- package/src/web/components/chat/attachment-chips.tsx +55 -0
- package/src/web/components/chat/chat-placeholder.tsx +10 -0
- package/src/web/components/chat/chat-tab.tsx +301 -0
- package/src/web/components/chat/file-picker.tsx +126 -0
- package/src/web/components/chat/message-input.tsx +420 -0
- package/src/web/components/chat/message-list.tsx +838 -0
- package/src/web/components/chat/session-picker.tsx +139 -0
- package/src/web/components/chat/slash-command-picker.tsx +135 -0
- package/src/web/components/chat/usage-badge.tsx +186 -0
- package/src/web/components/editor/code-editor.tsx +329 -0
- package/src/web/components/editor/diff-viewer.tsx +276 -0
- package/src/web/components/editor/editor-placeholder.tsx +10 -0
- package/src/web/components/explorer/file-actions.tsx +191 -0
- package/src/web/components/explorer/file-tree.tsx +298 -0
- package/src/web/components/git/git-graph.tsx +727 -0
- package/src/web/components/git/git-placeholder.tsx +55 -0
- package/src/web/components/git/git-status-panel.tsx +850 -0
- package/src/web/components/layout/mobile-drawer.tsx +137 -0
- package/src/web/components/layout/mobile-nav.tsx +103 -0
- package/src/web/components/layout/sidebar.tsx +90 -0
- package/src/web/components/layout/tab-bar.tsx +152 -0
- package/src/web/components/layout/tab-content.tsx +85 -0
- package/src/web/components/projects/dir-suggest.tsx +152 -0
- package/src/web/components/projects/project-list.tsx +187 -0
- package/src/web/components/settings/settings-tab.tsx +57 -0
- package/src/web/components/terminal/terminal-placeholder.tsx +10 -0
- package/src/web/components/terminal/terminal-tab.tsx +133 -0
- package/src/web/components/ui/button.tsx +64 -0
- package/src/web/components/ui/context-menu.tsx +250 -0
- package/src/web/components/ui/dialog.tsx +156 -0
- package/src/web/components/ui/dropdown-menu.tsx +257 -0
- package/src/web/components/ui/input.tsx +21 -0
- package/src/web/components/ui/scroll-area.tsx +56 -0
- package/src/web/components/ui/separator.tsx +26 -0
- package/src/web/components/ui/sonner.tsx +40 -0
- package/src/web/components/ui/tabs.tsx +91 -0
- package/src/web/components/ui/tooltip.tsx +57 -0
- package/src/web/hooks/use-chat.ts +420 -0
- package/src/web/hooks/use-terminal.ts +182 -0
- package/src/web/hooks/use-url-sync.ts +66 -0
- package/src/web/hooks/use-websocket.ts +48 -0
- package/src/web/index.html +16 -0
- package/src/web/lib/api-client.ts +90 -0
- package/src/web/lib/file-support.ts +68 -0
- package/src/web/lib/utils.ts +6 -0
- package/src/web/lib/ws-client.ts +100 -0
- package/src/web/main.tsx +10 -0
- package/src/web/public/icon-192.svg +5 -0
- package/src/web/public/icon-512.svg +5 -0
- package/src/web/stores/file-store.ts +81 -0
- package/src/web/stores/project-store.ts +50 -0
- package/src/web/stores/settings-store.ts +65 -0
- package/src/web/stores/tab-store.ts +187 -0
- package/src/web/styles/globals.css +227 -0
- package/src/web/vite-env.d.ts +1 -0
- package/tests/integration/api/chat-routes.test.ts +95 -0
- package/tests/integration/claude-agent-sdk-integration.test.ts +228 -0
- package/tests/integration/ws/chat-websocket.test.ts +312 -0
- package/tests/test-setup.ts +5 -0
- package/tests/unit/providers/claude-agent-sdk.test.ts +339 -0
- package/tests/unit/providers/mock-provider.test.ts +143 -0
- package/tests/unit/services/chat-service.test.ts +100 -0
- package/tsconfig.json +32 -0
- 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
|
+
```
|