@monoes/monobrowse 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/browser/action-builder/analyzer.d.ts +11 -0
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -0
- package/dist/src/browser/action-builder/analyzer.js +93 -0
- package/dist/src/browser/action-builder/analyzer.js.map +1 -0
- package/dist/src/browser/action-builder/types.d.ts +46 -0
- package/dist/src/browser/action-builder/types.d.ts.map +1 -0
- package/dist/src/browser/action-builder/types.js +2 -0
- package/dist/src/browser/action-builder/types.js.map +1 -0
- package/dist/src/browser/actions.d.ts +29 -0
- package/dist/src/browser/actions.d.ts.map +1 -0
- package/dist/src/browser/actions.js +398 -0
- package/dist/src/browser/actions.js.map +1 -0
- package/dist/src/browser/adapters/gemini.d.ts +3 -0
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -0
- package/dist/src/browser/adapters/gemini.js +13 -0
- package/dist/src/browser/adapters/gemini.js.map +1 -0
- package/dist/src/browser/adapters/google.d.ts +3 -0
- package/dist/src/browser/adapters/google.d.ts.map +1 -0
- package/dist/src/browser/adapters/google.js +17 -0
- package/dist/src/browser/adapters/google.js.map +1 -0
- package/dist/src/browser/adapters/index.d.ts +15 -0
- package/dist/src/browser/adapters/index.d.ts.map +1 -0
- package/dist/src/browser/adapters/index.js +18 -0
- package/dist/src/browser/adapters/index.js.map +1 -0
- package/dist/src/browser/adapters/instagram.d.ts +3 -0
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -0
- package/dist/src/browser/adapters/instagram.js +14 -0
- package/dist/src/browser/adapters/instagram.js.map +1 -0
- package/dist/src/browser/adapters/linkedin.d.ts +3 -0
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -0
- package/dist/src/browser/adapters/linkedin.js +13 -0
- package/dist/src/browser/adapters/linkedin.js.map +1 -0
- package/dist/src/browser/adapters/microsoft.d.ts +3 -0
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -0
- package/dist/src/browser/adapters/microsoft.js +16 -0
- package/dist/src/browser/adapters/microsoft.js.map +1 -0
- package/dist/src/browser/adapters/x.d.ts +3 -0
- package/dist/src/browser/adapters/x.d.ts.map +1 -0
- package/dist/src/browser/adapters/x.js +13 -0
- package/dist/src/browser/adapters/x.js.map +1 -0
- package/dist/src/browser/batch.d.ts +5 -0
- package/dist/src/browser/batch.d.ts.map +1 -0
- package/dist/src/browser/batch.js +2 -0
- package/dist/src/browser/batch.js.map +1 -0
- package/dist/src/browser/browser.d.ts +15 -0
- package/dist/src/browser/browser.d.ts.map +1 -0
- package/dist/src/browser/browser.js +256 -0
- package/dist/src/browser/browser.js.map +1 -0
- package/dist/src/browser/cdp.d.ts +18 -0
- package/dist/src/browser/cdp.d.ts.map +1 -0
- package/dist/src/browser/cdp.js +153 -0
- package/dist/src/browser/cdp.js.map +1 -0
- package/dist/src/browser/console-log.d.ts +23 -0
- package/dist/src/browser/console-log.d.ts.map +1 -0
- package/dist/src/browser/console-log.js +104 -0
- package/dist/src/browser/console-log.js.map +1 -0
- package/dist/src/browser/dashboard/api-types.d.ts +50 -0
- package/dist/src/browser/dashboard/api-types.d.ts.map +1 -0
- package/dist/src/browser/dashboard/api-types.js +14 -0
- package/dist/src/browser/dashboard/api-types.js.map +1 -0
- package/dist/src/browser/dashboard/server.d.ts +11 -0
- package/dist/src/browser/dashboard/server.d.ts.map +1 -0
- package/dist/src/browser/dashboard/server.js +191 -0
- package/dist/src/browser/dashboard/server.js.map +1 -0
- package/dist/src/browser/dialog.d.ts +12 -0
- package/dist/src/browser/dialog.d.ts.map +1 -0
- package/dist/src/browser/dialog.js +53 -0
- package/dist/src/browser/dialog.js.map +1 -0
- package/dist/src/browser/emulation.d.ts +15 -0
- package/dist/src/browser/emulation.d.ts.map +1 -0
- package/dist/src/browser/emulation.js +62 -0
- package/dist/src/browser/emulation.js.map +1 -0
- package/dist/src/browser/find.d.ts +22 -0
- package/dist/src/browser/find.d.ts.map +1 -0
- package/dist/src/browser/find.js +169 -0
- package/dist/src/browser/find.js.map +1 -0
- package/dist/src/browser/har.d.ts +27 -0
- package/dist/src/browser/har.d.ts.map +1 -0
- package/dist/src/browser/har.js +161 -0
- package/dist/src/browser/har.js.map +1 -0
- package/dist/src/browser/index.d.ts +23 -0
- package/dist/src/browser/index.d.ts.map +1 -0
- package/dist/src/browser/index.js +23 -0
- package/dist/src/browser/index.js.map +1 -0
- package/dist/src/browser/network.d.ts +32 -0
- package/dist/src/browser/network.d.ts.map +1 -0
- package/dist/src/browser/network.js +214 -0
- package/dist/src/browser/network.js.map +1 -0
- package/dist/src/browser/pdf.d.ts +15 -0
- package/dist/src/browser/pdf.d.ts.map +1 -0
- package/dist/src/browser/pdf.js +27 -0
- package/dist/src/browser/pdf.js.map +1 -0
- package/dist/src/browser/profiler.d.ts +10 -0
- package/dist/src/browser/profiler.d.ts.map +1 -0
- package/dist/src/browser/profiler.js +77 -0
- package/dist/src/browser/profiler.js.map +1 -0
- package/dist/src/browser/record.d.ts +21 -0
- package/dist/src/browser/record.d.ts.map +1 -0
- package/dist/src/browser/record.js +59 -0
- package/dist/src/browser/record.js.map +1 -0
- package/dist/src/browser/screenshot.d.ts +15 -0
- package/dist/src/browser/screenshot.d.ts.map +1 -0
- package/dist/src/browser/screenshot.js +38 -0
- package/dist/src/browser/screenshot.js.map +1 -0
- package/dist/src/browser/session.d.ts +8 -0
- package/dist/src/browser/session.d.ts.map +1 -0
- package/dist/src/browser/session.js +91 -0
- package/dist/src/browser/session.js.map +1 -0
- package/dist/src/browser/snapshot.d.ts +12 -0
- package/dist/src/browser/snapshot.d.ts.map +1 -0
- package/dist/src/browser/snapshot.js +179 -0
- package/dist/src/browser/snapshot.js.map +1 -0
- package/dist/src/browser/storage.d.ts +12 -0
- package/dist/src/browser/storage.d.ts.map +1 -0
- package/dist/src/browser/storage.js +46 -0
- package/dist/src/browser/storage.js.map +1 -0
- package/dist/src/browser/tabs.d.ts +8 -0
- package/dist/src/browser/tabs.d.ts.map +1 -0
- package/dist/src/browser/tabs.js +25 -0
- package/dist/src/browser/tabs.js.map +1 -0
- package/dist/src/browser/trace.d.ts +10 -0
- package/dist/src/browser/trace.d.ts.map +1 -0
- package/dist/src/browser/trace.js +87 -0
- package/dist/src/browser/trace.js.map +1 -0
- package/dist/src/browser/types.d.ts +111 -0
- package/dist/src/browser/types.d.ts.map +1 -0
- package/dist/src/browser/types.js +16 -0
- package/dist/src/browser/types.js.map +1 -0
- package/dist/src/browser/vitals.d.ts +15 -0
- package/dist/src/browser/vitals.d.ts.map +1 -0
- package/dist/src/browser/vitals.js +116 -0
- package/dist/src/browser/vitals.js.map +1 -0
- package/dist/src/browser/wait.d.ts +4 -0
- package/dist/src/browser/wait.d.ts.map +1 -0
- package/dist/src/browser/wait.js +146 -0
- package/dist/src/browser/wait.js.map +1 -0
- package/dist/src/browser/workflow/builtin-handlers.d.ts +3 -0
- package/dist/src/browser/workflow/builtin-handlers.d.ts.map +1 -0
- package/dist/src/browser/workflow/builtin-handlers.js +343 -0
- package/dist/src/browser/workflow/builtin-handlers.js.map +1 -0
- package/dist/src/browser/workflow/engine.d.ts +10 -0
- package/dist/src/browser/workflow/engine.d.ts.map +1 -0
- package/dist/src/browser/workflow/engine.js +174 -0
- package/dist/src/browser/workflow/engine.js.map +1 -0
- package/dist/src/browser/workflow/expression.d.ts +4 -0
- package/dist/src/browser/workflow/expression.d.ts.map +1 -0
- package/dist/src/browser/workflow/expression.js +89 -0
- package/dist/src/browser/workflow/expression.js.map +1 -0
- package/dist/src/browser/workflow/index.d.ts +6 -0
- package/dist/src/browser/workflow/index.d.ts.map +1 -0
- package/dist/src/browser/workflow/index.js +6 -0
- package/dist/src/browser/workflow/index.js.map +1 -0
- package/dist/src/browser/workflow/store.d.ts +8 -0
- package/dist/src/browser/workflow/store.d.ts.map +1 -0
- package/dist/src/browser/workflow/store.js +87 -0
- package/dist/src/browser/workflow/store.js.map +1 -0
- package/dist/src/browser/workflow/types.d.ts +62 -0
- package/dist/src/browser/workflow/types.d.ts.map +1 -0
- package/dist/src/browser/workflow/types.js +2 -0
- package/dist/src/browser/workflow/types.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +56 -0
- package/src/browser/action-builder/analyzer.ts +127 -0
- package/src/browser/action-builder/types.ts +17 -0
- package/src/browser/actions.ts +518 -0
- package/src/browser/adapters/gemini.ts +15 -0
- package/src/browser/adapters/google.ts +20 -0
- package/src/browser/adapters/index.ts +33 -0
- package/src/browser/adapters/instagram.ts +16 -0
- package/src/browser/adapters/linkedin.ts +15 -0
- package/src/browser/adapters/microsoft.ts +19 -0
- package/src/browser/adapters/x.ts +15 -0
- package/src/browser/batch.ts +4 -0
- package/src/browser/browser.ts +265 -0
- package/src/browser/cdp.ts +156 -0
- package/src/browser/console-log.ts +120 -0
- package/src/browser/dashboard/api-types.ts +71 -0
- package/src/browser/dashboard/server.ts +206 -0
- package/src/browser/dashboard/ui.html +444 -0
- package/src/browser/dialog.ts +63 -0
- package/src/browser/emulation.ts +94 -0
- package/src/browser/find.ts +225 -0
- package/src/browser/har.ts +202 -0
- package/src/browser/index.ts +22 -0
- package/src/browser/network.ts +244 -0
- package/src/browser/pdf.ts +41 -0
- package/src/browser/profiler.ts +96 -0
- package/src/browser/record.ts +92 -0
- package/src/browser/screenshot.ts +67 -0
- package/src/browser/session.ts +118 -0
- package/src/browser/snapshot.ts +248 -0
- package/src/browser/storage.ts +54 -0
- package/src/browser/tabs.ts +37 -0
- package/src/browser/trace.ts +106 -0
- package/src/browser/types.ts +135 -0
- package/src/browser/vitals.ts +122 -0
- package/src/browser/wait.ts +178 -0
- package/src/browser/workflow/builtin-handlers.ts +379 -0
- package/src/browser/workflow/engine.ts +202 -0
- package/src/browser/workflow/expression.ts +107 -0
- package/src/browser/workflow/index.ts +5 -0
- package/src/browser/workflow/store.ts +87 -0
- package/src/browser/workflow/types.ts +62 -0
- package/src/index.ts +6 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed API response schemas for the monobrowse dashboard.
|
|
3
|
+
*
|
|
4
|
+
* These interfaces are the canonical contract between server.ts and
|
|
5
|
+
* dashboard clients (ui.html + any external consumers). Changing a
|
|
6
|
+
* field name here will surface breakage at compile time rather than
|
|
7
|
+
* at runtime — preventing the field-name regression class documented
|
|
8
|
+
* in the sprint review (e.g. `msg` vs `message`, `tokensIn` vs
|
|
9
|
+
* `tokens_in`).
|
|
10
|
+
*
|
|
11
|
+
* @module @monomind/cli/browser/dashboard/api-types
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Org / run lifecycle
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface OrgStatusResponse {
|
|
19
|
+
name: string;
|
|
20
|
+
status: 'ready' | 'running' | 'stopped';
|
|
21
|
+
currentRun?: RunRecord;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface RunRecord {
|
|
25
|
+
/** Stable run identifier (UUID or monotonic ID). */
|
|
26
|
+
id: string;
|
|
27
|
+
workflowId: string;
|
|
28
|
+
startedAt: number; // Unix ms timestamp
|
|
29
|
+
itemsProcessed: number;
|
|
30
|
+
status: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Session management
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export interface SessionListResponse {
|
|
38
|
+
sessions: SessionEntry[];
|
|
39
|
+
total: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SessionEntry {
|
|
43
|
+
sessionId: string;
|
|
44
|
+
name: string;
|
|
45
|
+
createdAt: string; // ISO-8601 timestamp
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Budget / cost tracking
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
export interface BudgetResponse {
|
|
53
|
+
/** Input tokens consumed in this session/run. */
|
|
54
|
+
tokensIn: number;
|
|
55
|
+
/** Output tokens generated in this session/run. */
|
|
56
|
+
tokensOut: number;
|
|
57
|
+
/** Total monetary cost in `currency` units. */
|
|
58
|
+
totalCost: number;
|
|
59
|
+
currency: 'USD';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Error envelope
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
export interface ApiError {
|
|
67
|
+
/** Human-readable error message. */
|
|
68
|
+
error: string;
|
|
69
|
+
/** Optional machine-readable error code for programmatic handling. */
|
|
70
|
+
code?: string;
|
|
71
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import type { StepEvent, RunRecord } from '../workflow/types.js';
|
|
9
|
+
|
|
10
|
+
const RUNS_FILE = join(homedir(), '.monomind', 'browse-runs.json');
|
|
11
|
+
|
|
12
|
+
async function loadPersistedRuns(): Promise<RunRecord[]> {
|
|
13
|
+
if (!existsSync(RUNS_FILE)) return [];
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(await readFile(RUNS_FILE, 'utf-8')) as RunRecord[];
|
|
16
|
+
} catch { return []; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const _require = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const DEFAULT_PORT = parseInt(process.env['MONOBROWSE_DASHBOARD_PORT'] ?? '4242', 10);
|
|
23
|
+
const MAX_RUN_HISTORY = 50;
|
|
24
|
+
|
|
25
|
+
export interface DashboardServer {
|
|
26
|
+
broadcast(event: StepEvent): void;
|
|
27
|
+
addRunRecord(record: RunRecord): void;
|
|
28
|
+
isStopRequested(runId: string): boolean;
|
|
29
|
+
close(): void;
|
|
30
|
+
port: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let instance: DashboardServer | null = null;
|
|
34
|
+
|
|
35
|
+
export function startDashboard(port = DEFAULT_PORT): DashboardServer {
|
|
36
|
+
if (instance) return instance;
|
|
37
|
+
|
|
38
|
+
const runHistory: RunRecord[] = [];
|
|
39
|
+
const stopRequests = new Set<string>();
|
|
40
|
+
/** Map from client (WebSocket or SSE response) to the subscribed project dir,
|
|
41
|
+
* or null if the client subscribed without a ?dir= filter (receives all events). */
|
|
42
|
+
const clientDirs = new Map<any, string | null>();
|
|
43
|
+
|
|
44
|
+
// Try to load ws, fall back to SSE
|
|
45
|
+
let WebSocketServer: any = null;
|
|
46
|
+
try {
|
|
47
|
+
const wsModule = _require('ws');
|
|
48
|
+
WebSocketServer = wsModule.WebSocketServer ?? wsModule.Server;
|
|
49
|
+
} catch {
|
|
50
|
+
// ws not available — fall back to SSE
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ui.html must be copied to dist/ alongside server.js during build
|
|
54
|
+
let uiHtml: string;
|
|
55
|
+
try {
|
|
56
|
+
uiHtml = readFileSync(join(__dirname, 'ui.html'), 'utf-8');
|
|
57
|
+
} catch {
|
|
58
|
+
uiHtml = `<!DOCTYPE html><html><head><title>monobrowse dashboard</title></head><body style="background:#0f0f1a;color:#ccc;font-family:system-ui;padding:20px"><h1>monobrowse dashboard</h1><p>Dashboard UI not found. Run the build to include ui.html.</p><script>let _retryDelay=1000;function connectSSE(){const es=new EventSource('/events');es.onmessage=e=>{console.log(JSON.parse(e.data));_retryDelay=1000;};es.onerror=()=>{es.close();setTimeout(connectSSE,Math.min(_retryDelay*=2,30000));};};connectSSE();</script></body></html>`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const server = createServer((req, res) => {
|
|
62
|
+
const url = req.url ?? '/';
|
|
63
|
+
|
|
64
|
+
if (url === '/' && req.method === 'GET') {
|
|
65
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
66
|
+
res.end(uiHtml);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (url === '/runs' && req.method === 'GET') {
|
|
71
|
+
// Merge in-memory runs with persisted runs so the UI shows history even across restarts
|
|
72
|
+
loadPersistedRuns().then(persisted => {
|
|
73
|
+
const seen = new Set(runHistory.map(r => r.id));
|
|
74
|
+
const merged = [...runHistory, ...persisted.filter(r => !seen.has(r.id))]
|
|
75
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
76
|
+
.slice(0, MAX_RUN_HISTORY);
|
|
77
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
78
|
+
res.end(JSON.stringify(merged));
|
|
79
|
+
}).catch(() => {
|
|
80
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
81
|
+
res.end(JSON.stringify(runHistory));
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (url.startsWith('/stop/') && req.method === 'POST') {
|
|
87
|
+
const runId = url.slice(6);
|
|
88
|
+
stopRequests.add(runId);
|
|
89
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
90
|
+
res.end(JSON.stringify({ ok: true, runId }));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Parse the URL to correctly handle query parameters such as ?dir=
|
|
95
|
+
const parsed = new URL(url, 'http://localhost');
|
|
96
|
+
if (parsed.pathname === '/events' && (req.method === 'GET' || req.method === 'HEAD') && !WebSocketServer) {
|
|
97
|
+
// SSE endpoint (fallback when ws not available).
|
|
98
|
+
// No CORS header — the dashboard is served from 127.0.0.1:4242 and no cross-origin
|
|
99
|
+
// access is needed. A wildcard ACAO would let any web page subscribe to workflow events.
|
|
100
|
+
const subscribedDir = parsed.searchParams.get('dir') ?? null;
|
|
101
|
+
res.writeHead(200, {
|
|
102
|
+
'Content-Type': 'text/event-stream',
|
|
103
|
+
'Cache-Control': 'no-cache',
|
|
104
|
+
'Connection': 'keep-alive',
|
|
105
|
+
});
|
|
106
|
+
res.write(`data: ${JSON.stringify({ type: 'connected' })}\n\n`);
|
|
107
|
+
clientDirs.set(res, subscribedDir);
|
|
108
|
+
// 30-second keep-alive heartbeat prevents proxy idle-timeout drops.
|
|
109
|
+
const heartbeat = setInterval(() => {
|
|
110
|
+
try { res.write(': keep-alive\n\n'); } catch { clearInterval(heartbeat); }
|
|
111
|
+
}, 30_000);
|
|
112
|
+
req.on('close', () => {
|
|
113
|
+
clearInterval(heartbeat);
|
|
114
|
+
clientDirs.delete(res);
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
res.writeHead(404);
|
|
120
|
+
res.end('Not found');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (WebSocketServer) {
|
|
124
|
+
const wss = new WebSocketServer({ server });
|
|
125
|
+
wss.on('connection', async (ws: any, req: any) => {
|
|
126
|
+
// Extract optional project-dir filter from the WebSocket upgrade URL
|
|
127
|
+
const upgradeUrl = req?.url ?? '/ws';
|
|
128
|
+
const upgradeParsed = new URL(upgradeUrl, 'http://localhost');
|
|
129
|
+
const subscribedDir = upgradeParsed.searchParams.get('dir') ?? null;
|
|
130
|
+
clientDirs.set(ws, subscribedDir);
|
|
131
|
+
// Merge in-memory and persisted runs so history survives server restarts
|
|
132
|
+
const persisted = await loadPersistedRuns().catch(() => [] as RunRecord[]);
|
|
133
|
+
const seen = new Set(runHistory.map(r => r.id));
|
|
134
|
+
const merged = [...runHistory, ...persisted.filter(r => !seen.has(r.id))]
|
|
135
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
136
|
+
.slice(0, MAX_RUN_HISTORY);
|
|
137
|
+
ws.send(JSON.stringify({ type: 'history', runs: merged }));
|
|
138
|
+
ws.on('close', () => clientDirs.delete(ws));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
server.on('error', (err: NodeJS.ErrnoException) => {
|
|
143
|
+
if (err.code === 'EADDRINUSE') {
|
|
144
|
+
console.log(`Dashboard port ${port} in use — workflow will run without live dashboard.`);
|
|
145
|
+
} else {
|
|
146
|
+
console.error(`Dashboard server error: ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
// Replace with a no-op instance so the engine doesn't crash
|
|
149
|
+
instance = {
|
|
150
|
+
broadcast: () => {},
|
|
151
|
+
addRunRecord: () => {},
|
|
152
|
+
isStopRequested: () => false,
|
|
153
|
+
close: () => {},
|
|
154
|
+
port,
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
server.listen(port, '127.0.0.1');
|
|
159
|
+
|
|
160
|
+
function broadcast(event: StepEvent): void {
|
|
161
|
+
const msg = JSON.stringify(event);
|
|
162
|
+
for (const [client, subscribedDir] of clientDirs) {
|
|
163
|
+
// Server-side project filtering: if the client subscribed with ?dir=<path>, only
|
|
164
|
+
// deliver events whose projectDir matches. Clients without a dir filter (subscribedDir
|
|
165
|
+
// === null) receive all events, preserving backward-compatibility with ui.html.
|
|
166
|
+
if (subscribedDir !== null && event.projectDir !== undefined && subscribedDir !== event.projectDir) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
if (typeof client.send === 'function') {
|
|
171
|
+
client.send(msg); // WebSocket
|
|
172
|
+
} else {
|
|
173
|
+
client.write(`data: ${msg}\n\n`); // SSE
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
clientDirs.delete(client);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function addRunRecord(record: RunRecord): void {
|
|
182
|
+
const idx = runHistory.findIndex(r => r.id === record.id);
|
|
183
|
+
if (idx >= 0) {
|
|
184
|
+
runHistory[idx] = record;
|
|
185
|
+
} else {
|
|
186
|
+
runHistory.unshift(record);
|
|
187
|
+
if (runHistory.length > MAX_RUN_HISTORY) runHistory.pop();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isStopRequested(runId: string): boolean {
|
|
192
|
+
return stopRequests.has(runId);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function close(): void {
|
|
196
|
+
server.close();
|
|
197
|
+
instance = null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
instance = { broadcast, addRunRecord, isStopRequested, close, port };
|
|
201
|
+
return instance;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function getDashboard(): DashboardServer | null {
|
|
205
|
+
return instance;
|
|
206
|
+
}
|