sessioncast-cli 2.0.0 → 2.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/dist/agent/session-handler.d.ts +2 -1
- package/dist/agent/session-handler.js +77 -30
- package/dist/agent/tmux-executor.d.ts +33 -3
- package/dist/agent/tmux-executor.js +50 -3
- package/dist/agent/tmux.d.ts +6 -2
- package/dist/agent/tmux.js +9 -2
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/websocket.d.ts +18 -2
- package/dist/agent/websocket.js +26 -9
- package/dist/autopilot/index.d.ts +94 -0
- package/dist/autopilot/index.js +322 -0
- package/dist/autopilot/mission-analyzer.d.ts +27 -0
- package/dist/autopilot/mission-analyzer.js +232 -0
- package/dist/autopilot/project-detector.d.ts +12 -0
- package/dist/autopilot/project-detector.js +326 -0
- package/dist/autopilot/source-scanner.d.ts +26 -0
- package/dist/autopilot/source-scanner.js +285 -0
- package/dist/autopilot/speckit-generator.d.ts +60 -0
- package/dist/autopilot/speckit-generator.js +511 -0
- package/dist/autopilot/types.d.ts +110 -0
- package/dist/autopilot/types.js +6 -0
- package/dist/autopilot/workflow-generator.d.ts +33 -0
- package/dist/autopilot/workflow-generator.js +278 -0
- package/dist/project/executor.d.ts +73 -0
- package/dist/project/executor.js +437 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +20 -0
- package/dist/project/manager.d.ts +66 -0
- package/dist/project/manager.js +290 -0
- package/dist/project/relay-client.d.ts +37 -0
- package/dist/project/relay-client.js +204 -0
- package/dist/project/types.d.ts +48 -0
- package/dist/project/types.js +3 -0
- package/package.json +1 -1
|
@@ -11,7 +11,8 @@ export declare class TmuxSessionHandler {
|
|
|
11
11
|
private wsClient;
|
|
12
12
|
private onCreateSession?;
|
|
13
13
|
private running;
|
|
14
|
-
private
|
|
14
|
+
private lastScreens;
|
|
15
|
+
private lastPaneLayoutJson;
|
|
15
16
|
private lastForceSendTime;
|
|
16
17
|
private lastChangeTime;
|
|
17
18
|
private captureTimer;
|
|
@@ -49,7 +49,8 @@ class TmuxSessionHandler {
|
|
|
49
49
|
constructor(options) {
|
|
50
50
|
this.wsClient = null;
|
|
51
51
|
this.running = false;
|
|
52
|
-
this.
|
|
52
|
+
this.lastScreens = new Map();
|
|
53
|
+
this.lastPaneLayoutJson = '';
|
|
53
54
|
this.lastForceSendTime = 0;
|
|
54
55
|
this.lastChangeTime = 0;
|
|
55
56
|
this.captureTimer = null;
|
|
@@ -83,14 +84,16 @@ class TmuxSessionHandler {
|
|
|
83
84
|
});
|
|
84
85
|
this.wsClient.on('connected', () => {
|
|
85
86
|
console.log(`[${this.tmuxSession}] Connected to relay`);
|
|
87
|
+
// Reset pane layout cache so it gets re-sent on reconnection
|
|
88
|
+
this.lastPaneLayoutJson = '';
|
|
86
89
|
this.startScreenCapture();
|
|
87
90
|
});
|
|
88
91
|
this.wsClient.on('disconnected', ({ code, reason }) => {
|
|
89
92
|
console.log(`[${this.tmuxSession}] Disconnected: code=${code}, reason=${reason}`);
|
|
90
93
|
this.stopScreenCapture();
|
|
91
94
|
});
|
|
92
|
-
this.wsClient.on('keys', (keys) => {
|
|
93
|
-
this.handleKeys(keys);
|
|
95
|
+
this.wsClient.on('keys', (keys, meta) => {
|
|
96
|
+
this.handleKeys(keys, meta);
|
|
94
97
|
});
|
|
95
98
|
this.wsClient.on('resize', ({ cols, rows }) => {
|
|
96
99
|
console.log(`[${this.tmuxSession}] Resize: ${cols}x${rows}`);
|
|
@@ -279,8 +282,9 @@ class TmuxSessionHandler {
|
|
|
279
282
|
};
|
|
280
283
|
return types[ext] || 'application/octet-stream';
|
|
281
284
|
}
|
|
282
|
-
handleKeys(keys) {
|
|
283
|
-
|
|
285
|
+
handleKeys(keys, meta) {
|
|
286
|
+
const target = meta?.pane ? `${this.tmuxSession}.${meta.pane}` : this.tmuxSession;
|
|
287
|
+
tmux.sendKeys(target, keys, false);
|
|
284
288
|
}
|
|
285
289
|
startScreenCapture() {
|
|
286
290
|
if (this.captureTimer)
|
|
@@ -292,43 +296,86 @@ class TmuxSessionHandler {
|
|
|
292
296
|
return;
|
|
293
297
|
}
|
|
294
298
|
try {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
const panes = tmux.listPanes(this.tmuxSession);
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
const isMultiPane = panes.length > 1;
|
|
302
|
+
// Check if pane layout changed
|
|
303
|
+
const paneLayoutJson = JSON.stringify(panes);
|
|
304
|
+
if (paneLayoutJson !== this.lastPaneLayoutJson) {
|
|
305
|
+
this.lastPaneLayoutJson = paneLayoutJson;
|
|
306
|
+
this.wsClient.sendPaneLayout(panes);
|
|
307
|
+
// Clean up screens for removed panes
|
|
308
|
+
const currentPaneIds = new Set(panes.map(p => p.id));
|
|
309
|
+
for (const key of this.lastScreens.keys()) {
|
|
310
|
+
if (!currentPaneIds.has(key)) {
|
|
311
|
+
this.lastScreens.delete(key);
|
|
305
312
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
let anyChanged = false;
|
|
316
|
+
const forceTime = (now - this.lastForceSendTime) >= FORCE_SEND_INTERVAL_MS;
|
|
317
|
+
if (isMultiPane) {
|
|
318
|
+
// Multi-pane: capture each pane individually
|
|
319
|
+
for (const pane of panes) {
|
|
320
|
+
const screen = tmux.capturePane(this.tmuxSession, pane.id);
|
|
321
|
+
if (screen !== null) {
|
|
322
|
+
const lastScreen = this.lastScreens.get(pane.id) || '';
|
|
323
|
+
const changed = screen !== lastScreen;
|
|
324
|
+
if (changed || forceTime) {
|
|
325
|
+
this.lastScreens.set(pane.id, screen);
|
|
326
|
+
if (changed)
|
|
327
|
+
anyChanged = true;
|
|
328
|
+
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
329
|
+
const data = Buffer.from(fullOutput, 'utf-8');
|
|
330
|
+
const paneMeta = { pane: pane.id, index: pane.index };
|
|
331
|
+
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
332
|
+
this.wsClient.sendScreenCompressed(data, paneMeta);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
this.wsClient.sendScreen(data, paneMeta);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
315
338
|
}
|
|
316
339
|
}
|
|
317
|
-
// Adaptive sleep: faster when active, slower when idle
|
|
318
|
-
const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
|
|
319
|
-
const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
|
|
320
|
-
this.captureTimer = setTimeout(capture, sleepMs);
|
|
321
340
|
}
|
|
322
341
|
else {
|
|
323
|
-
|
|
342
|
+
// Single pane: backward compatible (no meta)
|
|
343
|
+
const screen = tmux.capturePane(this.tmuxSession);
|
|
344
|
+
if (screen !== null) {
|
|
345
|
+
const lastScreen = this.lastScreens.get('_single') || '';
|
|
346
|
+
const changed = screen !== lastScreen;
|
|
347
|
+
if (changed || forceTime) {
|
|
348
|
+
this.lastScreens.set('_single', screen);
|
|
349
|
+
if (changed)
|
|
350
|
+
anyChanged = true;
|
|
351
|
+
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
352
|
+
const data = Buffer.from(fullOutput, 'utf-8');
|
|
353
|
+
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
354
|
+
this.wsClient.sendScreenCompressed(data);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
this.wsClient.sendScreen(data);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (anyChanged || forceTime) {
|
|
363
|
+
this.lastForceSendTime = now;
|
|
364
|
+
if (anyChanged) {
|
|
365
|
+
this.lastChangeTime = now;
|
|
366
|
+
}
|
|
324
367
|
}
|
|
368
|
+
const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
|
|
369
|
+
const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
|
|
370
|
+
this.captureTimer = setTimeout(capture, sleepMs);
|
|
325
371
|
}
|
|
326
372
|
catch (error) {
|
|
327
373
|
console.error(`[${this.tmuxSession}] Screen capture error:`, error);
|
|
328
374
|
this.captureTimer = setTimeout(capture, 500);
|
|
329
375
|
}
|
|
330
376
|
};
|
|
331
|
-
|
|
377
|
+
// Small delay to ensure register message is processed by server first
|
|
378
|
+
setTimeout(capture, 300);
|
|
332
379
|
console.log(`[${this.tmuxSession}] Screen capture started`);
|
|
333
380
|
}
|
|
334
381
|
stopScreenCapture() {
|
|
@@ -4,7 +4,17 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export interface TmuxExecutor {
|
|
6
6
|
listSessions(): string[];
|
|
7
|
-
|
|
7
|
+
listPanes(session: string): {
|
|
8
|
+
id: string;
|
|
9
|
+
index: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
top: number;
|
|
13
|
+
left: number;
|
|
14
|
+
active: boolean;
|
|
15
|
+
title: string;
|
|
16
|
+
}[];
|
|
17
|
+
capturePane(session: string, paneId?: string): string | null;
|
|
8
18
|
sendKeys(session: string, keys: string): boolean;
|
|
9
19
|
sendSpecialKey(session: string, key: string): boolean;
|
|
10
20
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -20,7 +30,17 @@ export interface TmuxExecutor {
|
|
|
20
30
|
*/
|
|
21
31
|
export declare class UnixTmuxExecutor implements TmuxExecutor {
|
|
22
32
|
listSessions(): string[];
|
|
23
|
-
|
|
33
|
+
listPanes(session: string): {
|
|
34
|
+
id: string;
|
|
35
|
+
index: number;
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
top: number;
|
|
39
|
+
left: number;
|
|
40
|
+
active: boolean;
|
|
41
|
+
title: string;
|
|
42
|
+
}[];
|
|
43
|
+
capturePane(session: string, paneId?: string): string | null;
|
|
24
44
|
sendKeys(session: string, keys: string): boolean;
|
|
25
45
|
sendSpecialKey(session: string, key: string): boolean;
|
|
26
46
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -41,7 +61,17 @@ export declare class WindowsTmuxExecutor implements TmuxExecutor {
|
|
|
41
61
|
constructor(itmuxPath: string);
|
|
42
62
|
private executeCommand;
|
|
43
63
|
listSessions(): string[];
|
|
44
|
-
|
|
64
|
+
listPanes(session: string): {
|
|
65
|
+
id: string;
|
|
66
|
+
index: number;
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
top: number;
|
|
70
|
+
left: number;
|
|
71
|
+
active: boolean;
|
|
72
|
+
title: string;
|
|
73
|
+
}[];
|
|
74
|
+
capturePane(session: string, paneId?: string): string | null;
|
|
45
75
|
sendKeys(session: string, keys: string): boolean;
|
|
46
76
|
sendSpecialKey(session: string, key: string): boolean;
|
|
47
77
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -60,9 +60,31 @@ class UnixTmuxExecutor {
|
|
|
60
60
|
return [];
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
listPanes(session) {
|
|
64
64
|
try {
|
|
65
|
-
const output = (0, child_process_1.execSync)(`tmux
|
|
65
|
+
const output = (0, child_process_1.execSync)(`tmux list-panes -t "${session}" -F "#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{pane_active}:#{pane_title}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
66
|
+
return output.trim().split('\n').filter(l => l.length > 0).map(line => {
|
|
67
|
+
const parts = line.split(':');
|
|
68
|
+
return {
|
|
69
|
+
id: parts[0],
|
|
70
|
+
index: parseInt(parts[1], 10),
|
|
71
|
+
width: parseInt(parts[2], 10),
|
|
72
|
+
height: parseInt(parts[3], 10),
|
|
73
|
+
top: parseInt(parts[4], 10),
|
|
74
|
+
left: parseInt(parts[5], 10),
|
|
75
|
+
active: parts[6] === '1',
|
|
76
|
+
title: parts.slice(7).join(':') // title may contain colons
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
capturePane(session, paneId) {
|
|
85
|
+
try {
|
|
86
|
+
const target = paneId ? `${session}.${paneId}` : session;
|
|
87
|
+
const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${target}" -p -e -N`, {
|
|
66
88
|
encoding: 'utf-8',
|
|
67
89
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
68
90
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -223,9 +245,34 @@ class WindowsTmuxExecutor {
|
|
|
223
245
|
return [];
|
|
224
246
|
}
|
|
225
247
|
}
|
|
226
|
-
|
|
248
|
+
listPanes(session) {
|
|
227
249
|
try {
|
|
228
250
|
const escaped = this.escapeSession(session);
|
|
251
|
+
const output = this.executeCommand(`tmux list-panes -t '${escaped}' -F '#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{pane_active}:#{pane_title}'`);
|
|
252
|
+
if (!output)
|
|
253
|
+
return [];
|
|
254
|
+
return output.split('\n').map(s => s.trim()).filter(l => l.length > 0).map(line => {
|
|
255
|
+
const parts = line.split(':');
|
|
256
|
+
return {
|
|
257
|
+
id: parts[0],
|
|
258
|
+
index: parseInt(parts[1], 10),
|
|
259
|
+
width: parseInt(parts[2], 10),
|
|
260
|
+
height: parseInt(parts[3], 10),
|
|
261
|
+
top: parseInt(parts[4], 10),
|
|
262
|
+
left: parseInt(parts[5], 10),
|
|
263
|
+
active: parts[6] === '1',
|
|
264
|
+
title: parts.slice(7).join(':')
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
capturePane(session, paneId) {
|
|
273
|
+
try {
|
|
274
|
+
const target = paneId ? `${session}.${paneId}` : session;
|
|
275
|
+
const escaped = this.escapeSession(target);
|
|
229
276
|
const output = this.executeCommand(`tmux capture-pane -t '${escaped}' -p -e -N`);
|
|
230
277
|
if (!output)
|
|
231
278
|
return null;
|
package/dist/agent/tmux.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TmuxSession } from './types';
|
|
1
|
+
import { TmuxSession, PaneInfo } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Scan for all tmux sessions
|
|
4
4
|
*/
|
|
@@ -7,10 +7,14 @@ export declare function scanSessions(): string[];
|
|
|
7
7
|
* Get detailed session info
|
|
8
8
|
*/
|
|
9
9
|
export declare function listSessions(): TmuxSession[];
|
|
10
|
+
/**
|
|
11
|
+
* List all panes in a tmux session
|
|
12
|
+
*/
|
|
13
|
+
export declare function listPanes(sessionName: string): PaneInfo[];
|
|
10
14
|
/**
|
|
11
15
|
* Capture tmux pane content with escape sequences (colors)
|
|
12
16
|
*/
|
|
13
|
-
export declare function capturePane(sessionName: string): string | null;
|
|
17
|
+
export declare function capturePane(sessionName: string, paneId?: string): string | null;
|
|
14
18
|
/**
|
|
15
19
|
* Send keys to tmux session
|
|
16
20
|
*/
|
package/dist/agent/tmux.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.scanSessions = scanSessions;
|
|
4
4
|
exports.listSessions = listSessions;
|
|
5
|
+
exports.listPanes = listPanes;
|
|
5
6
|
exports.capturePane = capturePane;
|
|
6
7
|
exports.sendKeys = sendKeys;
|
|
7
8
|
exports.resizeWindow = resizeWindow;
|
|
@@ -49,11 +50,17 @@ function listSessions() {
|
|
|
49
50
|
return [];
|
|
50
51
|
}
|
|
51
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* List all panes in a tmux session
|
|
55
|
+
*/
|
|
56
|
+
function listPanes(sessionName) {
|
|
57
|
+
return getExecutor().listPanes(sessionName);
|
|
58
|
+
}
|
|
52
59
|
/**
|
|
53
60
|
* Capture tmux pane content with escape sequences (colors)
|
|
54
61
|
*/
|
|
55
|
-
function capturePane(sessionName) {
|
|
56
|
-
return getExecutor().capturePane(sessionName);
|
|
62
|
+
function capturePane(sessionName, paneId) {
|
|
63
|
+
return getExecutor().capturePane(sessionName, paneId);
|
|
57
64
|
}
|
|
58
65
|
/**
|
|
59
66
|
* Send keys to tmux session
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -37,6 +37,16 @@ export interface TmuxSession {
|
|
|
37
37
|
created?: string;
|
|
38
38
|
attached: boolean;
|
|
39
39
|
}
|
|
40
|
+
export interface PaneInfo {
|
|
41
|
+
id: string;
|
|
42
|
+
index: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
top: number;
|
|
46
|
+
left: number;
|
|
47
|
+
active: boolean;
|
|
48
|
+
title: string;
|
|
49
|
+
}
|
|
40
50
|
export interface ExecResult {
|
|
41
51
|
exitCode: number;
|
|
42
52
|
stdout: string;
|
|
@@ -29,8 +29,24 @@ export declare class RelayWebSocketClient extends EventEmitter {
|
|
|
29
29
|
private handleError;
|
|
30
30
|
private scheduleReconnect;
|
|
31
31
|
send(message: Message): boolean;
|
|
32
|
-
sendScreen(data: Buffer
|
|
33
|
-
|
|
32
|
+
sendScreen(data: Buffer, paneMeta?: {
|
|
33
|
+
pane: string;
|
|
34
|
+
index: number;
|
|
35
|
+
}): boolean;
|
|
36
|
+
sendScreenCompressed(data: Buffer, paneMeta?: {
|
|
37
|
+
pane: string;
|
|
38
|
+
index: number;
|
|
39
|
+
}): boolean;
|
|
40
|
+
sendPaneLayout(panes: {
|
|
41
|
+
id: string;
|
|
42
|
+
index: number;
|
|
43
|
+
width: number;
|
|
44
|
+
height: number;
|
|
45
|
+
top: number;
|
|
46
|
+
left: number;
|
|
47
|
+
active: boolean;
|
|
48
|
+
title: string;
|
|
49
|
+
}[]): boolean;
|
|
34
50
|
/**
|
|
35
51
|
* Send file content to be displayed in the web FileViewer
|
|
36
52
|
* @param filename - The name of the file
|
package/dist/agent/websocket.js
CHANGED
|
@@ -70,8 +70,8 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
70
70
|
this.isConnected = true;
|
|
71
71
|
this.reconnectAttempts = 0;
|
|
72
72
|
this.circuitBreakerOpen = false;
|
|
73
|
-
this.emit('connected');
|
|
74
73
|
this.registerAsHost();
|
|
74
|
+
this.emit('connected');
|
|
75
75
|
});
|
|
76
76
|
this.ws.on('message', (data) => {
|
|
77
77
|
try {
|
|
@@ -119,7 +119,7 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
119
119
|
switch (message.type) {
|
|
120
120
|
case 'keys':
|
|
121
121
|
if (message.session === this.sessionId && message.payload) {
|
|
122
|
-
this.emit('keys', message.payload);
|
|
122
|
+
this.emit('keys', message.payload, message.meta);
|
|
123
123
|
}
|
|
124
124
|
break;
|
|
125
125
|
case 'resize':
|
|
@@ -239,32 +239,49 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
239
239
|
return false;
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
sendScreen(data) {
|
|
242
|
+
sendScreen(data, paneMeta) {
|
|
243
243
|
if (!this.isConnected)
|
|
244
244
|
return false;
|
|
245
245
|
const base64Data = data.toString('base64');
|
|
246
|
-
|
|
246
|
+
const msg = {
|
|
247
247
|
type: 'screen',
|
|
248
248
|
session: this.sessionId,
|
|
249
249
|
payload: base64Data
|
|
250
|
-
}
|
|
250
|
+
};
|
|
251
|
+
if (paneMeta) {
|
|
252
|
+
msg.meta = { pane: paneMeta.pane, index: String(paneMeta.index) };
|
|
253
|
+
}
|
|
254
|
+
return this.send(msg);
|
|
251
255
|
}
|
|
252
|
-
sendScreenCompressed(data) {
|
|
256
|
+
sendScreenCompressed(data, paneMeta) {
|
|
253
257
|
if (!this.isConnected)
|
|
254
258
|
return false;
|
|
255
259
|
try {
|
|
256
260
|
const compressed = zlib.gzipSync(data);
|
|
257
261
|
const base64Data = compressed.toString('base64');
|
|
258
|
-
|
|
262
|
+
const msg = {
|
|
259
263
|
type: 'screenGz',
|
|
260
264
|
session: this.sessionId,
|
|
261
265
|
payload: base64Data
|
|
262
|
-
}
|
|
266
|
+
};
|
|
267
|
+
if (paneMeta) {
|
|
268
|
+
msg.meta = { pane: paneMeta.pane, index: String(paneMeta.index) };
|
|
269
|
+
}
|
|
270
|
+
return this.send(msg);
|
|
263
271
|
}
|
|
264
272
|
catch {
|
|
265
|
-
return this.sendScreen(data);
|
|
273
|
+
return this.sendScreen(data, paneMeta);
|
|
266
274
|
}
|
|
267
275
|
}
|
|
276
|
+
sendPaneLayout(panes) {
|
|
277
|
+
if (!this.isConnected)
|
|
278
|
+
return false;
|
|
279
|
+
return this.send({
|
|
280
|
+
type: 'paneLayout',
|
|
281
|
+
session: this.sessionId,
|
|
282
|
+
payload: JSON.stringify(panes)
|
|
283
|
+
});
|
|
284
|
+
}
|
|
268
285
|
/**
|
|
269
286
|
* Send file content to be displayed in the web FileViewer
|
|
270
287
|
* @param filename - The name of the file
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoPilot - Single-prompt execution layer for SessionCast
|
|
3
|
+
*
|
|
4
|
+
* Enables opencode-style experience: one prompt → auto-detect → auto-analyze → auto-execute
|
|
5
|
+
*/
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import { AutoPilotContext, AutoPilotOptions, GeneratedWorkflow } from './types';
|
|
8
|
+
import { toExecutableWorkflow } from './workflow-generator';
|
|
9
|
+
import { generateSpeckit } from './speckit-generator';
|
|
10
|
+
export * from './types';
|
|
11
|
+
export { generateSpeckit, generateQuickSpeckit, saveSpeckit } from './speckit-generator';
|
|
12
|
+
export type { SpeckitOutput } from './speckit-generator';
|
|
13
|
+
export declare class AutoPilot extends EventEmitter {
|
|
14
|
+
private options;
|
|
15
|
+
private context;
|
|
16
|
+
private llmClient?;
|
|
17
|
+
constructor(options: AutoPilotOptions);
|
|
18
|
+
/**
|
|
19
|
+
* Create initial context
|
|
20
|
+
*/
|
|
21
|
+
private createInitialContext;
|
|
22
|
+
/**
|
|
23
|
+
* Set LLM client for mission analysis
|
|
24
|
+
*/
|
|
25
|
+
setLlmClient(client: {
|
|
26
|
+
chat: (messages: {
|
|
27
|
+
role: string;
|
|
28
|
+
content: string;
|
|
29
|
+
}[]) => Promise<string>;
|
|
30
|
+
}): void;
|
|
31
|
+
/**
|
|
32
|
+
* Get current context
|
|
33
|
+
*/
|
|
34
|
+
getContext(): AutoPilotContext;
|
|
35
|
+
/**
|
|
36
|
+
* Main entry point: execute a single prompt
|
|
37
|
+
*/
|
|
38
|
+
execute(prompt: string): Promise<GeneratedWorkflow>;
|
|
39
|
+
/**
|
|
40
|
+
* Quick execute without LLM analysis
|
|
41
|
+
*/
|
|
42
|
+
quickExecute(prompt: string): Promise<GeneratedWorkflow>;
|
|
43
|
+
/**
|
|
44
|
+
* Phase 1: Detect project type
|
|
45
|
+
*/
|
|
46
|
+
private detectProject;
|
|
47
|
+
/**
|
|
48
|
+
* Phase 2: Scan sources
|
|
49
|
+
*/
|
|
50
|
+
private scanProject;
|
|
51
|
+
/**
|
|
52
|
+
* Phase 3: Analyze mission
|
|
53
|
+
*/
|
|
54
|
+
private analyzeMission;
|
|
55
|
+
/**
|
|
56
|
+
* Phase 4: Generate workflow
|
|
57
|
+
*/
|
|
58
|
+
private generateWorkflow;
|
|
59
|
+
/**
|
|
60
|
+
* Convert to executable workflow format (compatible with existing ProjectManager)
|
|
61
|
+
*/
|
|
62
|
+
toExecutableFormat(): ReturnType<typeof toExecutableWorkflow> | null;
|
|
63
|
+
/**
|
|
64
|
+
* Convert to Speckit format (plan.md + tasks.md)
|
|
65
|
+
*/
|
|
66
|
+
toSpeckit(): ReturnType<typeof generateSpeckit>;
|
|
67
|
+
/**
|
|
68
|
+
* Generate and save Speckit files
|
|
69
|
+
*/
|
|
70
|
+
saveSpeckit(outputDir?: string): {
|
|
71
|
+
planPath: string;
|
|
72
|
+
tasksPath: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Update status and emit event
|
|
76
|
+
*/
|
|
77
|
+
private updateStatus;
|
|
78
|
+
/**
|
|
79
|
+
* Get a summary of the analysis for display
|
|
80
|
+
*/
|
|
81
|
+
getSummary(): string;
|
|
82
|
+
/**
|
|
83
|
+
* Save workflow to file
|
|
84
|
+
*/
|
|
85
|
+
saveWorkflow(outputPath?: string): Promise<string>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Convenience function for one-shot execution
|
|
89
|
+
*/
|
|
90
|
+
export declare function autoPilot(prompt: string, options: AutoPilotOptions): Promise<GeneratedWorkflow>;
|
|
91
|
+
/**
|
|
92
|
+
* Quick version without LLM
|
|
93
|
+
*/
|
|
94
|
+
export declare function autoPilotQuick(prompt: string, options: AutoPilotOptions): Promise<GeneratedWorkflow>;
|