agentgui 1.0.33 → 1.0.35

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.
@@ -0,0 +1,287 @@
1
+ # State Machine Implementation - Checklist & Reference
2
+
3
+ ## ✅ Completed Features
4
+
5
+ ### Core State Machine
6
+ - [x] StateManager class with 9 defined states
7
+ - [x] State transition validation
8
+ - [x] Invalid transition guards (throw errors)
9
+ - [x] State history tracking with timestamps
10
+ - [x] Reason/metadata for each transition
11
+ - [x] Automatic 120-second timeout watchdog
12
+ - [x] Promise-based completion API
13
+ - [x] Terminal state detection
14
+ - [x] State history retrieval
15
+
16
+ ### Session Management
17
+ - [x] SessionStateStore global registry
18
+ - [x] Session creation with ID tracking
19
+ - [x] Session retrieval and validation
20
+ - [x] Active session filtering
21
+ - [x] Terminal session tracking
22
+ - [x] Automatic cleanup (>1 hour)
23
+ - [x] Diagnostic aggregation
24
+
25
+ ### Server Integration
26
+ - [x] Import StateManager in server.js
27
+ - [x] Create global SessionStateStore
28
+ - [x] Rewrite processMessage() to use state machine
29
+ - [x] Add state transitions for each step
30
+ - [x] Implement error handling with state tracking
31
+ - [x] Add getACP() timeout protection (60s)
32
+ - [x] Create /api/diagnostics/sessions endpoint
33
+ - [x] Add comprehensive logging
34
+
35
+ ### Database Fixes
36
+ - [x] Fix message content type handling (stringify objects)
37
+ - [x] Fix session response/error serialization
38
+ - [x] Fix event data JSON handling
39
+ - [x] Fix idempotencyKeys type conversion
40
+
41
+ ### Documentation
42
+ - [x] StateManager code comments
43
+ - [x] Architecture diagrams
44
+ - [x] Usage examples
45
+ - [x] Monitoring guide
46
+ - [x] Diagnostics explanation
47
+ - [x] Issue diagnosis (ACP hang)
48
+ - [x] Next steps guide
49
+
50
+ ---
51
+
52
+ ## 📊 State Machine States
53
+
54
+ ```
55
+ PENDING
56
+
57
+ ACQUIRING_ACP ← Connect to Claude Code ACP
58
+
59
+ ACP_ACQUIRED ← Connection established
60
+
61
+ SENDING_PROMPT ← Sending prompt to ACP
62
+
63
+ PROCESSING ← Processing response
64
+
65
+ COMPLETED ← ✅ Success
66
+
67
+ ERROR ← ❌ Any step failed (at any point)
68
+ TIMEOUT ← ❌ Exceeded 120s (automatic)
69
+ CANCELLED ← Stopped by user
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 🔍 Diagnostics Endpoint
75
+
76
+ **Endpoint**: `GET /api/diagnostics/sessions`
77
+
78
+ **Response Format**:
79
+ ```javascript
80
+ {
81
+ timestamp: ISO 8601 string,
82
+ activeSessions: number,
83
+ terminalSessions: number,
84
+ totalSessions: number,
85
+ active: [
86
+ {
87
+ sessionId: string,
88
+ state: string,
89
+ uptime: milliseconds
90
+ }
91
+ ],
92
+ recentTerminal: [
93
+ {
94
+ sessionId: string,
95
+ conversationId: string,
96
+ messageId: string,
97
+ state: 'completed'|'error'|'timeout'|'cancelled',
98
+ duration: '1234ms',
99
+ historyLength: number,
100
+ history: ['0ms: pending (initialized)', ...],
101
+ data: {
102
+ fullTextLength: number,
103
+ blocksCount: number,
104
+ error: null | string,
105
+ hasStackTrace: boolean
106
+ }
107
+ }
108
+ ]
109
+ }
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 🚀 Usage Examples
115
+
116
+ ### Create a Session
117
+ ```javascript
118
+ const stateManager = sessionStateStore.create(
119
+ sessionId,
120
+ conversationId,
121
+ messageId,
122
+ 120000 // timeout in ms
123
+ );
124
+ ```
125
+
126
+ ### Transition State
127
+ ```javascript
128
+ stateManager.transition(StateManager.STATES.ACQUIRING_ACP, {
129
+ reason: 'Starting ACP connection',
130
+ data: {}
131
+ });
132
+ ```
133
+
134
+ ### Check Current State
135
+ ```javascript
136
+ const state = stateManager.getState();
137
+ // 'pending' | 'acquiring_acp' | 'acp_acquired' | ...
138
+ ```
139
+
140
+ ### Get Full History
141
+ ```javascript
142
+ const history = stateManager.getHistory();
143
+ // Array of {state, timestamp, reason, details}
144
+ ```
145
+
146
+ ### Wait for Completion
147
+ ```javascript
148
+ try {
149
+ const result = await stateManager.waitForCompletion();
150
+ console.log(`Success in ${result.data.duration}`);
151
+ } catch (err) {
152
+ console.error(`Failed: ${err.message}`);
153
+ }
154
+ ```
155
+
156
+ ### Get Diagnostics
157
+ ```javascript
158
+ const diag = sessionStateStore.getDiagnostics();
159
+ console.log(`Active: ${diag.activeSessions}`);
160
+ console.log(`Terminal: ${diag.terminalSessions}`);
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 🛡️ Error Handling
166
+
167
+ ### Invalid Transition
168
+ ```javascript
169
+ // This will throw!
170
+ stateManager.transition(StateManager.STATES.COMPLETED, {});
171
+ // Error: "Invalid state transition: pending → completed. Valid: [acquiring_acp, cancelled]"
172
+ ```
173
+
174
+ ### Session Not Found
175
+ ```javascript
176
+ const manager = sessionStateStore.getOrThrow(sessionId);
177
+ // Throws if sessionId doesn't exist
178
+ ```
179
+
180
+ ### Timeout
181
+ ```javascript
182
+ // After 120 seconds in any non-terminal state:
183
+ // Automatically transitions to TIMEOUT state
184
+ ```
185
+
186
+ ---
187
+
188
+ ## 📝 Logging Output
189
+
190
+ ### State Transition Log
191
+ ```
192
+ [StateManager] sess-123 transitioned: pending → acquiring_acp (+1ms) | Starting ACP connection
193
+ [StateManager] sess-123 transitioned: acquiring_acp → acp_acquired (+25ms) | ACP connected
194
+ [StateManager] sess-123 transitioned: acp_acquired → sending_prompt (+0ms) | Sending to ACP
195
+ [StateManager] sess-123 transitioned: sending_prompt → processing (+100ms) | Processing response
196
+ [StateManager] sess-123 transitioned: processing → completed (+2145ms) | Response successfully generated
197
+ ```
198
+
199
+ ### Process Message Log
200
+ ```
201
+ [processMessage] Starting: conversationId=conv-123, sessionId=sess-456
202
+ [processMessage] Initial state: pending
203
+ [getACP] Step 1: Connecting to claude-code...
204
+ [getACP] Step 2: Connected, initializing...
205
+ [getACP] Step 3: Initialized, creating session...
206
+ [getACP] ✅ ACP connection ready for claude-code in /config
207
+ [processMessage] Sending prompt to ACP (45 chars)
208
+ [processMessage] ACP returned: stopReason=end_turn, fullText=12345 chars
209
+ [processMessage] ✅ Session completed: 2567ms
210
+ ```
211
+
212
+ ---
213
+
214
+ ## 🔧 Configuration
215
+
216
+ ### Timeouts
217
+ - **Session timeout**: 120 seconds (hardcoded)
218
+ - **ACP timeout**: 60 seconds (hardcoded in getACP)
219
+ - **Session cleanup TTL**: 3600000ms (1 hour)
220
+
221
+ ### Cleanup Schedule
222
+ - Runs every 10 minutes (600000ms)
223
+ - Removes terminal sessions older than 1 hour
224
+
225
+ ### Data Retention
226
+ - Recent terminal sessions: kept in memory indefinitely
227
+ - Cleanup prevents unbounded memory growth
228
+
229
+ ---
230
+
231
+ ## 🐛 Debugging
232
+
233
+ ### See All Active Sessions
234
+ ```bash
235
+ curl http://localhost:9899/gm/api/diagnostics/sessions | grep -A 5 "active"
236
+ ```
237
+
238
+ ### Find Stuck Sessions
239
+ ```bash
240
+ curl http://localhost:9899/gm/api/diagnostics/sessions | grep "acquiring_acp"
241
+ ```
242
+
243
+ ### Get Session History
244
+ ```bash
245
+ curl http://localhost:9899/gm/api/diagnostics/sessions | grep -A 20 "recentTerminal"
246
+ ```
247
+
248
+ ### Follow State Transitions
249
+ ```bash
250
+ tail -f server.log | grep "StateManager"
251
+ ```
252
+
253
+ ### Find Errors
254
+ ```bash
255
+ tail -f server.log | grep -E "ERROR|Stack:|❌"
256
+ ```
257
+
258
+ ---
259
+
260
+ ## 📚 Files Modified
261
+
262
+ | File | Changes | Lines |
263
+ |------|---------|-------|
264
+ | state-manager.js | NEW | 350 |
265
+ | server.js | Modified | +300, -80 |
266
+ | database.js | Fixed | +40 |
267
+ | DIAGNOSTICS.md | NEW | 80 |
268
+ | STATE_MACHINE_SUMMARY.md | NEW | 220 |
269
+
270
+ ---
271
+
272
+ ## ✨ Key Improvements
273
+
274
+ **Before State Machine**:
275
+ - ❌ Fire-and-forget processing
276
+ - ❌ No visibility into failures
277
+ - ❌ Hangs cause no feedback
278
+ - ❌ Hidden race conditions
279
+ - ❌ Impossible to debug
280
+
281
+ **After State Machine**:
282
+ - ✅ Every session tracked
283
+ - ✅ Complete visibility
284
+ - ✅ Immediate timeout detection
285
+ - ✅ Explicit error handling
286
+ - ✅ Full audit trail
287
+
package/bin/gmgui.cjs CHANGED
@@ -21,9 +21,12 @@ async function gmgui(args = []) {
21
21
  const useBun = hasBun();
22
22
  const installer = useBun ? 'bun' : 'npm';
23
23
 
24
- // Ensure dependencies are installed
24
+ // Ensure dependencies are installed only if node_modules is missing
25
+ // Skip this for bunx which manages dependencies independently
25
26
  const nodeModulesPath = path.join(projectRoot, 'node_modules');
26
- if (!fs.existsSync(nodeModulesPath)) {
27
+ const isBunx = process.env.npm_execpath && process.env.npm_execpath.includes('bunx');
28
+
29
+ if (!isBunx && !fs.existsSync(nodeModulesPath)) {
27
30
  console.log(`Installing dependencies with ${installer}...`);
28
31
  const installResult = spawnSync(installer, ['install'], {
29
32
  cwd: projectRoot,
@@ -45,12 +48,17 @@ async function gmgui(args = []) {
45
48
  stdio: 'inherit'
46
49
  });
47
50
 
51
+ ps.on('error', reject);
52
+
53
+ // Keep this process alive indefinitely to keep the server running
54
+ // The server will handle all actual work; this process just provides the bridge
55
+ process.stdin.resume();
56
+
57
+ // If server exits unexpectedly, log it but keep trying
48
58
  ps.on('exit', (code) => {
49
- if (code === 0) resolve();
50
- else reject(new Error(`Server exited with code ${code}`));
59
+ console.error(`Server process exited with code ${code}`);
60
+ // Don't reject or resolve - just keep waiting
51
61
  });
52
-
53
- ps.on('error', reject);
54
62
  });
55
63
  } else {
56
64
  throw new Error(`Unknown command: ${command}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/static/app.js CHANGED
