agentgui 1.0.65 → 1.0.66

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/lib/types.ts ADDED
@@ -0,0 +1,245 @@
1
+ /**
2
+ * TYPES.TS - Complete type definitions for the separated data and sync system
3
+ * Guarantees type safety across CLI tests, server, and client
4
+ * Immutable by design - all structures are readonly
5
+ */
6
+
7
+ // ============================================================================
8
+ // CONVERSATION TYPES
9
+ // ============================================================================
10
+
11
+ export interface Conversation {
12
+ readonly id: string;
13
+ readonly agentId: string;
14
+ readonly title: string | null;
15
+ readonly created_at: number;
16
+ readonly updated_at: number;
17
+ readonly status: ConversationStatus;
18
+ readonly agentType?: string;
19
+ readonly source?: 'gui' | 'imported';
20
+ readonly externalId?: string;
21
+ readonly firstPrompt?: string;
22
+ readonly messageCount?: number;
23
+ readonly projectPath?: string;
24
+ readonly gitBranch?: string;
25
+ readonly sourcePath?: string;
26
+ readonly lastSyncedAt?: number;
27
+ }
28
+
29
+ export type ConversationStatus = 'active' | 'archived' | 'deleted';
30
+
31
+ export interface ConversationCreateInput {
32
+ agentId: string;
33
+ title?: string | null;
34
+ }
35
+
36
+ export interface ConversationUpdateInput {
37
+ title?: string;
38
+ status?: ConversationStatus;
39
+ }
40
+
41
+ // ============================================================================
42
+ // MESSAGE TYPES
43
+ // ============================================================================
44
+
45
+ export interface Message {
46
+ readonly id: string;
47
+ readonly conversationId: string;
48
+ readonly role: MessageRole;
49
+ readonly content: string;
50
+ readonly created_at: number;
51
+ }
52
+
53
+ export type MessageRole = 'user' | 'assistant' | 'system';
54
+
55
+ export interface MessageCreateInput {
56
+ conversationId: string;
57
+ role: MessageRole;
58
+ content: string;
59
+ idempotencyKey?: string;
60
+ }
61
+
62
+ // ============================================================================
63
+ // SESSION TYPES (for message processing)
64
+ // ============================================================================
65
+
66
+ export interface Session {
67
+ readonly id: string;
68
+ readonly conversationId: string;
69
+ readonly status: SessionStatus;
70
+ readonly started_at: number;
71
+ readonly completed_at?: number;
72
+ readonly response?: SessionResponse;
73
+ readonly error?: string;
74
+ }
75
+
76
+ export type SessionStatus = 'pending' | 'processing' | 'completed' | 'error' | 'cancelled';
77
+
78
+ export interface SessionResponse {
79
+ readonly text: string;
80
+ readonly messageId: string;
81
+ }
82
+
83
+ // ============================================================================
84
+ // SYNC STATE TYPES
85
+ // ============================================================================
86
+
87
+ export type SyncState = 'idle' | 'loading' | 'synced' | 'error' | 'offline' | 'reconciling';
88
+
89
+ export interface SyncStatus {
90
+ readonly state: SyncState;
91
+ readonly lastSyncTime?: number;
92
+ readonly nextRetryTime?: number;
93
+ readonly error?: string;
94
+ readonly retryCount: number;
95
+ readonly maxRetries: number;
96
+ }
97
+
98
+ export interface SyncEvent {
99
+ readonly type: SyncEventType;
100
+ readonly timestamp: number;
101
+ readonly data: Record<string, unknown>;
102
+ }
103
+
104
+ export type SyncEventType =
105
+ | 'conversation_created'
106
+ | 'conversation_updated'
107
+ | 'conversation_deleted'
108
+ | 'message_created'
109
+ | 'message_updated'
110
+ | 'message_deleted'
111
+ | 'sync_started'
112
+ | 'sync_completed'
113
+ | 'sync_failed'
114
+ | 'offline_detected'
115
+ | 'online_detected';
116
+
117
+ // ============================================================================
118
+ // PAGINATION TYPES
119
+ // ============================================================================
120
+
121
+ export interface PaginationParams {
122
+ readonly limit: number;
123
+ readonly offset: number;
124
+ }
125
+
126
+ export interface PaginatedResult<T> {
127
+ readonly items: readonly T[];
128
+ readonly total: number;
129
+ readonly limit: number;
130
+ readonly offset: number;
131
+ readonly hasMore: boolean;
132
+ }
133
+
134
+ // ============================================================================
135
+ // IDEMPOTENCY TYPES
136
+ // ============================================================================
137
+
138
+ export interface IdempotencyKey {
139
+ readonly key: string;
140
+ readonly value: string;
141
+ readonly created_at: number;
142
+ readonly ttl: number;
143
+ }
144
+
145
+ // ============================================================================
146
+ // ERROR TYPES
147
+ // ============================================================================
148
+
149
+ export class SyncError extends Error {
150
+ constructor(
151
+ public code: string,
152
+ public message: string,
153
+ public retryable: boolean = false,
154
+ public context?: Record<string, unknown>
155
+ ) {
156
+ super(message);
157
+ this.name = 'SyncError';
158
+ }
159
+ }
160
+
161
+ export type ErrorCode =
162
+ | 'DATABASE_ERROR'
163
+ | 'NETWORK_ERROR'
164
+ | 'SYNC_CONFLICT'
165
+ | 'VALIDATION_ERROR'
166
+ | 'NOT_FOUND'
167
+ | 'UNAUTHORIZED'
168
+ | 'TIMEOUT'
169
+ | 'UNKNOWN';
170
+
171
+ // ============================================================================
172
+ // STATE MACHINE CONTEXT
173
+ // ============================================================================
174
+
175
+ export interface SyncMachineContext {
176
+ readonly conversationId?: string;
177
+ readonly messageId?: string;
178
+ readonly lastError?: Error;
179
+ readonly retryCount: number;
180
+ readonly syncData: Record<string, unknown>;
181
+ }
182
+
183
+ // ============================================================================
184
+ // API RESPONSE TYPES
185
+ // ============================================================================
186
+
187
+ export interface ApiResponse<T> {
188
+ readonly data?: T;
189
+ readonly error?: string;
190
+ readonly timestamp: number;
191
+ }
192
+
193
+ export interface ConversationsListResponse {
194
+ readonly conversations: readonly Conversation[];
195
+ readonly total: number;
196
+ }
197
+
198
+ export interface MessagesListResponse {
199
+ readonly messages: readonly Message[];
200
+ readonly total: number;
201
+ readonly hasMore: boolean;
202
+ }
203
+
204
+ // ============================================================================
205
+ // VALIDATION RESULT TYPES
206
+ // ============================================================================
207
+
208
+ export interface ValidationResult {
209
+ readonly valid: boolean;
210
+ readonly errors: readonly ValidationError[];
211
+ }
212
+
213
+ export interface ValidationError {
214
+ readonly field: string;
215
+ readonly message: string;
216
+ readonly value?: unknown;
217
+ }
218
+
219
+ // ============================================================================
220
+ // CONFLICT RESOLUTION TYPES
221
+ // ============================================================================
222
+
223
+ export type ConflictResolutionStrategy = 'last-write-wins' | 'server-wins' | 'client-wins';
224
+
225
+ export interface ConflictInfo {
226
+ readonly localVersion: unknown;
227
+ readonly remoteVersion: unknown;
228
+ readonly resolution: ConflictResolutionStrategy;
229
+ }
230
+
231
+ // ============================================================================
232
+ // RECOVERY TYPES
233
+ // ============================================================================
234
+
235
+ export interface RecoveryCheckpoint {
236
+ readonly timestamp: number;
237
+ readonly synced: boolean;
238
+ readonly data: Record<string, unknown>;
239
+ }
240
+
241
+ export interface RecoveryState {
242
+ readonly lastCheckpoint?: RecoveryCheckpoint;
243
+ readonly pendingOperations: readonly unknown[];
244
+ readonly offline: boolean;
245
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.65",
3
+ "version": "1.0.66",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -3,14 +3,9 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { WebSocketServer } from 'ws';
6
- import os from 'os';
7
6
  import { execSync } from 'child_process';
8
7
  import { queries } from './database.js';
9
- import ACPConnection from './acp-launcher.js';
10
- import { SessionStateStore } from './state-manager.js';
11
- import { StreamHandler } from './stream-handler.js';
12
- import { StateValidator } from './state-validator.js';
13
- import { getConversationSync } from './conversation-sync.js';
8
+ import { runClaudeWithStreaming } from './lib/claude-runner.js';
14
9
 
15
10
  // Debug logging to file
16
11
  const debugLog = (msg) => {
@@ -26,71 +21,6 @@ const watch = process.argv.includes('--no-watch') ? false : (process.argv.includ
26
21
  const staticDir = path.join(__dirname, 'static');
27
22
  if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir, { recursive: true });
28
23
 
29
- // ACP connection pool keyed by agentId
30
- const acpPool = new Map();
31
-
32
- // Global session state store - tracks ALL prompt processing with explicit states
33
- const sessionStateStore = new SessionStateStore();
34
-
35
- // Periodic cleanup of old sessions
36
- setInterval(() => {
37
- sessionStateStore.cleanup(3600000); // Clean sessions older than 1 hour
38
- }, 600000); // Run every 10 minutes
39
-
40
- /**
41
- * Get or create ACP connection with timeout protection
42
- */
43
- async function getACP(agentId, cwd) {
44
- let conn = acpPool.get(agentId);
45
- if (conn?.isRunning()) {
46
- console.log(`[getACP] Returning cached connection for ${agentId}`);
47
- return conn;
48
- }
49
-
50
- console.log(`[getACP] Creating new ACP connection for ${agentId}`);
51
- conn = new ACPConnection();
52
- const agentType = agentId === 'opencode' ? 'opencode' : 'claude-code';
53
-
54
- // Wrap entire init in timeout to prevent indefinite hangs
55
- return Promise.race([
56
- initializeACP(conn, agentType, cwd, agentId),
57
- new Promise((_, reject) =>
58
- setTimeout(() => reject(new Error('ACP initialization timeout (>60s)')), 60000)
59
- )
60
- ]);
61
- }
62
-
63
- /**
64
- * Initialize ACP with all steps
65
- */
66
- async function initializeACP(conn, agentType, cwd, agentId) {
67
- try {
68
- console.log(`[getACP] Step 1: Connecting to ${agentType}...`);
69
- await conn.connect(agentType, cwd);
70
- console.log(`[getACP] Step 2: Connected, initializing...`);
71
- await conn.initialize();
72
- console.log(`[getACP] Step 3: Initialized, creating session...`);
73
- await conn.newSession(cwd);
74
- console.log(`[getACP] Step 4: Session created, setting mode...`);
75
- await conn.setSessionMode('bypassPermissions');
76
- console.log(`[getACP] Step 5: Injecting skills...`);
77
- // Inject system prompt to ensure HTML/RippleUI formatting
78
- await conn.injectSkills();
79
- console.log(`[getACP] Step 6: Injecting system context...`);
80
- await conn.injectSystemContext();
81
- console.log(`[getACP] Step 7: All initialization complete, caching connection`);
82
- acpPool.set(agentId, conn);
83
- console.log(`[getACP] ✅ ACP connection ready for ${agentId} in ${cwd}`);
84
- return conn;
85
- } catch (err) {
86
- console.error(`[getACP] ❌ ERROR: Failed to initialize ACP connection for ${agentId}: ${err.message}`);
87
- console.error(`[getACP] Stack: ${err.stack}`);
88
- acpPool.delete(agentId);
89
- if (conn) await conn.terminate();
90
- throw new Error(`ACP initialization failed for ${agentId}: ${err.message}`);
91
- }
92
- }
93
-
94
24
  function discoverAgents() {
95
25
  const agents = [];
96
26
  const binaries = [
@@ -205,7 +135,7 @@ const server = http.createServer(async (req, res) => {
205
135
  res.writeHead(201, { 'Content-Type': 'application/json' });
206
136
  res.end(JSON.stringify({ message, session, idempotencyKey }));
207
137
  // Fire-and-forget with proper error handling
208
- processMessage(conversationId, message.id, session.id, body.content, body.agentId, body.folderContext)
138
+ processMessage(conversationId, message.id, body.content, body.agentId)
209
139
  .catch(err => debugLog(`[processMessage] Uncaught error: ${err.message}`));
210
140
  return;
211
141
  }
@@ -250,45 +180,6 @@ const server = http.createServer(async (req, res) => {
250
180
  return;
251
181
  }
252
182
 
253
- // Diagnostics endpoint - shows ALL active and recent sessions
254
- if (routePath === '/api/diagnostics/sessions' && req.method === 'GET') {
255
- const diagnostics = sessionStateStore.getDiagnostics();
256
- res.writeHead(200, { 'Content-Type': 'application/json' });
257
- res.end(JSON.stringify(diagnostics, null, 2));
258
- return;
259
- }
260
-
261
- const streamUpdatesMatch = routePath.match(/^\/api\/sessions\/([^/]+)\/stream-updates$/);
262
- if (streamUpdatesMatch && req.method === 'GET') {
263
- const sessionId = streamUpdatesMatch[1];
264
- const updates = queries.getSessionStreamUpdates(sessionId);
265
- res.writeHead(200, { 'Content-Type': 'application/json' });
266
- res.end(JSON.stringify({ sessionId, updates, count: updates.length }));
267
- return;
268
- }
269
-
270
- const stateRecoveryMatch = routePath.match(/^\/api\/sessions\/([^/]+)\/state-recovery$/);
271
- if (stateRecoveryMatch && req.method === 'GET') {
272
- const sessionId = stateRecoveryMatch[1];
273
- const state = StateValidator.getSessionState(sessionId);
274
- if (!state) {
275
- res.writeHead(404, { 'Content-Type': 'application/json' });
276
- res.end(JSON.stringify({ error: 'Session not found' }));
277
- return;
278
- }
279
- res.writeHead(200, { 'Content-Type': 'application/json' });
280
- res.end(JSON.stringify(state));
281
- return;
282
- }
283
-
284
- const stateValidationMatch = routePath.match(/^\/api\/sessions\/([^/]+)\/validate$/);
285
- if (stateValidationMatch && req.method === 'GET') {
286
- const sessionId = stateValidationMatch[1];
287
- const validation = StateValidator.validateSession(sessionId);
288
- res.writeHead(200, { 'Content-Type': 'application/json' });
289
- res.end(JSON.stringify(validation));
290
- return;
291
- }
292
183
 
293
184
  if (routePath === '/api/import/claude-code' && req.method === 'GET') {
294
185
  const result = queries.importClaudeCodeConversations();
@@ -396,159 +287,90 @@ function serveFile(filePath, res) {
396
287
  });
397
288
  }
398
289
 
399
- /**
400
- * Process a user message through the Claude Code ACP with real-time streaming
401
- * Updates are persisted to database and broadcast to clients immediately
402
- */
403
- async function processMessage(conversationId, messageId, sessionId, content, agentId, folderContext) {
404
- // Create state manager for this session
405
- const stateManager = sessionStateStore.create(sessionId, conversationId, messageId, 120000);
406
-
290
+ async function processMessage(conversationId, messageId, content, agentId) {
407
291
  try {
408
- console.log(`[processMessage] Starting: conversationId=${conversationId}, sessionId=${sessionId}`);
409
- console.log(`[processMessage] Initial state: ${stateManager.getState()}`);
292
+ debugLog(`[processMessage] Starting: conversationId=${conversationId}, agentId=${agentId}`);
410
293
 
411
- // STATE: PENDING → ACQUIRING_ACP
412
- stateManager.transition(stateManager.constructor.STATES.ACQUIRING_ACP, {
413
- reason: 'Connecting to ACP',
414
- data: {}
415
- });
416
-
417
- const cwd = folderContext?.path || '/config';
294
+ const cwd = '/config';
418
295
  const actualAgentId = agentId || 'claude-code';
419
296
 
420
- try {
421
- const conn = await getACP(actualAgentId, cwd);
422
-
423
- // STATE: ACQUIRING_ACP → ACP_ACQUIRED
424
- stateManager.transition(stateManager.constructor.STATES.ACP_ACQUIRED, {
425
- reason: 'ACP connection established',
426
- data: { acpConnectionTime: Date.now() }
427
- });
428
-
429
- // Create stream handler for real-time persistence and broadcasting
430
- const streamHandler = new StreamHandler(sessionId, conversationId, broadcastSync);
431
- let fullText = '';
432
-
433
- // Setup response streaming
434
- conn.onUpdate = (params) => {
435
- streamHandler.handleUpdate(params, BASE_URL);
436
- const u = params.update;
437
- if (u?.sessionUpdate === 'agent_message_chunk' && u.content?.text) {
438
- fullText += u.content.text;
439
- }
440
- };
441
-
442
- // STATE: ACP_ACQUIRED → SENDING_PROMPT
443
- stateManager.transition(stateManager.constructor.STATES.SENDING_PROMPT, {
444
- reason: 'Sending prompt to ACP',
445
- data: {}
446
- });
297
+ debugLog(`[processMessage] Calling runClaudeWithStreaming with prompt: "${content.substring(0, 50)}..."`);
298
+ const outputs = await runClaudeWithStreaming(content, cwd, actualAgentId);
299
+ debugLog(`[processMessage] Claude returned ${outputs.length} outputs`);
300
+
301
+ // Collect all message blocks to preserve full execution details
302
+ let allBlocks = [];
303
+ let lastAssistantMessage = null;
304
+
305
+ for (const output of outputs) {
306
+ if (output.type === 'assistant' && output.message?.content) {
307
+ debugLog(`[processMessage] Found assistant message with ${output.message.content.length} content blocks`);
308
+ lastAssistantMessage = output.message;
309
+ allBlocks.push(...(output.message.content || []));
310
+ } else if (output.type === 'tool_result' && output.result) {
311
+ debugLog(`[processMessage] Found tool result: ${typeof output.result}`);
312
+ allBlocks.push({
313
+ type: 'tool_result',
314
+ result: output.result,
315
+ tool_use_id: output.tool_use_id
316
+ });
317
+ }
318
+ }
447
319
 
448
- console.log(`[processMessage] Sending prompt to ACP (${content.length} chars)`);
449
- const result = await conn.sendPrompt(content);
450
- conn.onUpdate = null;
320
+ // Store full message structure if we have execution data, otherwise fallback to text
321
+ let messageContent = null;
451
322
 
452
- // STATE: SENDING_PROMPT PROCESSING
453
- stateManager.transition(stateManager.constructor.STATES.PROCESSING, {
454
- reason: 'ACP processing complete, formatting response',
455
- data: { promptSentTime: Date.now(), responseReceivedTime: Date.now() }
323
+ if (allBlocks.length > 0) {
324
+ // Store full message structure as JSON for proper rendering
325
+ messageContent = JSON.stringify({
326
+ type: 'claude_execution',
327
+ blocks: allBlocks,
328
+ timestamp: Date.now()
456
329
  });
330
+ debugLog(`[processMessage] Storing full execution with ${allBlocks.length} blocks`);
331
+ } else {
332
+ // Fallback: extract text for simple responses
333
+ let textParts = [];
334
+ for (const output of outputs) {
335
+ if (typeof output === 'string') {
336
+ textParts.push(output);
337
+ } else if (output.text) {
338
+ textParts.push(output.text);
339
+ } else if (output.content?.text) {
340
+ textParts.push(output.content.text);
341
+ } else if (output.result) {
342
+ textParts.push(String(output.result));
343
+ }
344
+ }
345
+ messageContent = textParts.join('\n').trim();
346
+ debugLog(`[processMessage] Storing text response: "${messageContent.substring(0, 100)}..."`);
347
+ }
457
348
 
458
- console.log(`[processMessage] ACP returned: stopReason=${result?.stopReason}, streamUpdates=${streamHandler.getUpdateCount()}`);
459
-
460
- // Save agent's complete response as-is, without any processing
461
- // The agent workflow must flow naturally - no HTML extraction or interference
462
- const responseText = fullText || result?.result || 'No response.';
463
-
464
- const messageContent = {
465
- text: responseText,
466
- streamUpdatesCount: streamHandler.getUpdateCount()
467
- };
468
-
469
- // Save consolidated response to database
349
+ if (messageContent) {
350
+ debugLog(`[processMessage] Creating assistant message`);
470
351
  const assistantMessage = queries.createMessage(conversationId, 'assistant', messageContent);
471
- queries.updateSession(sessionId, {
472
- status: 'completed',
473
- response: { text: responseText, messageId: assistantMessage.id },
474
- completed_at: Date.now()
475
- });
476
- queries.createEvent('session.completed', { messageId: assistantMessage.id }, conversationId, sessionId);
477
-
478
- // Broadcast final consolidated response with full message content
352
+ debugLog(`[processMessage] Created message with id: ${assistantMessage.id}`);
479
353
  broadcastSync({
480
- type: 'session_updated',
481
- sessionId,
354
+ type: 'message_created',
482
355
  conversationId,
483
- status: 'completed',
484
356
  message: assistantMessage,
485
357
  timestamp: Date.now()
486
358
  });
487
-
488
- // STATE: PROCESSING COMPLETED
489
- stateManager.transition(stateManager.constructor.STATES.COMPLETED, {
490
- reason: 'Response successfully generated and saved',
491
- data: {
492
- responseLength: responseText.length,
493
- messageId: assistantMessage.id,
494
- streamUpdates: streamHandler.getUpdateCount()
495
- }
496
- });
497
-
498
- console.log(`[processMessage] ✅ Session completed with ${streamHandler.getUpdateCount()} stream updates: ${stateManager.getSummary().duration}`);
499
-
500
- } catch (acpError) {
501
- console.error(`[processMessage] ACP Error: ${acpError.message}`);
502
- console.error(`[processMessage] Stack: ${acpError.stack}`);
503
-
504
- // STATE: → ERROR
505
- stateManager.transition(stateManager.constructor.STATES.ERROR, {
506
- reason: `ACP error: ${acpError.message}`,
507
- data: {
508
- error: acpError.message,
509
- stackTrace: acpError.stack
510
- }
511
- });
512
-
513
- // Save error to database
514
- const errorMsg = `ACP Error: ${acpError.message}`;
515
- const errorMessage = queries.createMessage(conversationId, 'assistant', errorMsg);
516
- queries.updateSession(sessionId, { status: 'error', error: acpError.message, completed_at: Date.now() });
517
- queries.createEvent('session.error', { error: acpError.message, stack: acpError.stack }, conversationId, sessionId);
518
- broadcastSync({ type: 'session_updated', sessionId, conversationId, status: 'error', error: acpError.message, message: errorMessage, timestamp: Date.now() });
519
-
520
- // Clean up ACP connection on error
521
- acpPool.delete(actualAgentId);
522
- throw acpError;
359
+ } else {
360
+ debugLog(`[processMessage] No response content extracted!`);
523
361
  }
524
362
 
525
- } catch (fatalError) {
526
- console.error(`[processMessage] Fatal error: ${fatalError.message}`);
527
- console.error(`[processMessage] Stack: ${fatalError.stack}`);
528
-
529
- // Ensure state is in error
530
- if (!stateManager.isTerminal()) {
531
- stateManager.transition(stateManager.constructor.STATES.ERROR, {
532
- reason: `Fatal error: ${fatalError.message}`,
533
- data: {
534
- error: fatalError.message,
535
- stackTrace: fatalError.stack
536
- }
537
- });
538
- }
539
-
540
- // Log full state history for debugging
541
- const summary = stateManager.getSummary();
542
- console.error(`[processMessage] State history: ${JSON.stringify(summary, null, 2)}`);
543
-
544
- } finally {
545
- // Cleanup: remove from state store immediately (async to not block)
546
- setImmediate(() => {
547
- sessionStateStore.remove(sessionId);
363
+ debugLog(`[processMessage] ✅ Completed: ${outputs.length} outputs received`);
364
+ } catch (error) {
365
+ debugLog(`[processMessage] Error: ${error.message}`);
366
+ debugLog(`[processMessage] Stack: ${error.stack}`);
367
+ const errorMessage = queries.createMessage(conversationId, 'assistant', `Error: ${error.message}`);
368
+ broadcastSync({
369
+ type: 'message_created',
370
+ conversationId,
371
+ message: errorMessage,
372
+ timestamp: Date.now()
548
373
  });
549
-
550
- // Log final state
551
- console.log(`[processMessage] Final state: ${stateManager.getState()}`);
552
374
  }
553
375
  }
554
376
 
@@ -579,29 +401,8 @@ wss.on('connection', (ws, req) => {
579
401
  const data = JSON.parse(msg);
580
402
  if (data.type === 'subscribe') {
581
403
  ws.subscriptions.add(data.sessionId);
582
- // On subscribe, send current state for recovery
583
- const state = StateValidator.getSessionState(data.sessionId);
584
- if (state) {
585
- ws.send(JSON.stringify({
586
- type: 'state_snapshot',
587
- sessionId: data.sessionId,
588
- state,
589
- timestamp: Date.now()
590
- }));
591
- }
592
404
  } else if (data.type === 'unsubscribe') {
593
405
  ws.subscriptions.delete(data.sessionId);
594
- } else if (data.type === 'recovery_request') {
595
- // Client asking to recover from a checkpoint
596
- const state = StateValidator.getSessionState(data.sessionId);
597
- if (state) {
598
- ws.send(JSON.stringify({
599
- type: 'recovery_response',
600
- sessionId: data.sessionId,
601
- state,
602
- timestamp: Date.now()
603
- }));
604
- }
605
406
  }
606
407
  } catch (e) {
607
408
  console.error('WebSocket message parse error:', e.message);
@@ -659,8 +460,6 @@ if (watch) {
659
460
  }
660
461
 
661
462
  process.on('SIGTERM', async () => {
662
- for (const conn of acpPool.values()) await conn.terminate();
663
- acpPool.clear();
664
463
  wss.close(() => server.close(() => process.exit(0)));
665
464
  });
666
465
 
@@ -687,13 +486,6 @@ function onServerReady() {
687
486
  // Then run it every 30 seconds (constant automatic importing)
688
487
  setInterval(performAutoImport, 30000);
689
488
 
690
- // Start conversation sync to watch for changes
691
- try {
692
- const sync = getConversationSync();
693
- sync.start();
694
- } catch (err) {
695
- console.error('[SERVER] Error starting conversation sync:', err.message);
696
- }
697
489
  }
698
490
 
699
491
  function performAutoImport() {