@esotech/contextuate 2.0.0 → 2.1.1

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 (103) hide show
  1. package/README.md +169 -1
  2. package/dist/commands/claude.d.ts +21 -0
  3. package/dist/commands/claude.js +213 -0
  4. package/dist/commands/context.d.ts +1 -0
  5. package/dist/commands/create.d.ts +3 -0
  6. package/dist/commands/index.d.ts +4 -0
  7. package/dist/commands/init.d.ts +7 -0
  8. package/dist/commands/init.js +67 -6
  9. package/dist/commands/install.d.ts +28 -0
  10. package/dist/commands/install.js +100 -11
  11. package/dist/commands/monitor.d.ts +55 -0
  12. package/dist/commands/monitor.js +1007 -0
  13. package/dist/commands/remove.d.ts +3 -0
  14. package/dist/commands/run.d.ts +6 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +113 -1
  17. package/dist/monitor/daemon/circuit-breaker.d.ts +121 -0
  18. package/dist/monitor/daemon/circuit-breaker.js +552 -0
  19. package/dist/monitor/daemon/cli.d.ts +8 -0
  20. package/dist/monitor/daemon/cli.js +82 -0
  21. package/dist/monitor/daemon/index.d.ts +137 -0
  22. package/dist/monitor/daemon/index.js +695 -0
  23. package/dist/monitor/daemon/notifier.d.ts +25 -0
  24. package/dist/monitor/daemon/notifier.js +98 -0
  25. package/dist/monitor/daemon/processor.d.ts +89 -0
  26. package/dist/monitor/daemon/processor.js +455 -0
  27. package/dist/monitor/daemon/state.d.ts +80 -0
  28. package/dist/monitor/daemon/state.js +162 -0
  29. package/dist/monitor/daemon/watcher.d.ts +47 -0
  30. package/dist/monitor/daemon/watcher.js +171 -0
  31. package/dist/monitor/daemon/wrapper-manager.d.ts +106 -0
  32. package/dist/monitor/daemon/wrapper-manager.js +374 -0
  33. package/dist/monitor/hooks/emit-event.js +652 -0
  34. package/dist/monitor/persistence/file-store.d.ts +88 -0
  35. package/dist/monitor/persistence/file-store.js +335 -0
  36. package/dist/monitor/persistence/index.d.ts +7 -0
  37. package/dist/monitor/persistence/index.js +10 -0
  38. package/dist/monitor/server/adapters/redis.d.ts +38 -0
  39. package/dist/monitor/server/adapters/redis.js +213 -0
  40. package/dist/monitor/server/adapters/unix-socket.d.ts +33 -0
  41. package/dist/monitor/server/adapters/unix-socket.js +182 -0
  42. package/dist/monitor/server/broker.d.ts +135 -0
  43. package/dist/monitor/server/broker.js +475 -0
  44. package/dist/monitor/server/cli.d.ts +8 -0
  45. package/dist/monitor/server/cli.js +98 -0
  46. package/dist/monitor/server/fastify.d.ts +16 -0
  47. package/dist/monitor/server/fastify.js +184 -0
  48. package/dist/monitor/server/index.d.ts +36 -0
  49. package/dist/monitor/server/index.js +153 -0
  50. package/dist/monitor/server/websocket.d.ts +80 -0
  51. package/dist/monitor/server/websocket.js +453 -0
  52. package/dist/monitor/ui/assets/index-4IssW9On.js +59 -0
  53. package/dist/monitor/ui/assets/index-vo9hLe5R.css +32 -0
  54. package/dist/monitor/ui/favicon.png +0 -0
  55. package/dist/monitor/ui/index.html +14 -0
  56. package/dist/monitor/ui/logo.png +0 -0
  57. package/dist/monitor/ui/logo.svg +1 -0
  58. package/dist/runtime/driver.d.ts +16 -0
  59. package/dist/runtime/tools.d.ts +10 -0
  60. package/dist/templates/README.md +33 -7
  61. package/dist/templates/agents/aegis.md +4 -0
  62. package/dist/templates/agents/archon.md +13 -22
  63. package/dist/templates/agents/atlas.md +4 -0
  64. package/dist/templates/agents/canvas.md +4 -0
  65. package/dist/templates/agents/chronicle.md +4 -0
  66. package/dist/templates/agents/chronos.md +4 -0
  67. package/dist/templates/agents/cipher.md +4 -0
  68. package/dist/templates/agents/crucible.md +4 -0
  69. package/dist/templates/agents/echo.md +4 -0
  70. package/dist/templates/agents/forge.md +4 -0
  71. package/dist/templates/agents/ledger.md +4 -0
  72. package/dist/templates/agents/meridian.md +4 -0
  73. package/dist/templates/agents/nexus.md +4 -0
  74. package/dist/templates/agents/pythia.md +217 -0
  75. package/dist/templates/agents/scribe.md +4 -0
  76. package/dist/templates/agents/sentinel.md +4 -0
  77. package/dist/templates/agents/{oracle.md → thoth.md} +11 -7
  78. package/dist/templates/agents/unity.md +4 -0
  79. package/dist/templates/agents/vox.md +4 -0
  80. package/dist/templates/agents/weaver.md +4 -0
  81. package/dist/templates/commands/consult.md +138 -0
  82. package/dist/templates/commands/orchestrate.md +173 -0
  83. package/dist/templates/framework-agents/documentation-expert.md +3 -3
  84. package/dist/templates/framework-agents/tools-expert.md +8 -8
  85. package/dist/templates/standards/agent-roles.md +68 -21
  86. package/dist/templates/standards/coding-standards.md +9 -26
  87. package/dist/templates/templates/context.md +17 -2
  88. package/dist/templates/templates/contextuate.md +21 -28
  89. package/dist/templates/tools/{agent-creator.tool.md → agent-creator.md} +3 -3
  90. package/dist/types/monitor.d.ts +660 -0
  91. package/dist/types/monitor.js +75 -0
  92. package/dist/utils/git.d.ts +9 -0
  93. package/dist/utils/tokens.d.ts +10 -0
  94. package/package.json +18 -5
  95. package/dist/templates/version.json +0 -8
  96. /package/dist/templates/templates/standards/{go.standards.md → go.md} +0 -0
  97. /package/dist/templates/templates/standards/{java.standards.md → java.md} +0 -0
  98. /package/dist/templates/templates/standards/{javascript.standards.md → javascript.md} +0 -0
  99. /package/dist/templates/templates/standards/{php.standards.md → php.md} +0 -0
  100. /package/dist/templates/templates/standards/{python.standards.md → python.md} +0 -0
  101. /package/dist/templates/tools/{quickref.tool.md → quickref.md} +0 -0
  102. /package/dist/templates/tools/{spawn.tool.md → spawn.md} +0 -0
  103. /package/dist/templates/tools/{standards-detector.tool.md → standards-detector.md} +0 -0