@@ -1458,7 +1458,57 @@ class GMGUIApp {
1458
1458
  }
1459
1459
  const content = document.createElement('div');
1460
1460
  content.className = 'html-content';
1461
- content.innerHTML = this.sanitizeHtml(event.html);
1461
+
1462
+ // Get current theme to apply to HTML content
1463
+ const currentTheme = document.documentElement.getAttribute('data-theme') ||
1464
+ (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
1465
+
1466
+ // Apply theme-aware CSS for consistent colors in dark/light mode
1467
+ const themeCSS = currentTheme === 'dark'
1468
+ ? `<style>
1469
+ .html-content {
1470
+ color: #f8fafc;
1471
+ background: transparent;
1472
+ }
1473
+ .html-content p { color: #cbd5e1; }
1474
+ .html-content h1, .html-content h2, .html-content h3,
1475
+ .html-content h4, .html-content h5, .html-content h6 {
1476
+ color: #f8fafc;
1477
+ }
1478
+ .html-content a { color: #6366f1; }
1479
+ .html-content code { color: #c7d2fe; background: rgba(0,0,0,0.3); }
1480
+ .html-content pre { background: rgba(0,0,0,0.5); color: #e0e7ff; }
1481
+ .html-content table { border-color: #334155; }
1482
+ .html-content th { background: #1a202c; color: #f8fafc; }
1483
+ .html-content td { border-color: #334155; }
1484
+ .html-content blockquote { border-color: #334155; color: #cbd5e1; }
1485
+ .html-content ul, .html-content ol { color: #cbd5e1; }
1486
+ .html-content li { color: #cbd5e1; }
1487
+ </style>`
1488
+ : `<style>
1489
+ .html-content {
1490
+ color: #1d2129;
1491
+ background: transparent;
1492
+ }
1493
+ .html-content p { color: #475569; }
1494
+ .html-content h1, .html-content h2, .html-content h3,
1495
+ .html-content h4, .html-content h5, .html-content h6 {
1496
+ color: #1d2129;
1497
+ }
1498
+ .html-content a { color: #4f46e5; }
1499
+ .html-content code { color: #6366f1; background: rgba(99,102,241,0.1); }
1500
+ .html-content pre { background: #f3f4f6; color: #1d2129; }
1501
+ .html-content table { border-color: #e5e7eb; }
1502
+ .html-content th { background: #f9fafb; color: #1d2129; }
1503
+ .html-content td { border-color: #e5e7eb; }
1504
+ .html-content blockquote { border-color: #e5e7eb; color: #475569; }
1505
+ .html-content ul, .html-content ol { color: #475569; }
1506
+ .html-content li { color: #475569; }
1507
+ </style>`;
1508
+
1509
+ const enhancedHtml = themeCSS + this.sanitizeHtml(event.html);
1510
+ content.innerHTML = enhancedHtml;
1511
+ content.setAttribute('data-theme', currentTheme);
1462
1512
  wrap.appendChild(content);
1463
1513
  return wrap;
1464
1514
  }