agileflow 2.99.8 → 3.0.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.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/lib/cache-provider.js +155 -0
- package/lib/codebase-indexer.js +1 -1
- package/lib/content-sanitizer.js +1 -0
- package/lib/dashboard-protocol.js +25 -0
- package/lib/dashboard-server.js +282 -150
- package/lib/errors.js +18 -0
- package/lib/file-cache.js +1 -1
- package/lib/flag-detection.js +11 -20
- package/lib/git-operations.js +15 -33
- package/lib/merge-operations.js +40 -34
- package/lib/process-executor.js +199 -0
- package/lib/registry-cache.js +13 -47
- package/lib/skill-loader.js +206 -0
- package/lib/smart-json-file.js +2 -4
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +13 -12
- package/scripts/agileflow-statusline.sh +30 -0
- package/scripts/agileflow-welcome.js +181 -212
- package/scripts/archive-completed-stories.sh +3 -0
- package/scripts/auto-self-improve.js +3 -3
- package/scripts/ci-summary.js +294 -0
- package/scripts/claude-smart.sh +85 -0
- package/scripts/claude-tmux.sh +272 -161
- package/scripts/damage-control-multi-agent.js +227 -0
- package/scripts/lib/bus-utils.js +471 -0
- package/scripts/lib/configure-detect.js +87 -10
- package/scripts/lib/configure-features.js +110 -4
- package/scripts/lib/configure-repair.js +5 -6
- package/scripts/lib/configure-utils.js +2 -3
- package/scripts/lib/context-formatter.js +87 -8
- package/scripts/lib/damage-control-utils.js +37 -3
- package/scripts/lib/file-lock.js +392 -0
- package/scripts/lib/ideation-index.js +2 -5
- package/scripts/lib/lifecycle-detector.js +123 -0
- package/scripts/lib/process-cleanup.js +55 -81
- package/scripts/lib/scale-detector.js +357 -0
- package/scripts/lib/signal-detectors.js +779 -0
- package/scripts/lib/story-state-machine.js +1 -1
- package/scripts/lib/sync-ideation-status.js +2 -3
- package/scripts/lib/task-registry.js +7 -1
- package/scripts/lib/team-events.js +357 -0
- package/scripts/messaging-bridge.js +79 -36
- package/scripts/migrate-ideation-index.js +37 -14
- package/scripts/obtain-context.js +37 -19
- package/scripts/precompact-context.sh +3 -0
- package/scripts/ralph-loop.js +3 -4
- package/scripts/smart-detect.js +390 -0
- package/scripts/team-manager.js +174 -30
- package/src/core/commands/audit.md +13 -11
- package/src/core/commands/babysit.md +162 -115
- package/src/core/commands/changelog.md +21 -4
- package/src/core/commands/configure.md +141 -21
- package/src/core/commands/debt.md +12 -2
- package/src/core/commands/feedback.md +7 -6
- package/src/core/commands/ideate/history.md +1 -1
- package/src/core/commands/ideate/new.md +5 -5
- package/src/core/commands/logic/audit.md +2 -2
- package/src/core/commands/pr.md +7 -6
- package/src/core/commands/research/analyze.md +28 -20
- package/src/core/commands/research/ask.md +43 -0
- package/src/core/commands/research/import.md +29 -21
- package/src/core/commands/research/list.md +8 -7
- package/src/core/commands/research/synthesize.md +356 -20
- package/src/core/commands/research/view.md +8 -5
- package/src/core/commands/review.md +24 -6
- package/src/core/commands/skill/create.md +34 -0
- package/tools/cli/lib/docs-setup.js +4 -0
package/lib/dashboard-server.js
CHANGED
|
@@ -21,32 +21,35 @@
|
|
|
21
21
|
|
|
22
22
|
'use strict';
|
|
23
23
|
|
|
24
|
-
const http = require('http');
|
|
25
|
-
const crypto = require('crypto');
|
|
26
24
|
const { EventEmitter } = require('events');
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
|
|
26
|
+
// Lazy-loaded dependencies - deferred until first use
|
|
27
|
+
let _http, _crypto, _protocol, _paths, _validatePaths, _childProcess;
|
|
28
|
+
|
|
29
|
+
function getHttp() {
|
|
30
|
+
if (!_http) _http = require('http');
|
|
31
|
+
return _http;
|
|
32
|
+
}
|
|
33
|
+
function getCrypto() {
|
|
34
|
+
if (!_crypto) _crypto = require('crypto');
|
|
35
|
+
return _crypto;
|
|
36
|
+
}
|
|
37
|
+
function getProtocol() {
|
|
38
|
+
if (!_protocol) _protocol = require('./dashboard-protocol');
|
|
39
|
+
return _protocol;
|
|
40
|
+
}
|
|
41
|
+
function getPaths() {
|
|
42
|
+
if (!_paths) _paths = require('./paths');
|
|
43
|
+
return _paths;
|
|
44
|
+
}
|
|
45
|
+
function getValidatePaths() {
|
|
46
|
+
if (!_validatePaths) _validatePaths = require('./validate-paths');
|
|
47
|
+
return _validatePaths;
|
|
48
|
+
}
|
|
49
|
+
function getChildProcess() {
|
|
50
|
+
if (!_childProcess) _childProcess = require('child_process');
|
|
51
|
+
return _childProcess;
|
|
52
|
+
}
|
|
50
53
|
|
|
51
54
|
// Lazy-load automation modules to avoid circular dependencies
|
|
52
55
|
let AutomationRegistry = null;
|
|
@@ -142,7 +145,7 @@ class DashboardSession {
|
|
|
142
145
|
send(message) {
|
|
143
146
|
if (this.ws && this.ws.writable) {
|
|
144
147
|
try {
|
|
145
|
-
const frame = encodeWebSocketFrame(serializeMessage(message));
|
|
148
|
+
const frame = encodeWebSocketFrame(getProtocol().serializeMessage(message));
|
|
146
149
|
this.ws.write(frame);
|
|
147
150
|
this.lastActivity = new Date();
|
|
148
151
|
} catch (error) {
|
|
@@ -180,7 +183,7 @@ class DashboardSession {
|
|
|
180
183
|
setState(state) {
|
|
181
184
|
this.state = state;
|
|
182
185
|
this.send(
|
|
183
|
-
createSessionState(this.id, state, {
|
|
186
|
+
getProtocol().createSessionState(this.id, state, {
|
|
184
187
|
messageCount: this.messages.length,
|
|
185
188
|
lastActivity: this.lastActivity.toISOString(),
|
|
186
189
|
})
|
|
@@ -249,13 +252,13 @@ class TerminalInstance {
|
|
|
249
252
|
|
|
250
253
|
this.pty.onData(data => {
|
|
251
254
|
if (!this.closed) {
|
|
252
|
-
this.session.send(createTerminalOutput(this.id, data));
|
|
255
|
+
this.session.send(getProtocol().createTerminalOutput(this.id, data));
|
|
253
256
|
}
|
|
254
257
|
});
|
|
255
258
|
|
|
256
259
|
this.pty.onExit(({ exitCode }) => {
|
|
257
260
|
this.closed = true;
|
|
258
|
-
this.session.send(createTerminalExit(this.id, exitCode));
|
|
261
|
+
this.session.send(getProtocol().createTerminalExit(this.id, exitCode));
|
|
259
262
|
});
|
|
260
263
|
|
|
261
264
|
return true;
|
|
@@ -275,7 +278,7 @@ class TerminalInstance {
|
|
|
275
278
|
const filteredEnv = this._getFilteredEnv();
|
|
276
279
|
|
|
277
280
|
// Use bash with interactive flag for better compatibility
|
|
278
|
-
this.pty = spawn(this.shell, ['-i'], {
|
|
281
|
+
this.pty = getChildProcess().spawn(this.shell, ['-i'], {
|
|
279
282
|
cwd: this.cwd,
|
|
280
283
|
env: {
|
|
281
284
|
...filteredEnv,
|
|
@@ -291,25 +294,27 @@ class TerminalInstance {
|
|
|
291
294
|
|
|
292
295
|
this.pty.stdout.on('data', data => {
|
|
293
296
|
if (!this.closed) {
|
|
294
|
-
this.session.send(createTerminalOutput(this.id, data.toString()));
|
|
297
|
+
this.session.send(getProtocol().createTerminalOutput(this.id, data.toString()));
|
|
295
298
|
}
|
|
296
299
|
});
|
|
297
300
|
|
|
298
301
|
this.pty.stderr.on('data', data => {
|
|
299
302
|
if (!this.closed) {
|
|
300
|
-
this.session.send(createTerminalOutput(this.id, data.toString()));
|
|
303
|
+
this.session.send(getProtocol().createTerminalOutput(this.id, data.toString()));
|
|
301
304
|
}
|
|
302
305
|
});
|
|
303
306
|
|
|
304
307
|
this.pty.on('close', exitCode => {
|
|
305
308
|
this.closed = true;
|
|
306
|
-
this.session.send(createTerminalExit(this.id, exitCode));
|
|
309
|
+
this.session.send(getProtocol().createTerminalExit(this.id, exitCode));
|
|
307
310
|
});
|
|
308
311
|
|
|
309
312
|
this.pty.on('error', error => {
|
|
310
313
|
console.error('[Terminal] Shell error:', error.message);
|
|
311
314
|
if (!this.closed) {
|
|
312
|
-
this.session.send(
|
|
315
|
+
this.session.send(
|
|
316
|
+
getProtocol().createTerminalOutput(this.id, `\r\nError: ${error.message}\r\n`)
|
|
317
|
+
);
|
|
313
318
|
}
|
|
314
319
|
});
|
|
315
320
|
|
|
@@ -318,7 +323,7 @@ class TerminalInstance {
|
|
|
318
323
|
if (!this.closed) {
|
|
319
324
|
const welcomeMsg = `\x1b[32mAgileFlow Terminal\x1b[0m (basic mode - node-pty not available)\r\n`;
|
|
320
325
|
const cwdMsg = `Working directory: ${this.cwd}\r\n\r\n`;
|
|
321
|
-
this.session.send(createTerminalOutput(this.id, welcomeMsg + cwdMsg));
|
|
326
|
+
this.session.send(getProtocol().createTerminalOutput(this.id, welcomeMsg + cwdMsg));
|
|
322
327
|
}
|
|
323
328
|
}, 100);
|
|
324
329
|
|
|
@@ -355,7 +360,7 @@ class TerminalInstance {
|
|
|
355
360
|
}
|
|
356
361
|
|
|
357
362
|
// Echo to terminal
|
|
358
|
-
this.session.send(createTerminalOutput(this.id, echoData));
|
|
363
|
+
this.session.send(getProtocol().createTerminalOutput(this.id, echoData));
|
|
359
364
|
|
|
360
365
|
// Send to shell stdin
|
|
361
366
|
this.pty.stdin.write(data);
|
|
@@ -406,7 +411,7 @@ class TerminalManager {
|
|
|
406
411
|
* @returns {string} - Terminal ID
|
|
407
412
|
*/
|
|
408
413
|
createTerminal(session, options = {}) {
|
|
409
|
-
const terminalId = options.id ||
|
|
414
|
+
const terminalId = options.id || getCrypto().randomBytes(8).toString('hex');
|
|
410
415
|
const terminal = new TerminalInstance(terminalId, session, {
|
|
411
416
|
cwd: options.cwd || session.projectRoot,
|
|
412
417
|
cols: options.cols,
|
|
@@ -507,13 +512,13 @@ class DashboardServer extends EventEmitter {
|
|
|
507
512
|
|
|
508
513
|
this.port = options.port || DEFAULT_PORT;
|
|
509
514
|
this.host = options.host || DEFAULT_HOST;
|
|
510
|
-
this.projectRoot = options.projectRoot || getProjectRoot();
|
|
515
|
+
this.projectRoot = options.projectRoot || getPaths().getProjectRoot();
|
|
511
516
|
|
|
512
517
|
// Auth is on by default - auto-generate key if not provided
|
|
513
518
|
// Set requireAuth: false explicitly to disable
|
|
514
519
|
this.requireAuth = options.requireAuth !== false;
|
|
515
520
|
this.apiKey =
|
|
516
|
-
options.apiKey || (this.requireAuth ?
|
|
521
|
+
options.apiKey || (this.requireAuth ? getCrypto().randomBytes(32).toString('hex') : null);
|
|
517
522
|
|
|
518
523
|
// Session management
|
|
519
524
|
this.sessions = new Map();
|
|
@@ -536,12 +541,15 @@ class DashboardServer extends EventEmitter {
|
|
|
536
541
|
this.httpServer = null;
|
|
537
542
|
|
|
538
543
|
// Validate project
|
|
539
|
-
if (!isAgileflowProject(this.projectRoot)) {
|
|
544
|
+
if (!getPaths().isAgileflowProject(this.projectRoot)) {
|
|
540
545
|
throw new Error(`Not an AgileFlow project: ${this.projectRoot}`);
|
|
541
546
|
}
|
|
542
547
|
|
|
543
548
|
// Initialize automation registry lazily
|
|
544
549
|
this._initAutomations();
|
|
550
|
+
|
|
551
|
+
// Listen for team metrics saves to broadcast to clients
|
|
552
|
+
this._initTeamMetricsListener();
|
|
545
553
|
}
|
|
546
554
|
|
|
547
555
|
/**
|
|
@@ -555,12 +563,12 @@ class DashboardServer extends EventEmitter {
|
|
|
555
563
|
// Listen to runner events
|
|
556
564
|
this._automationRunner.on('started', ({ automationId }) => {
|
|
557
565
|
this._runningAutomations.set(automationId, { startTime: Date.now() });
|
|
558
|
-
this.broadcast(createAutomationStatus(automationId, 'running'));
|
|
566
|
+
this.broadcast(getProtocol().createAutomationStatus(automationId, 'running'));
|
|
559
567
|
});
|
|
560
568
|
|
|
561
569
|
this._automationRunner.on('completed', ({ automationId, result }) => {
|
|
562
570
|
this._runningAutomations.delete(automationId);
|
|
563
|
-
this.broadcast(createAutomationStatus(automationId, 'completed', result));
|
|
571
|
+
this.broadcast(getProtocol().createAutomationStatus(automationId, 'completed', result));
|
|
564
572
|
|
|
565
573
|
// Add result to inbox if it has output or changes
|
|
566
574
|
if (result.output || result.changes) {
|
|
@@ -570,7 +578,9 @@ class DashboardServer extends EventEmitter {
|
|
|
570
578
|
|
|
571
579
|
this._automationRunner.on('failed', ({ automationId, result }) => {
|
|
572
580
|
this._runningAutomations.delete(automationId);
|
|
573
|
-
this.broadcast(
|
|
581
|
+
this.broadcast(
|
|
582
|
+
getProtocol().createAutomationStatus(automationId, 'error', { error: result.error })
|
|
583
|
+
);
|
|
574
584
|
|
|
575
585
|
// Add failure to inbox
|
|
576
586
|
this._addToInbox(automationId, result);
|
|
@@ -607,7 +617,7 @@ class DashboardServer extends EventEmitter {
|
|
|
607
617
|
};
|
|
608
618
|
|
|
609
619
|
this._inbox.set(itemId, item);
|
|
610
|
-
this.broadcast(createInboxItem(item));
|
|
620
|
+
this.broadcast(getProtocol().createInboxItem(item));
|
|
611
621
|
}
|
|
612
622
|
|
|
613
623
|
/**
|
|
@@ -623,7 +633,7 @@ class DashboardServer extends EventEmitter {
|
|
|
623
633
|
'Cache-Control': 'no-store',
|
|
624
634
|
};
|
|
625
635
|
|
|
626
|
-
this.httpServer =
|
|
636
|
+
this.httpServer = getHttp().createServer((req, res) => {
|
|
627
637
|
// Simple health check endpoint
|
|
628
638
|
if (req.url === '/health') {
|
|
629
639
|
res.writeHead(200, securityHeaders);
|
|
@@ -713,7 +723,7 @@ class DashboardServer extends EventEmitter {
|
|
|
713
723
|
const providedBuffer = Buffer.from(providedKey, 'utf8');
|
|
714
724
|
if (
|
|
715
725
|
keyBuffer.length !== providedBuffer.length ||
|
|
716
|
-
!
|
|
726
|
+
!getCrypto().timingSafeEqual(keyBuffer, providedBuffer)
|
|
717
727
|
) {
|
|
718
728
|
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
719
729
|
socket.destroy();
|
|
@@ -744,7 +754,7 @@ class DashboardServer extends EventEmitter {
|
|
|
744
754
|
|
|
745
755
|
// Complete WebSocket handshake
|
|
746
756
|
const key = req.headers['sec-websocket-key'];
|
|
747
|
-
const acceptKey =
|
|
757
|
+
const acceptKey = getCrypto()
|
|
748
758
|
.createHash('sha1')
|
|
749
759
|
.update(key + WS_GUID)
|
|
750
760
|
.digest('base64');
|
|
@@ -778,7 +788,7 @@ class DashboardServer extends EventEmitter {
|
|
|
778
788
|
}
|
|
779
789
|
|
|
780
790
|
// Generate new session ID
|
|
781
|
-
return
|
|
791
|
+
return getCrypto().randomBytes(16).toString('hex');
|
|
782
792
|
}
|
|
783
793
|
|
|
784
794
|
/**
|
|
@@ -801,7 +811,7 @@ class DashboardServer extends EventEmitter {
|
|
|
801
811
|
|
|
802
812
|
// Send initial state
|
|
803
813
|
session.send(
|
|
804
|
-
createSessionState(sessionId, 'connected', {
|
|
814
|
+
getProtocol().createSessionState(sessionId, 'connected', {
|
|
805
815
|
resumed: isResume,
|
|
806
816
|
messageCount: session.messages.length,
|
|
807
817
|
project: require('path').basename(this.projectRoot),
|
|
@@ -814,6 +824,9 @@ class DashboardServer extends EventEmitter {
|
|
|
814
824
|
// Send project status (stories/epics)
|
|
815
825
|
this.sendStatusUpdate(session);
|
|
816
826
|
|
|
827
|
+
// Send team metrics
|
|
828
|
+
this.sendTeamMetrics(session);
|
|
829
|
+
|
|
817
830
|
// Send session list with sync info
|
|
818
831
|
this.sendSessionList(session);
|
|
819
832
|
|
|
@@ -874,83 +887,85 @@ class DashboardServer extends EventEmitter {
|
|
|
874
887
|
handleMessage(session, data) {
|
|
875
888
|
// Rate limit incoming messages
|
|
876
889
|
if (!session.checkRateLimit()) {
|
|
877
|
-
session.send(
|
|
890
|
+
session.send(
|
|
891
|
+
getProtocol().createError('RATE_LIMITED', 'Too many messages, please slow down')
|
|
892
|
+
);
|
|
878
893
|
return;
|
|
879
894
|
}
|
|
880
895
|
|
|
881
|
-
const message = parseInboundMessage(data);
|
|
896
|
+
const message = getProtocol().parseInboundMessage(data);
|
|
882
897
|
if (!message) {
|
|
883
|
-
session.send(createError('INVALID_MESSAGE', 'Failed to parse message'));
|
|
898
|
+
session.send(getProtocol().createError('INVALID_MESSAGE', 'Failed to parse message'));
|
|
884
899
|
return;
|
|
885
900
|
}
|
|
886
901
|
|
|
887
902
|
console.log(`[Session ${session.id}] Received: ${message.type}`);
|
|
888
903
|
|
|
889
904
|
switch (message.type) {
|
|
890
|
-
case InboundMessageType.MESSAGE:
|
|
905
|
+
case getProtocol().InboundMessageType.MESSAGE:
|
|
891
906
|
this.handleUserMessage(session, message);
|
|
892
907
|
break;
|
|
893
908
|
|
|
894
|
-
case InboundMessageType.CANCEL:
|
|
909
|
+
case getProtocol().InboundMessageType.CANCEL:
|
|
895
910
|
this.handleCancel(session);
|
|
896
911
|
break;
|
|
897
912
|
|
|
898
|
-
case InboundMessageType.REFRESH:
|
|
913
|
+
case getProtocol().InboundMessageType.REFRESH:
|
|
899
914
|
this.handleRefresh(session, message);
|
|
900
915
|
break;
|
|
901
916
|
|
|
902
|
-
case InboundMessageType.GIT_STAGE:
|
|
903
|
-
case InboundMessageType.GIT_UNSTAGE:
|
|
904
|
-
case InboundMessageType.GIT_REVERT:
|
|
905
|
-
case InboundMessageType.GIT_COMMIT:
|
|
917
|
+
case getProtocol().InboundMessageType.GIT_STAGE:
|
|
918
|
+
case getProtocol().InboundMessageType.GIT_UNSTAGE:
|
|
919
|
+
case getProtocol().InboundMessageType.GIT_REVERT:
|
|
920
|
+
case getProtocol().InboundMessageType.GIT_COMMIT:
|
|
906
921
|
this.handleGitAction(session, message);
|
|
907
922
|
break;
|
|
908
923
|
|
|
909
|
-
case InboundMessageType.GIT_DIFF_REQUEST:
|
|
924
|
+
case getProtocol().InboundMessageType.GIT_DIFF_REQUEST:
|
|
910
925
|
this.handleDiffRequest(session, message);
|
|
911
926
|
break;
|
|
912
927
|
|
|
913
|
-
case InboundMessageType.SESSION_CLOSE:
|
|
928
|
+
case getProtocol().InboundMessageType.SESSION_CLOSE:
|
|
914
929
|
this.closeSession(session.id);
|
|
915
930
|
break;
|
|
916
931
|
|
|
917
|
-
case InboundMessageType.TERMINAL_SPAWN:
|
|
932
|
+
case getProtocol().InboundMessageType.TERMINAL_SPAWN:
|
|
918
933
|
this.handleTerminalSpawn(session, message);
|
|
919
934
|
break;
|
|
920
935
|
|
|
921
|
-
case InboundMessageType.TERMINAL_INPUT:
|
|
936
|
+
case getProtocol().InboundMessageType.TERMINAL_INPUT:
|
|
922
937
|
this.handleTerminalInput(session, message);
|
|
923
938
|
break;
|
|
924
939
|
|
|
925
|
-
case InboundMessageType.TERMINAL_RESIZE:
|
|
940
|
+
case getProtocol().InboundMessageType.TERMINAL_RESIZE:
|
|
926
941
|
this.handleTerminalResize(session, message);
|
|
927
942
|
break;
|
|
928
943
|
|
|
929
|
-
case InboundMessageType.TERMINAL_CLOSE:
|
|
944
|
+
case getProtocol().InboundMessageType.TERMINAL_CLOSE:
|
|
930
945
|
this.handleTerminalClose(session, message);
|
|
931
946
|
break;
|
|
932
947
|
|
|
933
|
-
case InboundMessageType.AUTOMATION_LIST_REQUEST:
|
|
948
|
+
case getProtocol().InboundMessageType.AUTOMATION_LIST_REQUEST:
|
|
934
949
|
this.sendAutomationList(session);
|
|
935
950
|
break;
|
|
936
951
|
|
|
937
|
-
case InboundMessageType.AUTOMATION_RUN:
|
|
952
|
+
case getProtocol().InboundMessageType.AUTOMATION_RUN:
|
|
938
953
|
this.handleAutomationRun(session, message);
|
|
939
954
|
break;
|
|
940
955
|
|
|
941
|
-
case InboundMessageType.AUTOMATION_STOP:
|
|
956
|
+
case getProtocol().InboundMessageType.AUTOMATION_STOP:
|
|
942
957
|
this.handleAutomationStop(session, message);
|
|
943
958
|
break;
|
|
944
959
|
|
|
945
|
-
case InboundMessageType.INBOX_LIST_REQUEST:
|
|
960
|
+
case getProtocol().InboundMessageType.INBOX_LIST_REQUEST:
|
|
946
961
|
this.sendInboxList(session);
|
|
947
962
|
break;
|
|
948
963
|
|
|
949
|
-
case InboundMessageType.INBOX_ACTION:
|
|
964
|
+
case getProtocol().InboundMessageType.INBOX_ACTION:
|
|
950
965
|
this.handleInboxAction(session, message);
|
|
951
966
|
break;
|
|
952
967
|
|
|
953
|
-
case InboundMessageType.OPEN_FILE:
|
|
968
|
+
case getProtocol().InboundMessageType.OPEN_FILE:
|
|
954
969
|
this.handleOpenFile(session, message);
|
|
955
970
|
break;
|
|
956
971
|
|
|
@@ -966,7 +981,7 @@ class DashboardServer extends EventEmitter {
|
|
|
966
981
|
handleUserMessage(session, message) {
|
|
967
982
|
const content = message.content?.trim();
|
|
968
983
|
if (!content) {
|
|
969
|
-
session.send(createError('EMPTY_MESSAGE', 'Message content is empty'));
|
|
984
|
+
session.send(getProtocol().createError('EMPTY_MESSAGE', 'Message content is empty'));
|
|
970
985
|
return;
|
|
971
986
|
}
|
|
972
987
|
|
|
@@ -985,7 +1000,7 @@ class DashboardServer extends EventEmitter {
|
|
|
985
1000
|
*/
|
|
986
1001
|
handleCancel(session) {
|
|
987
1002
|
session.setState('idle');
|
|
988
|
-
session.send(createNotification('info', 'Cancelled', 'Operation cancelled'));
|
|
1003
|
+
session.send(getProtocol().createNotification('info', 'Cancelled', 'Operation cancelled'));
|
|
989
1004
|
this.emit('user:cancel', session);
|
|
990
1005
|
}
|
|
991
1006
|
|
|
@@ -1015,9 +1030,13 @@ class DashboardServer extends EventEmitter {
|
|
|
1015
1030
|
case 'inbox':
|
|
1016
1031
|
this.sendInboxList(session);
|
|
1017
1032
|
break;
|
|
1033
|
+
case 'team_metrics':
|
|
1034
|
+
this.sendTeamMetrics(session);
|
|
1035
|
+
break;
|
|
1018
1036
|
default:
|
|
1019
1037
|
this.sendGitStatus(session);
|
|
1020
1038
|
this.sendStatusUpdate(session);
|
|
1039
|
+
this.sendTeamMetrics(session);
|
|
1021
1040
|
this.sendSessionList(session);
|
|
1022
1041
|
this.sendAutomationList(session);
|
|
1023
1042
|
this.sendInboxList(session);
|
|
@@ -1035,12 +1054,12 @@ class DashboardServer extends EventEmitter {
|
|
|
1035
1054
|
if (files && files.length > 0) {
|
|
1036
1055
|
for (const f of files) {
|
|
1037
1056
|
if (typeof f !== 'string' || f.includes('\0')) {
|
|
1038
|
-
session.send(createError('GIT_ERROR', 'Invalid file path'));
|
|
1057
|
+
session.send(getProtocol().createError('GIT_ERROR', 'Invalid file path'));
|
|
1039
1058
|
return;
|
|
1040
1059
|
}
|
|
1041
1060
|
const resolved = require('path').resolve(this.projectRoot, f);
|
|
1042
1061
|
if (!resolved.startsWith(this.projectRoot)) {
|
|
1043
|
-
session.send(createError('GIT_ERROR', 'File path outside project'));
|
|
1062
|
+
session.send(getProtocol().createError('GIT_ERROR', 'File path outside project'));
|
|
1044
1063
|
return;
|
|
1045
1064
|
}
|
|
1046
1065
|
}
|
|
@@ -1053,7 +1072,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1053
1072
|
commitMessage.length > 10000 ||
|
|
1054
1073
|
commitMessage.includes('\0')
|
|
1055
1074
|
) {
|
|
1056
|
-
session.send(createError('GIT_ERROR', 'Invalid commit message'));
|
|
1075
|
+
session.send(getProtocol().createError('GIT_ERROR', 'Invalid commit message'));
|
|
1057
1076
|
return;
|
|
1058
1077
|
}
|
|
1059
1078
|
}
|
|
@@ -1062,40 +1081,50 @@ class DashboardServer extends EventEmitter {
|
|
|
1062
1081
|
|
|
1063
1082
|
try {
|
|
1064
1083
|
switch (type) {
|
|
1065
|
-
case InboundMessageType.GIT_STAGE:
|
|
1084
|
+
case getProtocol().InboundMessageType.GIT_STAGE:
|
|
1066
1085
|
if (fileArgs) {
|
|
1067
|
-
execFileSync('git', ['add', '--', ...fileArgs], {
|
|
1086
|
+
getChildProcess().execFileSync('git', ['add', '--', ...fileArgs], {
|
|
1087
|
+
cwd: this.projectRoot,
|
|
1088
|
+
});
|
|
1068
1089
|
} else {
|
|
1069
|
-
execFileSync('git', ['add', '-A'], { cwd: this.projectRoot });
|
|
1090
|
+
getChildProcess().execFileSync('git', ['add', '-A'], { cwd: this.projectRoot });
|
|
1070
1091
|
}
|
|
1071
1092
|
break;
|
|
1072
|
-
case InboundMessageType.GIT_UNSTAGE:
|
|
1093
|
+
case getProtocol().InboundMessageType.GIT_UNSTAGE:
|
|
1073
1094
|
if (fileArgs) {
|
|
1074
|
-
execFileSync('git', ['restore', '--staged', '--', ...fileArgs], {
|
|
1095
|
+
getChildProcess().execFileSync('git', ['restore', '--staged', '--', ...fileArgs], {
|
|
1075
1096
|
cwd: this.projectRoot,
|
|
1076
1097
|
});
|
|
1077
1098
|
} else {
|
|
1078
|
-
execFileSync('git', ['restore', '--staged', '.'], {
|
|
1099
|
+
getChildProcess().execFileSync('git', ['restore', '--staged', '.'], {
|
|
1100
|
+
cwd: this.projectRoot,
|
|
1101
|
+
});
|
|
1079
1102
|
}
|
|
1080
1103
|
break;
|
|
1081
|
-
case InboundMessageType.GIT_REVERT:
|
|
1104
|
+
case getProtocol().InboundMessageType.GIT_REVERT:
|
|
1082
1105
|
if (fileArgs) {
|
|
1083
|
-
execFileSync('git', ['checkout', '--', ...fileArgs], {
|
|
1106
|
+
getChildProcess().execFileSync('git', ['checkout', '--', ...fileArgs], {
|
|
1107
|
+
cwd: this.projectRoot,
|
|
1108
|
+
});
|
|
1084
1109
|
}
|
|
1085
1110
|
break;
|
|
1086
|
-
case InboundMessageType.GIT_COMMIT:
|
|
1111
|
+
case getProtocol().InboundMessageType.GIT_COMMIT:
|
|
1087
1112
|
if (commitMessage) {
|
|
1088
|
-
execFileSync('git', ['commit', '-m', commitMessage], {
|
|
1113
|
+
getChildProcess().execFileSync('git', ['commit', '-m', commitMessage], {
|
|
1114
|
+
cwd: this.projectRoot,
|
|
1115
|
+
});
|
|
1089
1116
|
}
|
|
1090
1117
|
break;
|
|
1091
1118
|
}
|
|
1092
1119
|
|
|
1093
1120
|
// Send updated git status
|
|
1094
1121
|
this.sendGitStatus(session);
|
|
1095
|
-
session.send(
|
|
1122
|
+
session.send(
|
|
1123
|
+
getProtocol().createNotification('success', 'Git', `${type.replace('git_', '')} completed`)
|
|
1124
|
+
);
|
|
1096
1125
|
} catch (error) {
|
|
1097
1126
|
console.error('[Git Error]', error.message);
|
|
1098
|
-
session.send(createError('GIT_ERROR', 'Git operation failed'));
|
|
1127
|
+
session.send(getProtocol().createError('GIT_ERROR', 'Git operation failed'));
|
|
1099
1128
|
}
|
|
1100
1129
|
}
|
|
1101
1130
|
|
|
@@ -1106,7 +1135,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1106
1135
|
try {
|
|
1107
1136
|
const status = this.getGitStatus();
|
|
1108
1137
|
session.send({
|
|
1109
|
-
type: OutboundMessageType.GIT_STATUS,
|
|
1138
|
+
type: getProtocol().OutboundMessageType.GIT_STATUS,
|
|
1110
1139
|
...status,
|
|
1111
1140
|
timestamp: new Date().toISOString(),
|
|
1112
1141
|
});
|
|
@@ -1120,13 +1149,15 @@ class DashboardServer extends EventEmitter {
|
|
|
1120
1149
|
*/
|
|
1121
1150
|
getGitStatus() {
|
|
1122
1151
|
try {
|
|
1123
|
-
const branch =
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1152
|
+
const branch = getChildProcess()
|
|
1153
|
+
.execFileSync('git', ['branch', '--show-current'], {
|
|
1154
|
+
cwd: this.projectRoot,
|
|
1155
|
+
encoding: 'utf8',
|
|
1156
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1157
|
+
})
|
|
1158
|
+
.trim();
|
|
1128
1159
|
|
|
1129
|
-
const statusOutput = execFileSync('git', ['status', '--porcelain'], {
|
|
1160
|
+
const statusOutput = getChildProcess().execFileSync('git', ['status', '--porcelain'], {
|
|
1130
1161
|
cwd: this.projectRoot,
|
|
1131
1162
|
encoding: 'utf8',
|
|
1132
1163
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -1185,7 +1216,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1185
1216
|
const { path: filePath, staged } = message;
|
|
1186
1217
|
|
|
1187
1218
|
if (!filePath) {
|
|
1188
|
-
session.send(createError('INVALID_REQUEST', 'File path is required'));
|
|
1219
|
+
session.send(getProtocol().createError('INVALID_REQUEST', 'File path is required'));
|
|
1189
1220
|
return;
|
|
1190
1221
|
}
|
|
1191
1222
|
|
|
@@ -1194,7 +1225,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1194
1225
|
const stats = this.parseDiffStats(diff);
|
|
1195
1226
|
|
|
1196
1227
|
session.send(
|
|
1197
|
-
createGitDiff(filePath, diff, {
|
|
1228
|
+
getProtocol().createGitDiff(filePath, diff, {
|
|
1198
1229
|
additions: stats.additions,
|
|
1199
1230
|
deletions: stats.deletions,
|
|
1200
1231
|
staged: !!staged,
|
|
@@ -1202,7 +1233,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1202
1233
|
);
|
|
1203
1234
|
} catch (error) {
|
|
1204
1235
|
console.error('[Diff Error]', error.message);
|
|
1205
|
-
session.send(createError('DIFF_ERROR', 'Failed to get diff'));
|
|
1236
|
+
session.send(getProtocol().createError('DIFF_ERROR', 'Failed to get diff'));
|
|
1206
1237
|
}
|
|
1207
1238
|
}
|
|
1208
1239
|
|
|
@@ -1214,7 +1245,9 @@ class DashboardServer extends EventEmitter {
|
|
|
1214
1245
|
*/
|
|
1215
1246
|
getFileDiff(filePath, staged = false) {
|
|
1216
1247
|
// Validate filePath stays within project root
|
|
1217
|
-
const pathResult = validatePath(filePath, this.projectRoot, {
|
|
1248
|
+
const pathResult = getValidatePaths().validatePath(filePath, this.projectRoot, {
|
|
1249
|
+
allowSymlinks: true,
|
|
1250
|
+
});
|
|
1218
1251
|
if (!pathResult.ok) {
|
|
1219
1252
|
return '';
|
|
1220
1253
|
}
|
|
@@ -1222,17 +1255,19 @@ class DashboardServer extends EventEmitter {
|
|
|
1222
1255
|
try {
|
|
1223
1256
|
const diffArgs = staged ? ['diff', '--cached', '--', filePath] : ['diff', '--', filePath];
|
|
1224
1257
|
|
|
1225
|
-
const diff = execFileSync('git', diffArgs, {
|
|
1258
|
+
const diff = getChildProcess().execFileSync('git', diffArgs, {
|
|
1226
1259
|
cwd: this.projectRoot,
|
|
1227
1260
|
encoding: 'utf8',
|
|
1228
1261
|
});
|
|
1229
1262
|
|
|
1230
1263
|
// If no diff, file might be untracked - show entire file content as addition
|
|
1231
1264
|
if (!diff && !staged) {
|
|
1232
|
-
const statusOutput =
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1265
|
+
const statusOutput = getChildProcess()
|
|
1266
|
+
.execFileSync('git', ['status', '--porcelain', '--', filePath], {
|
|
1267
|
+
cwd: this.projectRoot,
|
|
1268
|
+
encoding: 'utf8',
|
|
1269
|
+
})
|
|
1270
|
+
.trim();
|
|
1236
1271
|
|
|
1237
1272
|
// Check if file is untracked
|
|
1238
1273
|
if (statusOutput.startsWith('??')) {
|
|
@@ -1318,12 +1353,68 @@ class DashboardServer extends EventEmitter {
|
|
|
1318
1353
|
})),
|
|
1319
1354
|
};
|
|
1320
1355
|
|
|
1321
|
-
session.send(createStatusUpdate(summary));
|
|
1356
|
+
session.send(getProtocol().createStatusUpdate(summary));
|
|
1322
1357
|
} catch (error) {
|
|
1323
1358
|
console.error('[Status Update Error]', error.message);
|
|
1324
1359
|
}
|
|
1325
1360
|
}
|
|
1326
1361
|
|
|
1362
|
+
/**
|
|
1363
|
+
* Initialize listener for team metrics events
|
|
1364
|
+
*/
|
|
1365
|
+
_initTeamMetricsListener() {
|
|
1366
|
+
try {
|
|
1367
|
+
const { teamMetricsEmitter } = require('../scripts/lib/team-events');
|
|
1368
|
+
teamMetricsEmitter.on('metrics_saved', () => {
|
|
1369
|
+
this.broadcastTeamMetrics();
|
|
1370
|
+
});
|
|
1371
|
+
} catch (e) {
|
|
1372
|
+
// team-events not available - non-critical
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/**
|
|
1377
|
+
* Send team metrics to a single session
|
|
1378
|
+
*/
|
|
1379
|
+
sendTeamMetrics(session) {
|
|
1380
|
+
const path = require('path');
|
|
1381
|
+
const fs = require('fs');
|
|
1382
|
+
const sessionStatePath = path.join(this.projectRoot, 'docs', '09-agents', 'session-state.json');
|
|
1383
|
+
if (!fs.existsSync(sessionStatePath)) return;
|
|
1384
|
+
|
|
1385
|
+
try {
|
|
1386
|
+
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
1387
|
+
const traces = (state.team_metrics && state.team_metrics.traces) || {};
|
|
1388
|
+
|
|
1389
|
+
for (const [traceId, metrics] of Object.entries(traces)) {
|
|
1390
|
+
session.send(getProtocol().createTeamMetrics(traceId, metrics));
|
|
1391
|
+
}
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
// Non-critical
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/**
|
|
1398
|
+
* Broadcast team metrics to all connected clients
|
|
1399
|
+
*/
|
|
1400
|
+
broadcastTeamMetrics() {
|
|
1401
|
+
const path = require('path');
|
|
1402
|
+
const fs = require('fs');
|
|
1403
|
+
const sessionStatePath = path.join(this.projectRoot, 'docs', '09-agents', 'session-state.json');
|
|
1404
|
+
if (!fs.existsSync(sessionStatePath)) return;
|
|
1405
|
+
|
|
1406
|
+
try {
|
|
1407
|
+
const state = JSON.parse(fs.readFileSync(sessionStatePath, 'utf8'));
|
|
1408
|
+
const traces = (state.team_metrics && state.team_metrics.traces) || {};
|
|
1409
|
+
|
|
1410
|
+
for (const [traceId, metrics] of Object.entries(traces)) {
|
|
1411
|
+
this.broadcast(getProtocol().createTeamMetrics(traceId, metrics));
|
|
1412
|
+
}
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
// Non-critical
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1327
1418
|
/**
|
|
1328
1419
|
* Send session list with sync status to dashboard
|
|
1329
1420
|
*/
|
|
@@ -1347,23 +1438,23 @@ class DashboardServer extends EventEmitter {
|
|
|
1347
1438
|
// Get branch and sync status via git
|
|
1348
1439
|
try {
|
|
1349
1440
|
const cwd = s.metadata.worktreePath || this.projectRoot;
|
|
1350
|
-
entry.branch =
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1441
|
+
entry.branch = getChildProcess()
|
|
1442
|
+
.execFileSync('git', ['branch', '--show-current'], {
|
|
1443
|
+
cwd,
|
|
1444
|
+
encoding: 'utf8',
|
|
1445
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1446
|
+
})
|
|
1447
|
+
.trim();
|
|
1355
1448
|
|
|
1356
1449
|
// Get ahead/behind counts relative to upstream
|
|
1357
1450
|
try {
|
|
1358
|
-
const counts =
|
|
1359
|
-
'git',
|
|
1360
|
-
['rev-list', '--left-right', '--count', 'HEAD...@{u}'],
|
|
1361
|
-
{
|
|
1451
|
+
const counts = getChildProcess()
|
|
1452
|
+
.execFileSync('git', ['rev-list', '--left-right', '--count', 'HEAD...@{u}'], {
|
|
1362
1453
|
cwd,
|
|
1363
1454
|
encoding: 'utf8',
|
|
1364
1455
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1456
|
+
})
|
|
1457
|
+
.trim();
|
|
1367
1458
|
const [ahead, behind] = counts.split(/\s+/).map(Number);
|
|
1368
1459
|
entry.ahead = ahead || 0;
|
|
1369
1460
|
entry.behind = behind || 0;
|
|
@@ -1388,7 +1479,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1388
1479
|
sessions.push(entry);
|
|
1389
1480
|
}
|
|
1390
1481
|
|
|
1391
|
-
session.send(createSessionList(sessions));
|
|
1482
|
+
session.send(getProtocol().createSessionList(sessions));
|
|
1392
1483
|
}
|
|
1393
1484
|
|
|
1394
1485
|
/**
|
|
@@ -1398,14 +1489,16 @@ class DashboardServer extends EventEmitter {
|
|
|
1398
1489
|
const { path: filePath, line } = message;
|
|
1399
1490
|
|
|
1400
1491
|
if (!filePath || typeof filePath !== 'string') {
|
|
1401
|
-
session.send(createError('INVALID_REQUEST', 'File path is required'));
|
|
1492
|
+
session.send(getProtocol().createError('INVALID_REQUEST', 'File path is required'));
|
|
1402
1493
|
return;
|
|
1403
1494
|
}
|
|
1404
1495
|
|
|
1405
1496
|
// Validate the path stays within project root
|
|
1406
|
-
const pathResult = validatePath(filePath, this.projectRoot, {
|
|
1497
|
+
const pathResult = getValidatePaths().validatePath(filePath, this.projectRoot, {
|
|
1498
|
+
allowSymlinks: true,
|
|
1499
|
+
});
|
|
1407
1500
|
if (!pathResult.ok) {
|
|
1408
|
-
session.send(createError('OPEN_FILE_ERROR', 'File path outside project'));
|
|
1501
|
+
session.send(getProtocol().createError('OPEN_FILE_ERROR', 'File path outside project'));
|
|
1409
1502
|
return;
|
|
1410
1503
|
}
|
|
1411
1504
|
|
|
@@ -1423,28 +1516,36 @@ class DashboardServer extends EventEmitter {
|
|
|
1423
1516
|
case 'cursor':
|
|
1424
1517
|
case 'windsurf': {
|
|
1425
1518
|
const gotoArg = lineNum ? `${fullPath}:${lineNum}` : fullPath;
|
|
1426
|
-
|
|
1519
|
+
getChildProcess()
|
|
1520
|
+
.spawn(editor, ['--goto', gotoArg], { detached: true, stdio: 'ignore' })
|
|
1521
|
+
.unref();
|
|
1427
1522
|
break;
|
|
1428
1523
|
}
|
|
1429
1524
|
case 'subl':
|
|
1430
1525
|
case 'sublime_text': {
|
|
1431
1526
|
const sublArg = lineNum ? `${fullPath}:${lineNum}` : fullPath;
|
|
1432
|
-
spawn(editor, [sublArg], { detached: true, stdio: 'ignore' }).unref();
|
|
1527
|
+
getChildProcess().spawn(editor, [sublArg], { detached: true, stdio: 'ignore' }).unref();
|
|
1433
1528
|
break;
|
|
1434
1529
|
}
|
|
1435
1530
|
default: {
|
|
1436
1531
|
// Generic: just open the file
|
|
1437
|
-
spawn(editor, [fullPath], { detached: true, stdio: 'ignore' }).unref();
|
|
1532
|
+
getChildProcess().spawn(editor, [fullPath], { detached: true, stdio: 'ignore' }).unref();
|
|
1438
1533
|
break;
|
|
1439
1534
|
}
|
|
1440
1535
|
}
|
|
1441
1536
|
|
|
1442
1537
|
session.send(
|
|
1443
|
-
|
|
1538
|
+
getProtocol().createNotification(
|
|
1539
|
+
'info',
|
|
1540
|
+
'Editor',
|
|
1541
|
+
`Opened ${require('path').basename(fullPath)}`
|
|
1542
|
+
)
|
|
1444
1543
|
);
|
|
1445
1544
|
} catch (error) {
|
|
1446
1545
|
console.error('[Open File Error]', error.message);
|
|
1447
|
-
session.send(
|
|
1546
|
+
session.send(
|
|
1547
|
+
getProtocol().createError('OPEN_FILE_ERROR', `Failed to open file: ${error.message}`)
|
|
1548
|
+
);
|
|
1448
1549
|
}
|
|
1449
1550
|
}
|
|
1450
1551
|
|
|
@@ -1457,10 +1558,15 @@ class DashboardServer extends EventEmitter {
|
|
|
1457
1558
|
// Validate cwd stays within project root
|
|
1458
1559
|
let safeCwd = this.projectRoot;
|
|
1459
1560
|
if (cwd) {
|
|
1460
|
-
const cwdResult = validatePath(cwd, this.projectRoot, {
|
|
1561
|
+
const cwdResult = getValidatePaths().validatePath(cwd, this.projectRoot, {
|
|
1562
|
+
allowSymlinks: true,
|
|
1563
|
+
});
|
|
1461
1564
|
if (!cwdResult.ok) {
|
|
1462
1565
|
session.send(
|
|
1463
|
-
createError(
|
|
1566
|
+
getProtocol().createError(
|
|
1567
|
+
'TERMINAL_ERROR',
|
|
1568
|
+
'Working directory must be within project root'
|
|
1569
|
+
)
|
|
1464
1570
|
);
|
|
1465
1571
|
return;
|
|
1466
1572
|
}
|
|
@@ -1480,7 +1586,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1480
1586
|
timestamp: new Date().toISOString(),
|
|
1481
1587
|
});
|
|
1482
1588
|
} else {
|
|
1483
|
-
session.send(createError('TERMINAL_ERROR', 'Failed to spawn terminal'));
|
|
1589
|
+
session.send(getProtocol().createError('TERMINAL_ERROR', 'Failed to spawn terminal'));
|
|
1484
1590
|
}
|
|
1485
1591
|
}
|
|
1486
1592
|
|
|
@@ -1521,7 +1627,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1521
1627
|
}
|
|
1522
1628
|
|
|
1523
1629
|
this.terminalManager.closeTerminal(terminalId);
|
|
1524
|
-
session.send(createNotification('info', 'Terminal', 'Terminal closed'));
|
|
1630
|
+
session.send(getProtocol().createNotification('info', 'Terminal', 'Terminal closed'));
|
|
1525
1631
|
}
|
|
1526
1632
|
|
|
1527
1633
|
// ==========================================================================
|
|
@@ -1533,7 +1639,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1533
1639
|
*/
|
|
1534
1640
|
sendAutomationList(session) {
|
|
1535
1641
|
if (!this._automationRegistry) {
|
|
1536
|
-
session.send(createAutomationList([]));
|
|
1642
|
+
session.send(getProtocol().createAutomationList([]));
|
|
1537
1643
|
return;
|
|
1538
1644
|
}
|
|
1539
1645
|
|
|
@@ -1555,10 +1661,10 @@ class DashboardServer extends EventEmitter {
|
|
|
1555
1661
|
};
|
|
1556
1662
|
});
|
|
1557
1663
|
|
|
1558
|
-
session.send(createAutomationList(enriched));
|
|
1664
|
+
session.send(getProtocol().createAutomationList(enriched));
|
|
1559
1665
|
} catch (error) {
|
|
1560
1666
|
console.error('[Automations] List error:', error.message);
|
|
1561
|
-
session.send(createAutomationList([]));
|
|
1667
|
+
session.send(getProtocol().createAutomationList([]));
|
|
1562
1668
|
}
|
|
1563
1669
|
}
|
|
1564
1670
|
|
|
@@ -1628,12 +1734,14 @@ class DashboardServer extends EventEmitter {
|
|
|
1628
1734
|
const { id: automationId } = message;
|
|
1629
1735
|
|
|
1630
1736
|
if (!automationId) {
|
|
1631
|
-
session.send(createError('INVALID_REQUEST', 'Automation ID is required'));
|
|
1737
|
+
session.send(getProtocol().createError('INVALID_REQUEST', 'Automation ID is required'));
|
|
1632
1738
|
return;
|
|
1633
1739
|
}
|
|
1634
1740
|
|
|
1635
1741
|
if (!this._automationRunner) {
|
|
1636
|
-
session.send(
|
|
1742
|
+
session.send(
|
|
1743
|
+
getProtocol().createError('AUTOMATION_ERROR', 'Automation runner not initialized')
|
|
1744
|
+
);
|
|
1637
1745
|
return;
|
|
1638
1746
|
}
|
|
1639
1747
|
|
|
@@ -1641,12 +1749,18 @@ class DashboardServer extends EventEmitter {
|
|
|
1641
1749
|
// Check if already running
|
|
1642
1750
|
if (this._runningAutomations.has(automationId)) {
|
|
1643
1751
|
session.send(
|
|
1644
|
-
createNotification(
|
|
1752
|
+
getProtocol().createNotification(
|
|
1753
|
+
'warning',
|
|
1754
|
+
'Automation',
|
|
1755
|
+
`${automationId} is already running`
|
|
1756
|
+
)
|
|
1645
1757
|
);
|
|
1646
1758
|
return;
|
|
1647
1759
|
}
|
|
1648
1760
|
|
|
1649
|
-
session.send(
|
|
1761
|
+
session.send(
|
|
1762
|
+
getProtocol().createNotification('info', 'Automation', `Starting ${automationId}...`)
|
|
1763
|
+
);
|
|
1650
1764
|
|
|
1651
1765
|
// Run the automation (async)
|
|
1652
1766
|
const result = await this._automationRunner.run(automationId);
|
|
@@ -1654,23 +1768,39 @@ class DashboardServer extends EventEmitter {
|
|
|
1654
1768
|
// Send result notification
|
|
1655
1769
|
if (result.success) {
|
|
1656
1770
|
session.send(
|
|
1657
|
-
createNotification(
|
|
1771
|
+
getProtocol().createNotification(
|
|
1772
|
+
'success',
|
|
1773
|
+
'Automation',
|
|
1774
|
+
`${automationId} completed successfully`
|
|
1775
|
+
)
|
|
1658
1776
|
);
|
|
1659
1777
|
} else {
|
|
1660
1778
|
session.send(
|
|
1661
|
-
createNotification(
|
|
1779
|
+
getProtocol().createNotification(
|
|
1780
|
+
'error',
|
|
1781
|
+
'Automation',
|
|
1782
|
+
`${automationId} failed: ${result.error}`
|
|
1783
|
+
)
|
|
1662
1784
|
);
|
|
1663
1785
|
}
|
|
1664
1786
|
|
|
1665
1787
|
// Send final status
|
|
1666
|
-
session.send(
|
|
1788
|
+
session.send(
|
|
1789
|
+
getProtocol().createAutomationStatus(
|
|
1790
|
+
automationId,
|
|
1791
|
+
result.success ? 'idle' : 'error',
|
|
1792
|
+
result
|
|
1793
|
+
)
|
|
1794
|
+
);
|
|
1667
1795
|
|
|
1668
1796
|
// Refresh the list
|
|
1669
1797
|
this.sendAutomationList(session);
|
|
1670
1798
|
} catch (error) {
|
|
1671
1799
|
console.error('[Automation Error]', error.message);
|
|
1672
|
-
session.send(createError('AUTOMATION_ERROR', 'Automation execution failed'));
|
|
1673
|
-
session.send(
|
|
1800
|
+
session.send(getProtocol().createError('AUTOMATION_ERROR', 'Automation execution failed'));
|
|
1801
|
+
session.send(
|
|
1802
|
+
getProtocol().createAutomationStatus(automationId, 'error', { error: 'Execution failed' })
|
|
1803
|
+
);
|
|
1674
1804
|
}
|
|
1675
1805
|
}
|
|
1676
1806
|
|
|
@@ -1681,7 +1811,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1681
1811
|
const { id: automationId } = message;
|
|
1682
1812
|
|
|
1683
1813
|
if (!automationId) {
|
|
1684
|
-
session.send(createError('INVALID_REQUEST', 'Automation ID is required'));
|
|
1814
|
+
session.send(getProtocol().createError('INVALID_REQUEST', 'Automation ID is required'));
|
|
1685
1815
|
return;
|
|
1686
1816
|
}
|
|
1687
1817
|
|
|
@@ -1691,8 +1821,8 @@ class DashboardServer extends EventEmitter {
|
|
|
1691
1821
|
}
|
|
1692
1822
|
|
|
1693
1823
|
this._runningAutomations.delete(automationId);
|
|
1694
|
-
session.send(createAutomationStatus(automationId, 'idle'));
|
|
1695
|
-
session.send(createNotification('info', 'Automation', `${automationId} stopped`));
|
|
1824
|
+
session.send(getProtocol().createAutomationStatus(automationId, 'idle'));
|
|
1825
|
+
session.send(getProtocol().createNotification('info', 'Automation', `${automationId} stopped`));
|
|
1696
1826
|
}
|
|
1697
1827
|
|
|
1698
1828
|
// ==========================================================================
|
|
@@ -1707,7 +1837,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1707
1837
|
(a, b) => new Date(b.timestamp) - new Date(a.timestamp)
|
|
1708
1838
|
);
|
|
1709
1839
|
|
|
1710
|
-
session.send(createInboxList(items));
|
|
1840
|
+
session.send(getProtocol().createInboxList(items));
|
|
1711
1841
|
}
|
|
1712
1842
|
|
|
1713
1843
|
/**
|
|
@@ -1717,13 +1847,13 @@ class DashboardServer extends EventEmitter {
|
|
|
1717
1847
|
const { id: itemId, action } = message;
|
|
1718
1848
|
|
|
1719
1849
|
if (!itemId) {
|
|
1720
|
-
session.send(createError('INVALID_REQUEST', 'Item ID is required'));
|
|
1850
|
+
session.send(getProtocol().createError('INVALID_REQUEST', 'Item ID is required'));
|
|
1721
1851
|
return;
|
|
1722
1852
|
}
|
|
1723
1853
|
|
|
1724
1854
|
const item = this._inbox.get(itemId);
|
|
1725
1855
|
if (!item) {
|
|
1726
|
-
session.send(createError('NOT_FOUND', `Inbox item ${itemId} not found`));
|
|
1856
|
+
session.send(getProtocol().createError('NOT_FOUND', `Inbox item ${itemId} not found`));
|
|
1727
1857
|
return;
|
|
1728
1858
|
}
|
|
1729
1859
|
|
|
@@ -1731,14 +1861,16 @@ class DashboardServer extends EventEmitter {
|
|
|
1731
1861
|
case 'accept':
|
|
1732
1862
|
// Mark as accepted and remove
|
|
1733
1863
|
item.status = 'accepted';
|
|
1734
|
-
session.send(
|
|
1864
|
+
session.send(
|
|
1865
|
+
getProtocol().createNotification('success', 'Inbox', `Accepted: ${item.title}`)
|
|
1866
|
+
);
|
|
1735
1867
|
this._inbox.delete(itemId);
|
|
1736
1868
|
break;
|
|
1737
1869
|
|
|
1738
1870
|
case 'dismiss':
|
|
1739
1871
|
// Mark as dismissed and remove
|
|
1740
1872
|
item.status = 'dismissed';
|
|
1741
|
-
session.send(createNotification('info', 'Inbox', `Dismissed: ${item.title}`));
|
|
1873
|
+
session.send(getProtocol().createNotification('info', 'Inbox', `Dismissed: ${item.title}`));
|
|
1742
1874
|
this._inbox.delete(itemId);
|
|
1743
1875
|
break;
|
|
1744
1876
|
|
|
@@ -1748,7 +1880,7 @@ class DashboardServer extends EventEmitter {
|
|
|
1748
1880
|
break;
|
|
1749
1881
|
|
|
1750
1882
|
default:
|
|
1751
|
-
session.send(createError('INVALID_ACTION', `Unknown action: ${action}`));
|
|
1883
|
+
session.send(getProtocol().createError('INVALID_ACTION', `Unknown action: ${action}`));
|
|
1752
1884
|
return;
|
|
1753
1885
|
}
|
|
1754
1886
|
|