@@ -0,0 +1,695 @@
1
+ "use strict";
2
+ /**
3
+ * Monitor Daemon
4
+ *
5
+ * Background daemon process that:
6
+ * - Watches raw/ directory for new events
7
+ * - Processes events (correlates sessions, tracks subagents)
8
+ * - Persists data to sessions/
9
+ * - Notifies UI server via Unix socket/Redis
10
+ * - Manages Claude wrapper sessions with PTY
11
+ *
12
+ * This is Layer 2 of the 3-layer architecture:
13
+ * Layer 1: Hooks (write to raw/)
14
+ * Layer 2: Daemon (process raw/ -> sessions/)
15
+ * Layer 3: UI Server (read from sessions/, serve WebSocket)
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.MonitorDaemon = void 0;
52
+ exports.startDaemon = startDaemon;
53
+ const fs = __importStar(require("fs"));
54
+ const path = __importStar(require("path"));
55
+ const net = __importStar(require("net"));
56
+ const monitor_js_1 = require("../../types/monitor.js");
57
+ const state_js_1 = require("./state.js");
58
+ const watcher_js_1 = require("./watcher.js");
59
+ const processor_js_1 = require("./processor.js");
60
+ const notifier_js_1 = require("./notifier.js");
61
+ const wrapper_manager_js_1 = require("./wrapper-manager.js");
62
+ const circuit_breaker_js_1 = require("./circuit-breaker.js");
63
+ const PATHS = (0, monitor_js_1.getDefaultMonitorPaths)();
64
+ class MonitorDaemon {
65
+ constructor(config, circuitBreakerConfig) {
66
+ this.socketServer = null;
67
+ this.uiClients = new Set();
68
+ this.legacyWrapperSessions = new Map();
69
+ this.running = false;
70
+ this.config = config;
71
+ this.state = new state_js_1.StateManager();
72
+ this.notifier = new notifier_js_1.Notifier(config, (data) => this.broadcastToClients(data));
73
+ this.processor = new processor_js_1.EventProcessor(this.state, this.notifier);
74
+ this.watcher = new watcher_js_1.FileWatcher();
75
+ // Initialize wrapper manager
76
+ const wrapperPersistPath = path.join(PATHS.baseDir, 'wrappers.json');
77
+ this.wrapperManager = new wrapper_manager_js_1.WrapperManager(wrapperPersistPath, (event) => {
78
+ this.handleWrapperManagerEvent(event);
79
+ });
80
+ // Initialize circuit breaker
81
+ this.circuitBreaker = new circuit_breaker_js_1.CircuitBreaker(circuitBreakerConfig || {}, this.wrapperManager, (alert) => this.handleCircuitAlert(alert));
82
+ // Connect event processor to circuit breaker
83
+ this.processor.setCircuitBreaker(this.circuitBreaker);
84
+ }
85
+ /**
86
+ * Handle circuit breaker alerts
87
+ */
88
+ handleCircuitAlert(alert) {
89
+ console.log(`[Daemon] Circuit alert: ${alert.sessionId} ${alert.previousState} → ${alert.newState} (${alert.reason})`);
90
+ this.broadcastToClients({
91
+ type: 'circuit_alert',
92
+ alert,
93
+ });
94
+ }
95
+ /**
96
+ * Handle events from WrapperManager
97
+ */
98
+ handleWrapperManagerEvent(event) {
99
+ switch (event.type) {
100
+ case 'output':
101
+ this.broadcastToClients({
102
+ type: 'wrapper_output',
103
+ wrapperId: event.wrapperId,
104
+ data: event.data,
105
+ timestamp: Date.now(),
106
+ });
107
+ break;
108
+ case 'state_changed':
109
+ this.broadcastToClients({
110
+ type: 'wrapper_state',
111
+ wrapperId: event.wrapperId,
112
+ state: event.state,
113
+ claudeSessionId: event.claudeSessionId,
114
+ });
115
+ break;
116
+ case 'started':
117
+ this.broadcastToClients({
118
+ type: 'wrapper_connected',
119
+ wrapperId: event.wrapperId,
120
+ state: event.state || 'starting',
121
+ });
122
+ break;
123
+ case 'ended':
124
+ this.broadcastToClients({
125
+ type: 'wrapper_disconnected',
126
+ wrapperId: event.wrapperId,
127
+ exitCode: event.exitCode,
128
+ });
129
+ break;
130
+ }
131
+ }
132
+ /**
133
+ * Broadcast data to all connected UI clients
134
+ */
135
+ broadcastToClients(data) {
136
+ const message = JSON.stringify(data) + '\n';
137
+ for (const client of this.uiClients) {
138
+ try {
139
+ client.write(message);
140
+ }
141
+ catch (err) {
142
+ // Client disconnected, will be cleaned up
143
+ }
144
+ }
145
+ }
146
+ /**
147
+ * Start the daemon
148
+ */
149
+ async start() {
150
+ console.log('[Daemon] Starting monitor daemon...');
151
+ // Load state from disk
152
+ await this.state.load();
153
+ // Load existing sessions
154
+ await this.processor.loadSessions();
155
+ // Initialize wrapper manager (loads persisted wrappers, cleans up dead ones)
156
+ await this.wrapperManager.initialize();
157
+ // Set up file watcher
158
+ this.watcher.setLastProcessedTimestamp(this.state.lastProcessedTimestamp);
159
+ this.watcher.setHandler((event, filepath) => this.processor.processEvent(event, filepath));
160
+ await this.watcher.start();
161
+ // Start periodic state saving (every 30 seconds)
162
+ this.state.startPeriodicSave(30000);
163
+ // Start socket server for UI connections
164
+ await this.startSocketServer();
165
+ // Start circuit breaker health monitoring
166
+ this.circuitBreaker.start();
167
+ this.running = true;
168
+ console.log('[Daemon] Monitor daemon started');
169
+ }
170
+ /**
171
+ * Stop the daemon
172
+ */
173
+ async stop() {
174
+ console.log('[Daemon] Stopping monitor daemon...');
175
+ this.running = false;
176
+ // Stop circuit breaker health monitoring
177
+ this.circuitBreaker.stop();
178
+ // Shutdown wrapper manager (kills all wrappers, persists state)
179
+ await this.wrapperManager.shutdown();
180
+ // Stop file watcher
181
+ await this.watcher.stop();
182
+ // Stop periodic save and save final state
183
+ this.state.stopPeriodicSave();
184
+ await this.state.save();
185
+ // Close socket server
186
+ if (this.socketServer) {
187
+ // Forcefully disconnect all UI clients
188
+ for (const client of this.uiClients) {
189
+ try {
190
+ client.write(JSON.stringify({ type: 'error', message: 'Daemon shutting down' }) + '\n');
191
+ client.destroy(); // Force close the connection
192
+ }
193
+ catch (err) {
194
+ // Ignore errors during shutdown
195
+ }
196
+ }
197
+ await new Promise((resolve) => {
198
+ // Add timeout to force resolve if close takes too long
199
+ const timeout = setTimeout(() => {
200
+ console.log('[Daemon] Socket server close timeout, forcing shutdown');
201
+ this.socketServer = null;
202
+ this.uiClients.clear();
203
+ // Clean up socket file
204
+ try {
205
+ fs.unlinkSync('/tmp/contextuate-daemon.sock');
206
+ }
207
+ catch (err) {
208
+ // Ignore if already removed
209
+ }
210
+ resolve();
211
+ }, 3000);
212
+ this.socketServer.close(() => {
213
+ clearTimeout(timeout);
214
+ this.socketServer = null;
215
+ this.uiClients.clear();
216
+ console.log('[Daemon] Socket server stopped');
217
+ // Clean up socket file
218
+ try {
219
+ fs.unlinkSync('/tmp/contextuate-daemon.sock');
220
+ }
221
+ catch (err) {
222
+ // Ignore if already removed
223
+ }
224
+ resolve();
225
+ });
226
+ });
227
+ }
228
+ console.log('[Daemon] Monitor daemon stopped');
229
+ }
230
+ /**
231
+ * Check if daemon is running
232
+ */
233
+ isRunning() {
234
+ return this.running;
235
+ }
236
+ /**
237
+ * Start Unix socket server for hook notifications and UI clients
238
+ */
239
+ async startSocketServer() {
240
+ const socketPath = '/tmp/contextuate-daemon.sock';
241
+ // Remove existing socket file
242
+ try {
243
+ await fs.promises.unlink(socketPath);
244
+ }
245
+ catch (err) {
246
+ // Ignore if doesn't exist
247
+ }
248
+ this.socketServer = net.createServer((socket) => {
249
+ // Track this as a UI client for broadcasting
250
+ this.uiClients.add(socket);
251
+ console.log(`[Daemon] Client connected (${this.uiClients.size} total)`);
252
+ // Handle incoming data (from hooks, wrappers, or UI commands)
253
+ let buffer = '';
254
+ let isWrapper = false;
255
+ let wrapperId = null;
256
+ socket.on('data', (data) => {
257
+ buffer += data.toString();
258
+ const lines = buffer.split('\n');
259
+ buffer = lines.pop() || '';
260
+ for (const line of lines) {
261
+ if (line.trim()) {
262
+ try {
263
+ const message = JSON.parse(line);
264
+ // Handle wrapper registration
265
+ if (message.type === 'wrapper_register') {
266
+ isWrapper = true;
267
+ wrapperId = message.wrapperId;
268
+ this.handleWrapperRegister(socket, message);
269
+ continue;
270
+ }
271
+ // Handle wrapper messages
272
+ if (message.type === 'wrapper_started') {
273
+ this.handleWrapperStarted(message);
274
+ continue;
275
+ }
276
+ if (message.type === 'wrapper_ended') {
277
+ this.handleWrapperEnded(message);
278
+ continue;
279
+ }
280
+ if (message.type === 'state_changed') {
281
+ this.handleWrapperStateChange(message);
282
+ continue;
283
+ }
284
+ if (message.type === 'output') {
285
+ this.handleWrapperOutput(message);
286
+ continue;
287
+ }
288
+ // Handle input injection request from UI
289
+ if (message.type === 'inject_input') {
290
+ this.handleInputInjection(message);
291
+ continue;
292
+ }
293
+ // Handle resize request from UI
294
+ if (message.type === 'resize_wrapper') {
295
+ this.handleWrapperResize(message);
296
+ continue;
297
+ }
298
+ // Handle spawn wrapper request
299
+ if (message.type === 'spawn_wrapper') {
300
+ this.handleSpawnWrapper(socket, message);
301
+ continue;
302
+ }
303
+ // Handle kill wrapper request
304
+ if (message.type === 'kill_wrapper') {
305
+ this.handleKillWrapper(message);
306
+ continue;
307
+ }
308
+ // Handle get wrappers request
309
+ if (message.type === 'get_wrappers') {
310
+ this.handleGetWrappers(socket);
311
+ continue;
312
+ }
313
+ // Circuit breaker messages
314
+ if (message.type === 'get_circuit_health') {
315
+ this.handleGetCircuitHealth(socket);
316
+ continue;
317
+ }
318
+ if (message.type === 'get_session_health') {
319
+ this.handleGetSessionHealth(socket, message);
320
+ continue;
321
+ }
322
+ if (message.type === 'reset_circuit') {
323
+ this.handleResetCircuit(message);
324
+ continue;
325
+ }
326
+ if (message.type === 'update_circuit_config') {
327
+ this.handleUpdateCircuitConfig(socket, message);
328
+ continue;
329
+ }
330
+ // Process hook events immediately for instant updates
331
+ if (message.id && message.eventType) {
332
+ // This is an event from a hook - process it instantly
333
+ this.processor.processEvent(message, null).catch(err => {
334
+ console.error('[Daemon] Error processing socket event:', err);
335
+ });
336
+ // Check if this event indicates waiting for input
337
+ // and notify relevant wrapper
338
+ this.checkAndNotifyWrapperState(message);
339
+ }
340
+ }
341
+ catch (err) {
342
+ // Ignore parse errors
343
+ }
344
+ }
345
+ }
346
+ });
347
+ socket.on('close', () => {
348
+ this.uiClients.delete(socket);
349
+ // Clean up legacy wrapper session if this was an external wrapper
350
+ if (isWrapper && wrapperId) {
351
+ this.legacyWrapperSessions.delete(wrapperId);
352
+ console.log(`[Daemon] Legacy wrapper ${wrapperId} disconnected (${this.legacyWrapperSessions.size} legacy wrappers remaining)`);
353
+ }
354
+ else {
355
+ console.log(`[Daemon] Client disconnected (${this.uiClients.size} remaining)`);
356
+ }
357
+ });
358
+ socket.on('error', () => {
359
+ this.uiClients.delete(socket);
360
+ if (isWrapper && wrapperId) {
361
+ this.legacyWrapperSessions.delete(wrapperId);
362
+ }
363
+ });
364
+ });
365
+ this.socketServer.listen(socketPath, () => {
366
+ console.log(`[Daemon] Socket server listening on ${socketPath}`);
367
+ });
368
+ }
369
+ /**
370
+ * Handle wrapper registration (legacy external wrapper process)
371
+ */
372
+ handleWrapperRegister(socket, message) {
373
+ const session = {
374
+ wrapperId: message.wrapperId,
375
+ socket,
376
+ pid: message.pid,
377
+ claudeSessionId: null,
378
+ state: 'starting',
379
+ cwd: '',
380
+ startTime: Date.now(),
381
+ };
382
+ this.legacyWrapperSessions.set(message.wrapperId, session);
383
+ console.log(`[Daemon] Legacy wrapper registered: ${message.wrapperId} (PID: ${message.pid})`);
384
+ // Acknowledge registration
385
+ socket.write(JSON.stringify({ type: 'registered', wrapperId: message.wrapperId }) + '\n');
386
+ // Notify UI clients about new wrapper
387
+ this.broadcastToClients({
388
+ type: 'wrapper_connected',
389
+ wrapperId: message.wrapperId,
390
+ state: 'starting',
391
+ });
392
+ }
393
+ /**
394
+ * Handle wrapper started notification (legacy)
395
+ */
396
+ handleWrapperStarted(message) {
397
+ const session = this.legacyWrapperSessions.get(message.wrapperId);
398
+ if (session) {
399
+ session.cwd = message.cwd || '';
400
+ console.log(`[Daemon] Legacy wrapper ${message.wrapperId} started Claude in ${session.cwd}`);
401
+ }
402
+ }
403
+ /**
404
+ * Handle wrapper ended notification (legacy)
405
+ */
406
+ handleWrapperEnded(message) {
407
+ const session = this.legacyWrapperSessions.get(message.wrapperId);
408
+ if (session) {
409
+ session.state = 'ended';
410
+ console.log(`[Daemon] Legacy wrapper ${message.wrapperId} ended (exit: ${message.exitCode})`);
411
+ // Notify UI clients
412
+ this.broadcastToClients({
413
+ type: 'wrapper_disconnected',
414
+ wrapperId: message.wrapperId,
415
+ exitCode: message.exitCode,
416
+ });
417
+ }
418
+ }
419
+ /**
420
+ * Handle wrapper state change (legacy)
421
+ */
422
+ handleWrapperStateChange(message) {
423
+ const session = this.legacyWrapperSessions.get(message.wrapperId);
424
+ if (session) {
425
+ session.state = message.state;
426
+ console.log(`[Daemon] Legacy wrapper ${message.wrapperId} state: ${message.state}`);
427
+ // Notify UI clients about state change
428
+ this.broadcastToClients({
429
+ type: 'wrapper_state',
430
+ wrapperId: message.wrapperId,
431
+ state: message.state,
432
+ });
433
+ }
434
+ }
435
+ /**
436
+ * Handle wrapper output (for legacy external wrappers)
437
+ */
438
+ handleWrapperOutput(message) {
439
+ // Forward output to UI clients for session log view
440
+ this.broadcastToClients({
441
+ type: 'wrapper_output',
442
+ wrapperId: message.wrapperId,
443
+ data: message.data,
444
+ timestamp: message.timestamp,
445
+ });
446
+ }
447
+ /**
448
+ * Handle input injection request from UI
449
+ */
450
+ handleInputInjection(message) {
451
+ const { wrapperId, input } = message;
452
+ // Try managed wrapper first
453
+ if (this.wrapperManager.writeInput(wrapperId, input)) {
454
+ return;
455
+ }
456
+ // Fall back to legacy wrapper
457
+ const session = this.legacyWrapperSessions.get(wrapperId);
458
+ if (session) {
459
+ session.socket.write(JSON.stringify({
460
+ type: 'inject_input',
461
+ input,
462
+ }) + '\n');
463
+ }
464
+ }
465
+ /**
466
+ * Handle resize request from UI
467
+ */
468
+ handleWrapperResize(message) {
469
+ const { wrapperId, cols, rows } = message;
470
+ // Try managed wrapper first
471
+ if (this.wrapperManager.resize(wrapperId, cols, rows)) {
472
+ return;
473
+ }
474
+ // Fall back to legacy wrapper
475
+ const session = this.legacyWrapperSessions.get(wrapperId);
476
+ if (session) {
477
+ session.socket.write(JSON.stringify({
478
+ type: 'resize',
479
+ cols,
480
+ rows,
481
+ }) + '\n');
482
+ }
483
+ }
484
+ /**
485
+ * Handle spawn wrapper request
486
+ */
487
+ async handleSpawnWrapper(socket, message) {
488
+ try {
489
+ const wrapperId = await this.wrapperManager.spawn({
490
+ cwd: message.cwd || process.cwd(),
491
+ args: message.args || [],
492
+ cols: message.cols || 120,
493
+ rows: message.rows || 40,
494
+ });
495
+ // Send response with wrapper ID
496
+ socket.write(JSON.stringify({
497
+ type: 'wrapper_spawned',
498
+ wrapperId,
499
+ success: true,
500
+ }) + '\n');
501
+ console.log(`[Daemon] Spawned wrapper ${wrapperId}`);
502
+ }
503
+ catch (err) {
504
+ const error = err;
505
+ socket.write(JSON.stringify({
506
+ type: 'wrapper_spawned',
507
+ success: false,
508
+ error: error.message,
509
+ }) + '\n');
510
+ console.error(`[Daemon] Failed to spawn wrapper:`, error.message);
511
+ }
512
+ }
513
+ /**
514
+ * Handle kill wrapper request
515
+ */
516
+ handleKillWrapper(message) {
517
+ const { wrapperId } = message;
518
+ if (this.wrapperManager.kill(wrapperId)) {
519
+ console.log(`[Daemon] Killed wrapper ${wrapperId}`);
520
+ }
521
+ }
522
+ /**
523
+ * Handle get wrappers request
524
+ */
525
+ handleGetWrappers(socket) {
526
+ const managedWrappers = this.wrapperManager.getAll().map(w => ({
527
+ wrapperId: w.wrapperId,
528
+ pid: w.pid,
529
+ state: w.state,
530
+ claudeSessionId: w.claudeSessionId,
531
+ cwd: w.cwd,
532
+ startTime: w.startTime,
533
+ managed: true,
534
+ }));
535
+ const legacyWrappers = Array.from(this.legacyWrapperSessions.values()).map(w => ({
536
+ wrapperId: w.wrapperId,
537
+ pid: w.pid,
538
+ state: w.state,
539
+ claudeSessionId: w.claudeSessionId,
540
+ cwd: w.cwd,
541
+ startTime: w.startTime,
542
+ managed: false,
543
+ }));
544
+ socket.write(JSON.stringify({
545
+ type: 'wrappers_list',
546
+ wrappers: [...managedWrappers, ...legacyWrappers],
547
+ }) + '\n');
548
+ }
549
+ /**
550
+ * Check if a hook event indicates waiting for input
551
+ * and notify relevant wrapper
552
+ */
553
+ checkAndNotifyWrapperState(event) {
554
+ // Stop events typically indicate Claude is waiting for input
555
+ if (event.hookType === 'Stop' || event.hookType === 'Notification') {
556
+ // Try managed wrappers first
557
+ for (const wrapper of this.wrapperManager.getAll()) {
558
+ if (wrapper.claudeSessionId === event.sessionId) {
559
+ this.wrapperManager.updateState(wrapper.wrapperId, 'waiting_input');
560
+ return;
561
+ }
562
+ // Try to match by working directory
563
+ if (!wrapper.claudeSessionId && event.workingDirectory === wrapper.cwd) {
564
+ this.wrapperManager.updateState(wrapper.wrapperId, 'waiting_input', event.sessionId);
565
+ console.log(`[Daemon] Associated managed wrapper ${wrapper.wrapperId} with Claude session ${event.sessionId}`);
566
+ return;
567
+ }
568
+ }
569
+ // Try legacy wrappers
570
+ for (const [wrapperId, session] of this.legacyWrapperSessions) {
571
+ // Match by Claude session ID if we have it
572
+ if (session.claudeSessionId === event.sessionId) {
573
+ session.state = 'waiting_input';
574
+ session.socket.write(JSON.stringify({
575
+ type: 'state_update',
576
+ state: 'waiting_input',
577
+ }) + '\n');
578
+ this.broadcastToClients({
579
+ type: 'wrapper_state',
580
+ wrapperId,
581
+ state: 'waiting_input',
582
+ });
583
+ return;
584
+ }
585
+ // Try to match by working directory
586
+ if (!session.claudeSessionId && event.workingDirectory === session.cwd) {
587
+ // Associate this Claude session with the wrapper
588
+ session.claudeSessionId = event.sessionId;
589
+ session.state = 'waiting_input';
590
+ console.log(`[Daemon] Associated legacy wrapper ${wrapperId} with Claude session ${event.sessionId}`);
591
+ session.socket.write(JSON.stringify({
592
+ type: 'state_update',
593
+ state: 'waiting_input',
594
+ }) + '\n');
595
+ this.broadcastToClients({
596
+ type: 'wrapper_state',
597
+ wrapperId,
598
+ state: 'waiting_input',
599
+ claudeSessionId: event.sessionId,
600
+ });
601
+ return;
602
+ }
603
+ }
604
+ }
605
+ }
606
+ // =============================================================================
607
+ // Circuit Breaker Handlers
608
+ // =============================================================================
609
+ /**
610
+ * Handle get circuit health request
611
+ */
612
+ handleGetCircuitHealth(socket) {
613
+ const health = this.circuitBreaker.getAllHealth();
614
+ socket.write(JSON.stringify({
615
+ type: 'circuit_health',
616
+ health,
617
+ }) + '\n');
618
+ }
619
+ /**
620
+ * Handle get session health request
621
+ */
622
+ handleGetSessionHealth(socket, message) {
623
+ const { sessionId } = message;
624
+ const health = this.circuitBreaker.getSessionHealth(sessionId);
625
+ if (health) {
626
+ socket.write(JSON.stringify({
627
+ type: 'session_health',
628
+ health,
629
+ }) + '\n');
630
+ }
631
+ else {
632
+ socket.write(JSON.stringify({
633
+ type: 'error',
634
+ message: `No health data for session ${sessionId}`,
635
+ }) + '\n');
636
+ }
637
+ }
638
+ /**
639
+ * Handle reset circuit request
640
+ */
641
+ handleResetCircuit(message) {
642
+ const { sessionId } = message;
643
+ this.circuitBreaker.resetCircuit(sessionId);
644
+ console.log(`[Daemon] Circuit reset for session ${sessionId}`);
645
+ }
646
+ /**
647
+ * Handle update circuit config request
648
+ */
649
+ handleUpdateCircuitConfig(socket, message) {
650
+ const { config } = message;
651
+ this.circuitBreaker.updateConfig(config);
652
+ // Broadcast updated config to all clients
653
+ this.broadcastToClients({
654
+ type: 'circuit_config',
655
+ config: this.circuitBreaker.getConfig(),
656
+ });
657
+ socket.write(JSON.stringify({
658
+ type: 'circuit_config',
659
+ config: this.circuitBreaker.getConfig(),
660
+ }) + '\n');
661
+ }
662
+ /**
663
+ * Get the circuit breaker instance (for external access)
664
+ */
665
+ getCircuitBreaker() {
666
+ return this.circuitBreaker;
667
+ }
668
+ /**
669
+ * Get list of active wrapper sessions (for UI)
670
+ */
671
+ getWrapperSessions() {
672
+ const managed = this.wrapperManager.getAll().map(s => ({
673
+ wrapperId: s.wrapperId,
674
+ state: s.state,
675
+ claudeSessionId: s.claudeSessionId,
676
+ managed: true,
677
+ }));
678
+ const legacy = Array.from(this.legacyWrapperSessions.values()).map(s => ({
679
+ wrapperId: s.wrapperId,
680
+ state: s.state,
681
+ claudeSessionId: s.claudeSessionId,
682
+ managed: false,
683
+ }));
684
+ return [...managed, ...legacy];
685
+ }
686
+ }
687
+ exports.MonitorDaemon = MonitorDaemon;
688
+ /**
689
+ * Start the daemon (exported for CLI)
690
+ */
691
+ async function startDaemon(config) {
692
+ const daemon = new MonitorDaemon(config);
693
+ await daemon.start();
694
+ return daemon;
695
+ }