@blackbelt-technology/pi-agent-dashboard 0.2.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/AGENTS.md +342 -0
- package/README.md +619 -0
- package/docs/architecture.md +646 -0
- package/package.json +92 -0
- package/packages/extension/package.json +33 -0
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +85 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +712 -0
- package/packages/extension/src/__tests__/connection.test.ts +344 -0
- package/packages/extension/src/__tests__/credentials-updated.test.ts +26 -0
- package/packages/extension/src/__tests__/dev-build.test.ts +79 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +89 -0
- package/packages/extension/src/__tests__/git-info.test.ts +112 -0
- package/packages/extension/src/__tests__/git-link-builder.test.ts +102 -0
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +232 -0
- package/packages/extension/src/__tests__/openspec-poller.test.ts +119 -0
- package/packages/extension/src/__tests__/process-metrics.test.ts +47 -0
- package/packages/extension/src/__tests__/process-scanner.test.ts +202 -0
- package/packages/extension/src/__tests__/prompt-expander.test.ts +54 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +167 -0
- package/packages/extension/src/__tests__/server-launcher.test.ts +44 -0
- package/packages/extension/src/__tests__/server-probe.test.ts +25 -0
- package/packages/extension/src/__tests__/session-switch.test.ts +139 -0
- package/packages/extension/src/__tests__/session-sync.test.ts +55 -0
- package/packages/extension/src/__tests__/source-detector.test.ts +73 -0
- package/packages/extension/src/__tests__/stats-extractor.test.ts +92 -0
- package/packages/extension/src/__tests__/ui-proxy.test.ts +583 -0
- package/packages/extension/src/__tests__/watchdog.test.ts +161 -0
- package/packages/extension/src/ask-user-tool.ts +63 -0
- package/packages/extension/src/bridge-context.ts +64 -0
- package/packages/extension/src/bridge.ts +926 -0
- package/packages/extension/src/command-handler.ts +538 -0
- package/packages/extension/src/connection.ts +204 -0
- package/packages/extension/src/dev-build.ts +39 -0
- package/packages/extension/src/event-forwarder.ts +40 -0
- package/packages/extension/src/flow-event-wiring.ts +102 -0
- package/packages/extension/src/git-info.ts +65 -0
- package/packages/extension/src/git-link-builder.ts +112 -0
- package/packages/extension/src/model-tracker.ts +56 -0
- package/packages/extension/src/pi-env.d.ts +23 -0
- package/packages/extension/src/process-metrics.ts +70 -0
- package/packages/extension/src/process-scanner.ts +396 -0
- package/packages/extension/src/prompt-expander.ts +87 -0
- package/packages/extension/src/provider-register.ts +276 -0
- package/packages/extension/src/server-auto-start.ts +87 -0
- package/packages/extension/src/server-launcher.ts +82 -0
- package/packages/extension/src/server-probe.ts +33 -0
- package/packages/extension/src/session-sync.ts +154 -0
- package/packages/extension/src/source-detector.ts +26 -0
- package/packages/extension/src/ui-proxy.ts +269 -0
- package/packages/extension/tsconfig.json +11 -0
- package/packages/server/package.json +37 -0
- package/packages/server/src/__tests__/auth-plugin.test.ts +117 -0
- package/packages/server/src/__tests__/auth.test.ts +224 -0
- package/packages/server/src/__tests__/auto-attach.test.ts +246 -0
- package/packages/server/src/__tests__/auto-resume.test.ts +135 -0
- package/packages/server/src/__tests__/auto-shutdown.test.ts +136 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +104 -0
- package/packages/server/src/__tests__/bulk-archive-handler.test.ts +15 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +73 -0
- package/packages/server/src/__tests__/client-discovery.test.ts +39 -0
- package/packages/server/src/__tests__/config-api.test.ts +104 -0
- package/packages/server/src/__tests__/cors.test.ts +48 -0
- package/packages/server/src/__tests__/directory-service.test.ts +240 -0
- package/packages/server/src/__tests__/editor-detection.test.ts +60 -0
- package/packages/server/src/__tests__/editor-endpoints.test.ts +26 -0
- package/packages/server/src/__tests__/editor-manager.test.ts +73 -0
- package/packages/server/src/__tests__/editor-registry.test.ts +151 -0
- package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +55 -0
- package/packages/server/src/__tests__/event-status-extraction.test.ts +58 -0
- package/packages/server/src/__tests__/extension-register.test.ts +61 -0
- package/packages/server/src/__tests__/file-endpoint.test.ts +49 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +109 -0
- package/packages/server/src/__tests__/git-operations.test.ts +251 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +233 -0
- package/packages/server/src/__tests__/headless-shutdown-fallback.test.ts +109 -0
- package/packages/server/src/__tests__/health-endpoint.test.ts +35 -0
- package/packages/server/src/__tests__/heartbeat-ack.test.ts +63 -0
- package/packages/server/src/__tests__/json-store.test.ts +70 -0
- package/packages/server/src/__tests__/localhost-guard.test.ts +149 -0
- package/packages/server/src/__tests__/memory-event-store.test.ts +260 -0
- package/packages/server/src/__tests__/memory-session-manager.test.ts +80 -0
- package/packages/server/src/__tests__/meta-persistence.test.ts +107 -0
- package/packages/server/src/__tests__/migrate-persistence.test.ts +180 -0
- package/packages/server/src/__tests__/npm-search-proxy.test.ts +153 -0
- package/packages/server/src/__tests__/oauth-callback-server.test.ts +165 -0
- package/packages/server/src/__tests__/openspec-archive.test.ts +87 -0
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +163 -0
- package/packages/server/src/__tests__/package-routes.test.ts +172 -0
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +69 -0
- package/packages/server/src/__tests__/pending-load-manager.test.ts +144 -0
- package/packages/server/src/__tests__/pending-resume-registry.test.ts +130 -0
- package/packages/server/src/__tests__/pi-resource-scanner.test.ts +235 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +108 -0
- package/packages/server/src/__tests__/process-manager.test.ts +184 -0
- package/packages/server/src/__tests__/provider-auth-handlers.test.ts +93 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +143 -0
- package/packages/server/src/__tests__/provider-auth-storage.test.ts +114 -0
- package/packages/server/src/__tests__/resolve-path.test.ts +38 -0
- package/packages/server/src/__tests__/ring-buffer.test.ts +45 -0
- package/packages/server/src/__tests__/server-pid.test.ts +89 -0
- package/packages/server/src/__tests__/session-api.test.ts +244 -0
- package/packages/server/src/__tests__/session-diff.test.ts +138 -0
- package/packages/server/src/__tests__/session-file-dedup.test.ts +102 -0
- package/packages/server/src/__tests__/session-file-reader.test.ts +85 -0
- package/packages/server/src/__tests__/session-lifecycle-logging.test.ts +138 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +135 -0
- package/packages/server/src/__tests__/session-ordering-integration.test.ts +102 -0
- package/packages/server/src/__tests__/session-scanner.test.ts +199 -0
- package/packages/server/src/__tests__/shutdown-endpoint.test.ts +42 -0
- package/packages/server/src/__tests__/skip-wipe.test.ts +123 -0
- package/packages/server/src/__tests__/sleep-aware-heartbeat.test.ts +126 -0
- package/packages/server/src/__tests__/smoke-integration.test.ts +175 -0
- package/packages/server/src/__tests__/spa-fallback.test.ts +68 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +155 -0
- package/packages/server/src/__tests__/terminal-gateway.test.ts +61 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +257 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +84 -0
- package/packages/server/src/__tests__/tunnel.test.ts +206 -0
- package/packages/server/src/__tests__/ws-ping-pong.test.ts +112 -0
- package/packages/server/src/auth-plugin.ts +302 -0
- package/packages/server/src/auth.ts +323 -0
- package/packages/server/src/browse.ts +55 -0
- package/packages/server/src/browser-gateway.ts +495 -0
- package/packages/server/src/browser-handlers/directory-handler.ts +137 -0
- package/packages/server/src/browser-handlers/handler-context.ts +45 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +271 -0
- package/packages/server/src/browser-handlers/session-meta-handler.ts +95 -0
- package/packages/server/src/browser-handlers/subscription-handler.ts +154 -0
- package/packages/server/src/browser-handlers/terminal-handler.ts +37 -0
- package/packages/server/src/cli.ts +347 -0
- package/packages/server/src/config-api.ts +130 -0
- package/packages/server/src/directory-service.ts +162 -0
- package/packages/server/src/editor-detection.ts +60 -0
- package/packages/server/src/editor-manager.ts +352 -0
- package/packages/server/src/editor-proxy.ts +134 -0
- package/packages/server/src/editor-registry.ts +108 -0
- package/packages/server/src/event-status-extraction.ts +131 -0
- package/packages/server/src/event-wiring.ts +589 -0
- package/packages/server/src/extension-register.ts +92 -0
- package/packages/server/src/git-operations.ts +200 -0
- package/packages/server/src/headless-pid-registry.ts +207 -0
- package/packages/server/src/idle-timer.ts +61 -0
- package/packages/server/src/json-store.ts +32 -0
- package/packages/server/src/localhost-guard.ts +117 -0
- package/packages/server/src/memory-event-store.ts +193 -0
- package/packages/server/src/memory-session-manager.ts +123 -0
- package/packages/server/src/meta-persistence.ts +64 -0
- package/packages/server/src/migrate-persistence.ts +195 -0
- package/packages/server/src/npm-search-proxy.ts +143 -0
- package/packages/server/src/oauth-callback-server.ts +177 -0
- package/packages/server/src/openspec-archive.ts +60 -0
- package/packages/server/src/package-manager-wrapper.ts +200 -0
- package/packages/server/src/pending-fork-registry.ts +53 -0
- package/packages/server/src/pending-load-manager.ts +110 -0
- package/packages/server/src/pending-resume-registry.ts +69 -0
- package/packages/server/src/pi-gateway.ts +419 -0
- package/packages/server/src/pi-resource-scanner.ts +369 -0
- package/packages/server/src/preferences-store.ts +116 -0
- package/packages/server/src/process-manager.ts +311 -0
- package/packages/server/src/provider-auth-handlers.ts +438 -0
- package/packages/server/src/provider-auth-storage.ts +200 -0
- package/packages/server/src/resolve-path.ts +12 -0
- package/packages/server/src/routes/editor-routes.ts +86 -0
- package/packages/server/src/routes/file-routes.ts +116 -0
- package/packages/server/src/routes/git-routes.ts +89 -0
- package/packages/server/src/routes/openspec-routes.ts +99 -0
- package/packages/server/src/routes/package-routes.ts +172 -0
- package/packages/server/src/routes/provider-auth-routes.ts +244 -0
- package/packages/server/src/routes/provider-routes.ts +101 -0
- package/packages/server/src/routes/route-deps.ts +23 -0
- package/packages/server/src/routes/session-routes.ts +91 -0
- package/packages/server/src/routes/system-routes.ts +271 -0
- package/packages/server/src/server-pid.ts +84 -0
- package/packages/server/src/server.ts +554 -0
- package/packages/server/src/session-api.ts +330 -0
- package/packages/server/src/session-bootstrap.ts +80 -0
- package/packages/server/src/session-diff.ts +178 -0
- package/packages/server/src/session-discovery.ts +134 -0
- package/packages/server/src/session-file-reader.ts +135 -0
- package/packages/server/src/session-order-manager.ts +73 -0
- package/packages/server/src/session-scanner.ts +233 -0
- package/packages/server/src/session-stats-reader.ts +99 -0
- package/packages/server/src/terminal-gateway.ts +51 -0
- package/packages/server/src/terminal-manager.ts +241 -0
- package/packages/server/src/tunnel.ts +329 -0
- package/packages/server/tsconfig.json +11 -0
- package/packages/shared/package.json +15 -0
- package/packages/shared/src/__tests__/config.test.ts +358 -0
- package/packages/shared/src/__tests__/deriveChangeState.test.ts +95 -0
- package/packages/shared/src/__tests__/mdns-discovery.test.ts +80 -0
- package/packages/shared/src/__tests__/protocol.test.ts +243 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +17 -0
- package/packages/shared/src/__tests__/server-identity.test.ts +73 -0
- package/packages/shared/src/__tests__/session-meta.test.ts +125 -0
- package/packages/shared/src/archive-types.ts +11 -0
- package/packages/shared/src/browser-protocol.ts +534 -0
- package/packages/shared/src/config.ts +245 -0
- package/packages/shared/src/diff-types.ts +41 -0
- package/packages/shared/src/editor-types.ts +18 -0
- package/packages/shared/src/mdns-discovery.ts +248 -0
- package/packages/shared/src/openspec-activity-detector.ts +109 -0
- package/packages/shared/src/openspec-poller.ts +96 -0
- package/packages/shared/src/protocol.ts +369 -0
- package/packages/shared/src/resolve-jiti.ts +43 -0
- package/packages/shared/src/rest-api.ts +255 -0
- package/packages/shared/src/server-identity.ts +51 -0
- package/packages/shared/src/session-meta.ts +86 -0
- package/packages/shared/src/state-replay.ts +174 -0
- package/packages/shared/src/stats-extractor.ts +54 -0
- package/packages/shared/src/terminal-types.ts +18 -0
- package/packages/shared/src/types.ts +351 -0
- package/packages/shared/tsconfig.json +8 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket connection manager with exponential backoff reconnection
|
|
3
|
+
* and message buffering during disconnection.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ConnectionManagerOptions {
|
|
7
|
+
url: string;
|
|
8
|
+
WebSocketImpl?: any;
|
|
9
|
+
maxBufferSize?: number;
|
|
10
|
+
/** Server liveness watchdog: force reconnect after this many ms without any received message. Default 60000. Set 0 to disable. */
|
|
11
|
+
watchdogTimeout?: number;
|
|
12
|
+
onMessage?: (data: unknown) => void;
|
|
13
|
+
onReconnect?: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ConnectionManager {
|
|
17
|
+
private url: string;
|
|
18
|
+
private WS: any;
|
|
19
|
+
private ws: any | null = null;
|
|
20
|
+
private buffer: string[] = [];
|
|
21
|
+
private maxBufferSize: number;
|
|
22
|
+
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
23
|
+
private backoff = 0;
|
|
24
|
+
private intentionalClose = false;
|
|
25
|
+
private hasConnectedBefore = false;
|
|
26
|
+
private onMessage?: (data: unknown) => void;
|
|
27
|
+
private onReconnect?: () => void;
|
|
28
|
+
|
|
29
|
+
private static readonly INITIAL_BACKOFF = 1000;
|
|
30
|
+
private static readonly MAX_BACKOFF = 30000;
|
|
31
|
+
private static readonly WATCHDOG_CHECK_INTERVAL = 15_000;
|
|
32
|
+
private static readonly DEFAULT_WATCHDOG_TIMEOUT = 60_000;
|
|
33
|
+
|
|
34
|
+
private lastMessageAt = 0;
|
|
35
|
+
private watchdogTimer: ReturnType<typeof setInterval> | null = null;
|
|
36
|
+
private watchdogTimeout: number;
|
|
37
|
+
|
|
38
|
+
constructor(options: ConnectionManagerOptions) {
|
|
39
|
+
this.url = options.url;
|
|
40
|
+
this.WS = options.WebSocketImpl ?? (globalThis as any).WebSocket;
|
|
41
|
+
this.maxBufferSize = options.maxBufferSize ?? 10000;
|
|
42
|
+
this.watchdogTimeout = options.watchdogTimeout ?? ConnectionManager.DEFAULT_WATCHDOG_TIMEOUT;
|
|
43
|
+
this.onMessage = options.onMessage;
|
|
44
|
+
this.onReconnect = options.onReconnect;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
connect(): void {
|
|
48
|
+
this.intentionalClose = false;
|
|
49
|
+
this.createConnection();
|
|
50
|
+
this.startWatchdog();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
disconnect(): void {
|
|
54
|
+
this.intentionalClose = true;
|
|
55
|
+
this.stopWatchdog();
|
|
56
|
+
if (this.reconnectTimer) {
|
|
57
|
+
clearTimeout(this.reconnectTimer);
|
|
58
|
+
this.reconnectTimer = null;
|
|
59
|
+
}
|
|
60
|
+
if (this.ws) {
|
|
61
|
+
this.ws.onclose = null;
|
|
62
|
+
this.ws.close();
|
|
63
|
+
this.ws = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
send(message: unknown): void {
|
|
68
|
+
const data = JSON.stringify(message);
|
|
69
|
+
|
|
70
|
+
if (this.ws?.readyState === 1) {
|
|
71
|
+
try {
|
|
72
|
+
this.ws.send(data);
|
|
73
|
+
} catch {
|
|
74
|
+
// Connection died between readyState check and send — buffer instead
|
|
75
|
+
this.bufferMessage(data);
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
this.bufferMessage(data);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private bufferMessage(data: string): void {
|
|
83
|
+
this.buffer.push(data);
|
|
84
|
+
if (this.buffer.length > this.maxBufferSize) {
|
|
85
|
+
this.buffer.shift();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get isConnected(): boolean {
|
|
90
|
+
return this.ws?.readyState === 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Update the WebSocket URL and reconnect.
|
|
95
|
+
* Used when mDNS discovers the server on a different address/port.
|
|
96
|
+
*/
|
|
97
|
+
updateUrl(newUrl: string): void {
|
|
98
|
+
if (newUrl === this.url) return;
|
|
99
|
+
this.url = newUrl;
|
|
100
|
+
// Force reconnect to new URL
|
|
101
|
+
if (this.ws) {
|
|
102
|
+
this.handleDisconnect();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private createConnection(): void {
|
|
107
|
+
try {
|
|
108
|
+
this.ws = new this.WS(this.url);
|
|
109
|
+
} catch {
|
|
110
|
+
// Constructor failed — schedule reconnect
|
|
111
|
+
this.ws = null;
|
|
112
|
+
if (!this.intentionalClose) {
|
|
113
|
+
this.scheduleReconnect();
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.ws.onopen = () => {
|
|
119
|
+
// Reset backoff on successful connection
|
|
120
|
+
this.backoff = 0;
|
|
121
|
+
this.lastMessageAt = Date.now();
|
|
122
|
+
|
|
123
|
+
// Notify reconnect if this isn't the first connection
|
|
124
|
+
if (this.hasConnectedBefore) {
|
|
125
|
+
this.onReconnect?.();
|
|
126
|
+
}
|
|
127
|
+
this.hasConnectedBefore = true;
|
|
128
|
+
|
|
129
|
+
// Flush buffer
|
|
130
|
+
const buffered = [...this.buffer];
|
|
131
|
+
this.buffer = [];
|
|
132
|
+
for (const data of buffered) {
|
|
133
|
+
this.ws?.send(data);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
this.ws.onmessage = (ev: { data: string }) => {
|
|
138
|
+
this.lastMessageAt = Date.now();
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(ev.data);
|
|
141
|
+
this.onMessage?.(parsed);
|
|
142
|
+
} catch {
|
|
143
|
+
// Ignore malformed messages
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
this.ws.onclose = () => {
|
|
148
|
+
this.handleDisconnect();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
this.ws.onerror = () => {
|
|
152
|
+
// Node 22's built-in WebSocket may fire onerror WITHOUT onclose
|
|
153
|
+
// on connection failure. Handle once and prevent re-entrant calls
|
|
154
|
+
// (ws.close() can re-trigger onerror synchronously).
|
|
155
|
+
this.handleDisconnect();
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private handleDisconnect(): void {
|
|
160
|
+
if (!this.ws) return; // Already handled — idempotent guard
|
|
161
|
+
const ws = this.ws;
|
|
162
|
+
this.ws = null;
|
|
163
|
+
// Detach handlers to prevent re-entrant calls from ws.close()
|
|
164
|
+
ws.onclose = null;
|
|
165
|
+
ws.onerror = null;
|
|
166
|
+
ws.onopen = null;
|
|
167
|
+
ws.onmessage = null;
|
|
168
|
+
try { ws.close(); } catch { /* ignore — may already be closed */ }
|
|
169
|
+
if (!this.intentionalClose) {
|
|
170
|
+
this.scheduleReconnect();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private startWatchdog(): void {
|
|
175
|
+
this.stopWatchdog();
|
|
176
|
+
if (this.watchdogTimeout <= 0) return;
|
|
177
|
+
this.watchdogTimer = setInterval(() => {
|
|
178
|
+
if (this.ws && this.lastMessageAt > 0 && Date.now() - this.lastMessageAt >= this.watchdogTimeout) {
|
|
179
|
+
// Server has gone silent — force close to trigger reconnect
|
|
180
|
+
this.handleDisconnect();
|
|
181
|
+
}
|
|
182
|
+
}, ConnectionManager.WATCHDOG_CHECK_INTERVAL);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private stopWatchdog(): void {
|
|
186
|
+
if (this.watchdogTimer) {
|
|
187
|
+
clearInterval(this.watchdogTimer);
|
|
188
|
+
this.watchdogTimer = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private scheduleReconnect(): void {
|
|
193
|
+
if (this.backoff === 0) {
|
|
194
|
+
this.backoff = ConnectionManager.INITIAL_BACKOFF;
|
|
195
|
+
} else {
|
|
196
|
+
this.backoff = Math.min(this.backoff * 2, ConnectionManager.MAX_BACKOFF);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.reconnectTimer = setTimeout(() => {
|
|
200
|
+
this.reconnectTimer = null;
|
|
201
|
+
this.createConnection();
|
|
202
|
+
}, this.backoff);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev build-on-reload helper.
|
|
3
|
+
* Builds the Vite client and requests server shutdown.
|
|
4
|
+
*/
|
|
5
|
+
import { execSync as defaultExecSync } from "node:child_process";
|
|
6
|
+
|
|
7
|
+
export interface DevBuildOptions {
|
|
8
|
+
packageRoot: string;
|
|
9
|
+
serverPort: number;
|
|
10
|
+
/** @internal for testing */
|
|
11
|
+
_execSync?: typeof defaultExecSync;
|
|
12
|
+
/** @internal for testing */
|
|
13
|
+
_fetch?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run the dev build and shutdown sequence.
|
|
18
|
+
* Errors are caught and logged — never throws.
|
|
19
|
+
*/
|
|
20
|
+
export function runDevBuild(opts: DevBuildOptions): void {
|
|
21
|
+
const execSyncFn = opts._execSync ?? defaultExecSync;
|
|
22
|
+
const fetchFn = opts._fetch ?? fetch;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
console.log("🔨 Dashboard: building client...");
|
|
26
|
+
execSyncFn("npm run build", { cwd: opts.packageRoot, stdio: "inherit" });
|
|
27
|
+
console.log("✅ Dashboard: client built");
|
|
28
|
+
} catch (err: any) {
|
|
29
|
+
console.log(`❌ Dashboard: build failed — ${err.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
console.log("🛑 Dashboard: stopping server...");
|
|
34
|
+
fetchFn(`http://localhost:${opts.serverPort}/api/shutdown`, { method: "POST" }).catch(() => {});
|
|
35
|
+
console.log("✅ Dashboard: server stopped");
|
|
36
|
+
} catch {
|
|
37
|
+
// Server may not be running — that's fine
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { EventForwardMessage } from "@blackbelt-technology/pi-dashboard-shared/protocol.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract only JSON-serializable fields from an event object.
|
|
5
|
+
* Strips functions, AbortSignals, and other non-serializable values.
|
|
6
|
+
*/
|
|
7
|
+
function extractSerializable(obj: Record<string, unknown>): Record<string, unknown> {
|
|
8
|
+
const result: Record<string, unknown> = {};
|
|
9
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
10
|
+
if (value === undefined || value === null) {
|
|
11
|
+
result[key] = value;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === "function") continue;
|
|
15
|
+
if (value instanceof AbortSignal) continue;
|
|
16
|
+
if (typeof value === "object" && "aborted" in (value as object)) continue;
|
|
17
|
+
result[key] = value;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Map a pi event object to an event_forward protocol message.
|
|
24
|
+
*/
|
|
25
|
+
export function mapEventToProtocol(
|
|
26
|
+
sessionId: string,
|
|
27
|
+
piEvent: Record<string, unknown>,
|
|
28
|
+
): EventForwardMessage {
|
|
29
|
+
const serializable = extractSerializable(piEvent);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
type: "event_forward",
|
|
33
|
+
sessionId,
|
|
34
|
+
event: {
|
|
35
|
+
eventType: (piEvent.type as string) ?? "unknown",
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
data: serializable,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow event wiring: registers listeners for pi-flows events
|
|
3
|
+
* and forwards them as protocol messages to the dashboard server.
|
|
4
|
+
*/
|
|
5
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import type { BridgeContext } from "./bridge-context.js";
|
|
7
|
+
import { filterHiddenCommands } from "./bridge-context.js";
|
|
8
|
+
import type { FlowInfo } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
9
|
+
|
|
10
|
+
/** Map of pi-flows event names to dashboard protocol event types */
|
|
11
|
+
export const FLOW_EVENT_MAP: Record<string, string> = {
|
|
12
|
+
"flow:flow-started": "flow_started",
|
|
13
|
+
"flow:agent-started": "flow_agent_started",
|
|
14
|
+
"flow:agent-complete": "flow_agent_complete",
|
|
15
|
+
"flow:subagent-tool-call": "flow_tool_call",
|
|
16
|
+
"flow:subagent-tool-result": "flow_tool_result",
|
|
17
|
+
"flow:assistant-text": "flow_assistant_text",
|
|
18
|
+
"flow:thinking-text": "flow_thinking_text",
|
|
19
|
+
"flow:loop-iteration": "flow_loop_iteration",
|
|
20
|
+
"flow:auto-decision": "flow_auto_decision",
|
|
21
|
+
"flow:complete": "flow_complete",
|
|
22
|
+
"flow:summary-started": "flow_summary_started",
|
|
23
|
+
"flow:summary-ready": "flow_summary_ready",
|
|
24
|
+
"flow:summary-dismissed": "flow_summary_dismissed",
|
|
25
|
+
// Architect lifecycle events
|
|
26
|
+
"flow:architect-started": "architect_started",
|
|
27
|
+
"flow:architect-tool-call": "architect_tool_call",
|
|
28
|
+
"flow:architect-tool-result": "architect_tool_result",
|
|
29
|
+
"flow:architect-text": "architect_text",
|
|
30
|
+
"flow:architect-preview": "architect_preview",
|
|
31
|
+
"flow:architect-complete": "architect_complete",
|
|
32
|
+
"flow:architect-replan": "architect_replan",
|
|
33
|
+
"flow:architect-cancelled": "architect_cancelled",
|
|
34
|
+
"flow:architect-saved": "architect_saved",
|
|
35
|
+
"flow:architect-error": "architect_error",
|
|
36
|
+
"flow:architect-context-generating": "architect_context_generating",
|
|
37
|
+
"flow:architect-context-ready": "architect_context_ready",
|
|
38
|
+
"flow:architect-run-handoff": "architect_run_handoff",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** Map of pi-subagents event names to dashboard protocol event types */
|
|
42
|
+
export const SUBAGENT_EVENT_MAP: Record<string, string> = {
|
|
43
|
+
"subagents:created": "subagent_created",
|
|
44
|
+
"subagents:started": "subagent_started",
|
|
45
|
+
"subagents:completed": "subagent_completed",
|
|
46
|
+
"subagents:failed": "subagent_failed",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register flow event listeners on pi.events.
|
|
51
|
+
* Must be called after session_start when pi.events is available.
|
|
52
|
+
*
|
|
53
|
+
* @param bc - Bridge context (mutable state)
|
|
54
|
+
* @param isSessionReady - Function that returns whether session is ready
|
|
55
|
+
* @param getFlowsList - Function to get current flows list
|
|
56
|
+
*/
|
|
57
|
+
export function registerFlowEventListeners(
|
|
58
|
+
bc: BridgeContext,
|
|
59
|
+
isSessionReady: () => boolean,
|
|
60
|
+
getFlowsList: () => FlowInfo[],
|
|
61
|
+
): void {
|
|
62
|
+
const { pi, connection } = bc;
|
|
63
|
+
if (!pi.events) return;
|
|
64
|
+
|
|
65
|
+
// Re-send commands and flows list when pi-flows discovers new flows or completes
|
|
66
|
+
const resendCommandsAndFlows = () => {
|
|
67
|
+
if (!isSessionReady()) return;
|
|
68
|
+
const commands = filterHiddenCommands(pi.getCommands());
|
|
69
|
+
connection.send({ type: "commands_list", sessionId: bc.sessionId, commands });
|
|
70
|
+
const flows = getFlowsList();
|
|
71
|
+
connection.send({ type: "flows_list", sessionId: bc.sessionId, flows });
|
|
72
|
+
};
|
|
73
|
+
pi.events.on("flow:rediscover", resendCommandsAndFlows);
|
|
74
|
+
pi.events.on("flow:complete", resendCommandsAndFlows);
|
|
75
|
+
|
|
76
|
+
// Note: event_forward sending for flow and subagent events is handled by
|
|
77
|
+
// the EventBus emit intercept in bridge.ts (catch-all forwarding).
|
|
78
|
+
|
|
79
|
+
// Forward architect prompt requests directly to the dashboard widget bar.
|
|
80
|
+
// Non-architect prompts still go through ui-proxy (flow-tui -> ctx.ui.select -> extension_ui_request).
|
|
81
|
+
// Both paths race -- first response wins via emitPromptAndAwait's resolved guard.
|
|
82
|
+
pi.events.on("flow:prompt-request", (data: unknown) => {
|
|
83
|
+
if (!isSessionReady()) return;
|
|
84
|
+
const req = data as { id?: string; pipeline?: string; type?: string; question?: string; options?: string[]; defaultValue?: string };
|
|
85
|
+
if (!req.id || !req.pipeline?.startsWith("architect-")) return;
|
|
86
|
+
connection.send({
|
|
87
|
+
type: "event_forward",
|
|
88
|
+
sessionId: bc.sessionId,
|
|
89
|
+
event: {
|
|
90
|
+
eventType: "architect_prompt_request",
|
|
91
|
+
timestamp: Date.now(),
|
|
92
|
+
data: {
|
|
93
|
+
id: req.id,
|
|
94
|
+
promptType: req.type,
|
|
95
|
+
question: req.question,
|
|
96
|
+
options: req.options,
|
|
97
|
+
defaultValue: req.defaultValue,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git info gathering — detects branch, remote URL, and PR number.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { buildGitLinks, type GitLinks } from "./git-link-builder.js";
|
|
6
|
+
|
|
7
|
+
export interface GitInfo {
|
|
8
|
+
gitBranch: string;
|
|
9
|
+
gitBranchUrl?: string;
|
|
10
|
+
gitPrNumber?: number;
|
|
11
|
+
gitPrUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Run a shell command and return trimmed stdout, or undefined on failure. */
|
|
15
|
+
function runGit(command: string, cwd: string): string | undefined {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(command, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 }).trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Detect the current git branch. Returns short SHA for detached HEAD. */
|
|
24
|
+
export function detectBranch(cwd: string): string | undefined {
|
|
25
|
+
const ref = runGit("git rev-parse --abbrev-ref HEAD", cwd);
|
|
26
|
+
if (!ref) return undefined;
|
|
27
|
+
if (ref === "HEAD") {
|
|
28
|
+
// Detached HEAD — return short commit SHA
|
|
29
|
+
return runGit("git rev-parse --short HEAD", cwd) ?? "HEAD";
|
|
30
|
+
}
|
|
31
|
+
return ref;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Detect the remote origin URL. */
|
|
35
|
+
export function detectRemoteUrl(cwd: string): string | undefined {
|
|
36
|
+
return runGit("git remote get-url origin", cwd);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Detect the PR number via gh CLI (best effort). */
|
|
40
|
+
export function detectPrNumber(cwd: string): number | undefined {
|
|
41
|
+
const result = runGit("gh pr view --json number -q .number", cwd);
|
|
42
|
+
if (result) {
|
|
43
|
+
const num = parseInt(result, 10);
|
|
44
|
+
if (!isNaN(num)) return num;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Gather all git info for a directory. Returns undefined if not a git repo. */
|
|
50
|
+
export function gatherGitInfo(cwd: string): GitInfo | undefined {
|
|
51
|
+
const branch = detectBranch(cwd);
|
|
52
|
+
if (!branch) return undefined;
|
|
53
|
+
|
|
54
|
+
const remoteUrl = detectRemoteUrl(cwd);
|
|
55
|
+
const prNumber = detectPrNumber(cwd);
|
|
56
|
+
|
|
57
|
+
const links: GitLinks = remoteUrl ? buildGitLinks(remoteUrl, branch, prNumber) : {};
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
gitBranch: branch,
|
|
61
|
+
gitBranchUrl: links.branchUrl,
|
|
62
|
+
gitPrNumber: prNumber,
|
|
63
|
+
gitPrUrl: links.prUrl,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git link builder — parses remote URLs and builds platform-specific links.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface GitLinks {
|
|
6
|
+
branchUrl?: string;
|
|
7
|
+
prUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ParsedRemote {
|
|
11
|
+
host: string;
|
|
12
|
+
user: string;
|
|
13
|
+
repo: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type Platform = "github" | "gitlab" | "bitbucket" | "gitea" | "codeberg" | "sourcehut";
|
|
17
|
+
|
|
18
|
+
const HOST_TO_PLATFORM: Record<string, Platform> = {
|
|
19
|
+
"github.com": "github",
|
|
20
|
+
"gitlab.com": "gitlab",
|
|
21
|
+
"bitbucket.org": "bitbucket",
|
|
22
|
+
"gitea.com": "gitea",
|
|
23
|
+
"codeberg.org": "codeberg",
|
|
24
|
+
"sr.ht": "sourcehut",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Parse an SSH or HTTPS remote URL into host/user/repo. */
|
|
28
|
+
export function parseRemoteUrl(url: string): ParsedRemote | undefined {
|
|
29
|
+
// SSH: git@host:user/repo.git
|
|
30
|
+
const sshMatch = url.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
|
|
31
|
+
if (sshMatch) {
|
|
32
|
+
const [, host, path] = sshMatch;
|
|
33
|
+
const parts = path!.split("/");
|
|
34
|
+
if (parts.length >= 2) {
|
|
35
|
+
return { host: host!, user: parts.slice(0, -1).join("/"), repo: parts[parts.length - 1]! };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// HTTPS: https://host/user/repo.git
|
|
40
|
+
const httpsMatch = url.match(/^https?:\/\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
41
|
+
if (httpsMatch) {
|
|
42
|
+
const [, host, path] = httpsMatch;
|
|
43
|
+
const parts = path!.split("/");
|
|
44
|
+
if (parts.length >= 2) {
|
|
45
|
+
return { host: host!, user: parts.slice(0, -1).join("/"), repo: parts[parts.length - 1]! };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Detect the hosting platform from a hostname. */
|
|
53
|
+
export function detectPlatform(host: string): Platform | undefined {
|
|
54
|
+
return HOST_TO_PLATFORM[host];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Build branch and PR URLs for a given platform. */
|
|
58
|
+
export function buildGitLinks(remoteUrl: string, branch: string, prNumber?: number): GitLinks {
|
|
59
|
+
const parsed = parseRemoteUrl(remoteUrl);
|
|
60
|
+
if (!parsed) return {};
|
|
61
|
+
|
|
62
|
+
const platform = detectPlatform(parsed.host);
|
|
63
|
+
if (!platform) return {};
|
|
64
|
+
|
|
65
|
+
const baseUrl = `https://${parsed.host}/${parsed.user}/${parsed.repo}`;
|
|
66
|
+
const encodedBranch = encodeURIComponent(branch);
|
|
67
|
+
|
|
68
|
+
const links: GitLinks = {};
|
|
69
|
+
|
|
70
|
+
// Don't generate branch URL for detached HEAD
|
|
71
|
+
if (branch !== "HEAD") {
|
|
72
|
+
switch (platform) {
|
|
73
|
+
case "github":
|
|
74
|
+
case "sourcehut":
|
|
75
|
+
links.branchUrl = `${baseUrl}/tree/${encodedBranch}`;
|
|
76
|
+
break;
|
|
77
|
+
case "gitlab":
|
|
78
|
+
links.branchUrl = `${baseUrl}/-/tree/${encodedBranch}`;
|
|
79
|
+
break;
|
|
80
|
+
case "bitbucket":
|
|
81
|
+
links.branchUrl = `${baseUrl}/src/${encodedBranch}`;
|
|
82
|
+
break;
|
|
83
|
+
case "gitea":
|
|
84
|
+
case "codeberg":
|
|
85
|
+
links.branchUrl = `${baseUrl}/src/branch/${encodedBranch}`;
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (prNumber !== undefined) {
|
|
91
|
+
switch (platform) {
|
|
92
|
+
case "github":
|
|
93
|
+
links.prUrl = `${baseUrl}/pull/${prNumber}`;
|
|
94
|
+
break;
|
|
95
|
+
case "gitlab":
|
|
96
|
+
links.prUrl = `${baseUrl}/-/merge_requests/${prNumber}`;
|
|
97
|
+
break;
|
|
98
|
+
case "bitbucket":
|
|
99
|
+
links.prUrl = `${baseUrl}/pull-requests/${prNumber}`;
|
|
100
|
+
break;
|
|
101
|
+
case "gitea":
|
|
102
|
+
case "codeberg":
|
|
103
|
+
links.prUrl = `${baseUrl}/pulls/${prNumber}`;
|
|
104
|
+
break;
|
|
105
|
+
case "sourcehut":
|
|
106
|
+
links.prUrl = `${baseUrl}/patches/${prNumber}`;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return links;
|
|
112
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model and thinking-level change detection.
|
|
3
|
+
* Sends model_update only when values actually change.
|
|
4
|
+
*/
|
|
5
|
+
import type { BridgeContext } from "./bridge-context.js";
|
|
6
|
+
import { getCurrentModelString } from "./bridge-context.js";
|
|
7
|
+
import { gatherGitInfo } from "./git-info.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Send model_update if model or thinking level has changed since last send.
|
|
11
|
+
*/
|
|
12
|
+
export function sendModelUpdateIfChanged(bc: BridgeContext): void {
|
|
13
|
+
const model = getCurrentModelString(bc);
|
|
14
|
+
const thinkingLevel = (bc.pi as any).getThinkingLevel?.() ?? undefined;
|
|
15
|
+
if (model === bc.lastModel && thinkingLevel === bc.lastThinkingLevel) return;
|
|
16
|
+
bc.lastModel = model;
|
|
17
|
+
bc.lastThinkingLevel = thinkingLevel;
|
|
18
|
+
if (model) {
|
|
19
|
+
bc.connection.send({
|
|
20
|
+
type: "model_update",
|
|
21
|
+
sessionId: bc.sessionId,
|
|
22
|
+
model,
|
|
23
|
+
thinkingLevel,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Send session_name_update if name has changed since last send.
|
|
30
|
+
*/
|
|
31
|
+
export function sendSessionNameIfChanged(bc: BridgeContext): void {
|
|
32
|
+
const name = bc.pi.getSessionName() ?? "";
|
|
33
|
+
if (name === bc.lastSessionName) return;
|
|
34
|
+
bc.lastSessionName = name;
|
|
35
|
+
bc.connection.send({
|
|
36
|
+
type: "session_name_update",
|
|
37
|
+
sessionId: bc.sessionId,
|
|
38
|
+
name,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Send git_info_update if branch or PR has changed since last send.
|
|
44
|
+
*/
|
|
45
|
+
export function sendGitInfoIfChanged(bc: BridgeContext, cwd: string): void {
|
|
46
|
+
const info = gatherGitInfo(cwd);
|
|
47
|
+
if (!info) return;
|
|
48
|
+
if (info.gitBranch === bc.lastGitBranch && info.gitPrNumber === bc.lastGitPrNumber) return;
|
|
49
|
+
bc.lastGitBranch = info.gitBranch;
|
|
50
|
+
bc.lastGitPrNumber = info.gitPrNumber;
|
|
51
|
+
bc.connection.send({
|
|
52
|
+
type: "git_info_update",
|
|
53
|
+
sessionId: bc.sessionId,
|
|
54
|
+
...info,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Ambient declarations for pi runtime packages.
|
|
2
|
+
// The actual types are provided by whichever host (pi or OMP) loads this extension.
|
|
3
|
+
// tsconfig paths handles resolution when one of the packages is installed;
|
|
4
|
+
// these declarations serve as fallback when neither is available (e.g. CI, dev without pi).
|
|
5
|
+
declare module "@mariozechner/pi-coding-agent" {
|
|
6
|
+
export type ExtensionAPI = import("@oh-my-pi/pi-coding-agent").ExtensionAPI;
|
|
7
|
+
}
|
|
8
|
+
declare module "@oh-my-pi/pi-coding-agent" {
|
|
9
|
+
export interface ModelRegistry {
|
|
10
|
+
getAvailable(): Array<{ provider: string; id: string }>;
|
|
11
|
+
refresh(): void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ExtensionAPI {
|
|
15
|
+
on(event: string, handler: (...args: any[]) => any): void;
|
|
16
|
+
getCommands(): any[];
|
|
17
|
+
sendUserMessage(message: string | any[]): void;
|
|
18
|
+
setSessionName(name: string): void;
|
|
19
|
+
getSessionName(): string | undefined;
|
|
20
|
+
registerCommand(name: string, options: { description?: string; handler: (args: string, ctx: any) => Promise<void> }): void;
|
|
21
|
+
exec(command: string, args: string[], options?: { timeout?: number }): Promise<{ stdout: string; stderr: string; exitCode: number }>;
|
|
22
|
+
}
|
|
23
|
+
}
|