dmux 2.2.0 → 3.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/DmuxApp.d.ts.map +1 -1
- package/dist/DmuxApp.js +412 -179
- package/dist/DmuxApp.js.map +1 -1
- package/dist/MergePane.d.ts.map +1 -1
- package/dist/MergePane.js +4 -15
- package/dist/MergePane.js.map +1 -1
- package/dist/PaneAnalyzer.d.ts +45 -0
- package/dist/PaneAnalyzer.d.ts.map +1 -0
- package/dist/PaneAnalyzer.js +278 -0
- package/dist/PaneAnalyzer.js.map +1 -0
- package/dist/_plugin-vue_export-helper.css +1 -0
- package/dist/actions/index.d.ts +19 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +54 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/paneActions.d.ts +45 -0
- package/dist/actions/paneActions.d.ts.map +1 -0
- package/dist/actions/paneActions.js +932 -0
- package/dist/actions/paneActions.js.map +1 -0
- package/dist/actions/types.d.ts +101 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +129 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/adapters/apiActionHandler.d.ts +64 -0
- package/dist/adapters/apiActionHandler.d.ts.map +1 -0
- package/dist/adapters/apiActionHandler.js +170 -0
- package/dist/adapters/apiActionHandler.js.map +1 -0
- package/dist/adapters/tuiActionHandler.d.ts +57 -0
- package/dist/adapters/tuiActionHandler.d.ts.map +1 -0
- package/dist/adapters/tuiActionHandler.js +152 -0
- package/dist/adapters/tuiActionHandler.js.map +1 -0
- package/dist/chunks/_plugin-vue_export-helper-Cvoq67hi.js +28 -0
- package/dist/components/ActionChoiceDialog.d.ts +16 -0
- package/dist/components/ActionChoiceDialog.d.ts.map +1 -0
- package/dist/components/ActionChoiceDialog.js +29 -0
- package/dist/components/ActionChoiceDialog.js.map +1 -0
- package/dist/components/ActionConfirmDialog.d.ts +16 -0
- package/dist/components/ActionConfirmDialog.d.ts.map +1 -0
- package/dist/components/ActionConfirmDialog.js +31 -0
- package/dist/components/ActionConfirmDialog.js.map +1 -0
- package/dist/components/ActionInputDialog.d.ts +16 -0
- package/dist/components/ActionInputDialog.d.ts.map +1 -0
- package/dist/components/ActionInputDialog.js +49 -0
- package/dist/components/ActionInputDialog.js.map +1 -0
- package/dist/components/ActionProgressDialog.d.ts +13 -0
- package/dist/components/ActionProgressDialog.d.ts.map +1 -0
- package/dist/components/ActionProgressDialog.js +20 -0
- package/dist/components/ActionProgressDialog.js.map +1 -0
- package/dist/components/FooterHelp.d.ts +2 -0
- package/dist/components/FooterHelp.d.ts.map +1 -1
- package/dist/components/FooterHelp.js +9 -2
- package/dist/components/FooterHelp.js.map +1 -1
- package/dist/components/KebabMenu.d.ts +10 -0
- package/dist/components/KebabMenu.d.ts.map +1 -0
- package/dist/components/KebabMenu.js +18 -0
- package/dist/components/KebabMenu.js.map +1 -0
- package/dist/components/LoadingIndicator.d.ts.map +1 -1
- package/dist/components/LoadingIndicator.js +5 -5
- package/dist/components/LoadingIndicator.js.map +1 -1
- package/dist/components/PaneCard.d.ts +1 -0
- package/dist/components/PaneCard.d.ts.map +1 -1
- package/dist/components/PaneCard.js +21 -20
- package/dist/components/PaneCard.js.map +1 -1
- package/dist/components/PanesGrid.d.ts +1 -0
- package/dist/components/PanesGrid.d.ts.map +1 -1
- package/dist/components/PanesGrid.js +5 -4
- package/dist/components/PanesGrid.js.map +1 -1
- package/dist/components/QRCode.d.ts +7 -0
- package/dist/components/QRCode.d.ts.map +1 -0
- package/dist/components/QRCode.js +19 -0
- package/dist/components/QRCode.js.map +1 -0
- package/dist/components/Spinner.d.ts +10 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +15 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/dashboard.html +14 -0
- package/dist/dashboard.js +2 -0
- package/dist/hooks/useActionSystem.d.ts +31 -0
- package/dist/hooks/useActionSystem.d.ts.map +1 -0
- package/dist/hooks/useActionSystem.js +95 -0
- package/dist/hooks/useActionSystem.js.map +1 -0
- package/dist/hooks/useAgentStatus.d.ts +4 -3
- package/dist/hooks/useAgentStatus.d.ts.map +1 -1
- package/dist/hooks/useAgentStatus.js +45 -194
- package/dist/hooks/useAgentStatus.js.map +1 -1
- package/dist/hooks/usePaneCreation.d.ts +2 -1
- package/dist/hooks/usePaneCreation.d.ts.map +1 -1
- package/dist/hooks/usePaneCreation.js +46 -100
- package/dist/hooks/usePaneCreation.js.map +1 -1
- package/dist/hooks/usePanes.d.ts.map +1 -1
- package/dist/hooks/usePanes.js +5 -3
- package/dist/hooks/usePanes.js.map +1 -1
- package/dist/hooks/useTerminalWidth.d.ts.map +1 -1
- package/dist/hooks/useTerminalWidth.js +18 -1
- package/dist/hooks/useTerminalWidth.js.map +1 -1
- package/dist/hooks/useWorktreeActions.d.ts.map +1 -1
- package/dist/hooks/useWorktreeActions.js +4 -0
- package/dist/hooks/useWorktreeActions.js.map +1 -1
- package/dist/index.js +43 -6
- package/dist/index.js.map +1 -1
- package/dist/server/actionsApi.d.ts +37 -0
- package/dist/server/actionsApi.d.ts.map +1 -0
- package/dist/server/actionsApi.js +256 -0
- package/dist/server/actionsApi.js.map +1 -0
- package/dist/server/embedded-assets.d.ts +13 -0
- package/dist/server/embedded-assets.d.ts.map +1 -0
- package/dist/server/embedded-assets.js +5012 -0
- package/dist/server/embedded-assets.js.map +1 -0
- package/dist/server/index.d.ts +21 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +99 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/routes.js +672 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/static.d.ts +6 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +3040 -0
- package/dist/server/static.js.map +1 -0
- package/dist/services/ConfigWatcher.d.ts +20 -0
- package/dist/services/ConfigWatcher.d.ts.map +1 -0
- package/dist/services/ConfigWatcher.js +75 -0
- package/dist/services/ConfigWatcher.js.map +1 -0
- package/dist/services/PaneWorkerManager.d.ts +69 -0
- package/dist/services/PaneWorkerManager.d.ts.map +1 -0
- package/dist/services/PaneWorkerManager.js +272 -0
- package/dist/services/PaneWorkerManager.js.map +1 -0
- package/dist/services/StatusDetector.d.ts +87 -0
- package/dist/services/StatusDetector.d.ts.map +1 -0
- package/dist/services/StatusDetector.js +387 -0
- package/dist/services/StatusDetector.js.map +1 -0
- package/dist/services/TerminalDiffer.d.ts +85 -0
- package/dist/services/TerminalDiffer.d.ts.map +1 -0
- package/dist/services/TerminalDiffer.js +499 -0
- package/dist/services/TerminalDiffer.js.map +1 -0
- package/dist/services/TerminalStreamer.d.ts +80 -0
- package/dist/services/TerminalStreamer.d.ts.map +1 -0
- package/dist/services/TerminalStreamer.js +490 -0
- package/dist/services/TerminalStreamer.js.map +1 -0
- package/dist/services/TunnelService.d.ts +9 -0
- package/dist/services/TunnelService.d.ts.map +1 -0
- package/dist/services/TunnelService.js +34 -0
- package/dist/services/TunnelService.js.map +1 -0
- package/dist/services/WorkerMessageBus.d.ts +48 -0
- package/dist/services/WorkerMessageBus.d.ts.map +1 -0
- package/dist/services/WorkerMessageBus.js +120 -0
- package/dist/services/WorkerMessageBus.js.map +1 -0
- package/dist/shared/StateManager.d.ts +34 -0
- package/dist/shared/StateManager.d.ts.map +1 -0
- package/dist/shared/StateManager.js +108 -0
- package/dist/shared/StateManager.js.map +1 -0
- package/dist/shared/StreamProtocol.d.ts +75 -0
- package/dist/shared/StreamProtocol.d.ts.map +1 -0
- package/dist/shared/StreamProtocol.js +37 -0
- package/dist/shared/StreamProtocol.js.map +1 -0
- package/dist/terminal.html +17 -0
- package/dist/terminal.js +3 -0
- package/dist/types.d.ts +21 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agentDetection.d.ts +18 -0
- package/dist/utils/agentDetection.d.ts.map +1 -0
- package/dist/utils/agentDetection.js +73 -0
- package/dist/utils/agentDetection.js.map +1 -0
- package/dist/utils/aiMerge.d.ts +35 -0
- package/dist/utils/aiMerge.d.ts.map +1 -0
- package/dist/utils/aiMerge.js +298 -0
- package/dist/utils/aiMerge.js.map +1 -0
- package/dist/utils/conflictResolutionPane.d.ts +19 -0
- package/dist/utils/conflictResolutionPane.d.ts.map +1 -0
- package/dist/utils/conflictResolutionPane.js +214 -0
- package/dist/utils/conflictResolutionPane.js.map +1 -0
- package/dist/utils/mergeExecution.d.ts +52 -0
- package/dist/utils/mergeExecution.d.ts.map +1 -0
- package/dist/utils/mergeExecution.js +192 -0
- package/dist/utils/mergeExecution.js.map +1 -0
- package/dist/utils/mergeValidation.d.ts +67 -0
- package/dist/utils/mergeValidation.d.ts.map +1 -0
- package/dist/utils/mergeValidation.js +213 -0
- package/dist/utils/mergeValidation.js.map +1 -0
- package/dist/utils/paneCreation.d.ts +17 -0
- package/dist/utils/paneCreation.d.ts.map +1 -0
- package/dist/utils/paneCreation.js +274 -0
- package/dist/utils/paneCreation.js.map +1 -0
- package/dist/utils/port.d.ts +5 -0
- package/dist/utils/port.d.ts.map +1 -0
- package/dist/utils/port.js +54 -0
- package/dist/utils/port.js.map +1 -0
- package/dist/workers/PaneWorker.d.ts +2 -0
- package/dist/workers/PaneWorker.d.ts.map +1 -0
- package/dist/workers/PaneWorker.js +362 -0
- package/dist/workers/PaneWorker.js.map +1 -0
- package/dist/workers/WorkerMessages.d.ts +36 -0
- package/dist/workers/WorkerMessages.d.ts.map +1 -0
- package/dist/workers/WorkerMessages.js +9 -0
- package/dist/workers/WorkerMessages.js.map +1 -0
- package/package.json +19 -5
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
import { eventHandler, getRouterParams, readBody, setHeader, createEventStream, createRouter } from 'h3';
|
|
2
|
+
import { StateManager } from '../shared/StateManager.js';
|
|
3
|
+
import { getEmbeddedAsset } from './embedded-assets.js';
|
|
4
|
+
function serveEmbeddedAsset(filename) {
|
|
5
|
+
const asset = getEmbeddedAsset(filename);
|
|
6
|
+
if (!asset) {
|
|
7
|
+
throw new Error(`Embedded asset not found: ${filename}`);
|
|
8
|
+
}
|
|
9
|
+
return asset.content;
|
|
10
|
+
}
|
|
11
|
+
import { getTerminalStreamer } from '../services/TerminalStreamer.js';
|
|
12
|
+
import { formatStreamMessage } from '../shared/StreamProtocol.js';
|
|
13
|
+
import { handleListActions, handleGetPaneActions, handleExecuteAction, handleConfirmCallback, handleChoiceCallback, handleInputCallback } from './actionsApi.js';
|
|
14
|
+
const stateManager = StateManager.getInstance();
|
|
15
|
+
export function setupRoutes(app) {
|
|
16
|
+
// CORS middleware for all routes
|
|
17
|
+
app.use('/', eventHandler(async (event) => {
|
|
18
|
+
setHeader(event, 'Access-Control-Allow-Origin', '*');
|
|
19
|
+
setHeader(event, 'Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
20
|
+
setHeader(event, 'Access-Control-Allow-Headers', 'Content-Type');
|
|
21
|
+
if (event.node.req.method === 'OPTIONS') {
|
|
22
|
+
event.node.res.statusCode = 204;
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
// GET /api/health - Health check
|
|
27
|
+
app.use('/api/health', eventHandler(async () => {
|
|
28
|
+
return { status: 'ok', timestamp: Date.now() };
|
|
29
|
+
}));
|
|
30
|
+
// GET /api/session - Get session info
|
|
31
|
+
app.use('/api/session', eventHandler(async () => {
|
|
32
|
+
const state = stateManager.getState();
|
|
33
|
+
return {
|
|
34
|
+
projectName: state.projectName,
|
|
35
|
+
sessionName: state.sessionName,
|
|
36
|
+
projectRoot: state.projectRoot,
|
|
37
|
+
serverUrl: state.serverUrl,
|
|
38
|
+
settings: state.settings,
|
|
39
|
+
paneCount: state.panes.length,
|
|
40
|
+
timestamp: Date.now()
|
|
41
|
+
};
|
|
42
|
+
}));
|
|
43
|
+
// ===== Action System Routes =====
|
|
44
|
+
// Create a router for exact route matching
|
|
45
|
+
const apiRouter = createRouter();
|
|
46
|
+
// GET /api/actions - List all actions
|
|
47
|
+
apiRouter.get('/api/actions', eventHandler(async (event) => {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
handleListActions(event.node.req, {
|
|
50
|
+
writeHead: (code, headers) => {
|
|
51
|
+
event.node.res.statusCode = code;
|
|
52
|
+
Object.entries(headers).forEach(([k, v]) => setHeader(event, k, v));
|
|
53
|
+
},
|
|
54
|
+
end: (data) => resolve(JSON.parse(data))
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}));
|
|
58
|
+
// GET /api/panes/:id/actions - Get available actions for pane
|
|
59
|
+
apiRouter.get('/api/panes/:id/actions', eventHandler(async (event) => {
|
|
60
|
+
const params = getRouterParams(event);
|
|
61
|
+
const paneId = params?.id;
|
|
62
|
+
if (!paneId) {
|
|
63
|
+
event.node.res.statusCode = 400;
|
|
64
|
+
return { error: 'Missing pane ID' };
|
|
65
|
+
}
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
handleGetPaneActions(event.node.req, {
|
|
68
|
+
writeHead: (code, headers) => {
|
|
69
|
+
event.node.res.statusCode = code;
|
|
70
|
+
Object.entries(headers).forEach(([k, v]) => setHeader(event, k, v));
|
|
71
|
+
},
|
|
72
|
+
end: (data) => resolve(JSON.parse(data))
|
|
73
|
+
}, decodeURIComponent(paneId));
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
76
|
+
// POST /api/panes/:id/actions/:actionId - Execute action
|
|
77
|
+
apiRouter.post('/api/panes/:paneId/actions/:actionId', eventHandler(async (event) => {
|
|
78
|
+
const params = getRouterParams(event);
|
|
79
|
+
const paneId = params?.paneId;
|
|
80
|
+
const actionId = params?.actionId;
|
|
81
|
+
if (!paneId || !actionId) {
|
|
82
|
+
event.node.res.statusCode = 400;
|
|
83
|
+
return { error: 'Missing pane ID or action ID' };
|
|
84
|
+
}
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
handleExecuteAction(event.node.req, {
|
|
87
|
+
writeHead: (code, headers) => {
|
|
88
|
+
event.node.res.statusCode = code;
|
|
89
|
+
Object.entries(headers).forEach(([k, v]) => setHeader(event, k, v));
|
|
90
|
+
},
|
|
91
|
+
end: (data) => resolve(JSON.parse(data))
|
|
92
|
+
}, decodeURIComponent(paneId), decodeURIComponent(actionId));
|
|
93
|
+
});
|
|
94
|
+
}));
|
|
95
|
+
// POST /api/callbacks/confirm/:callbackId - Respond to confirm dialog
|
|
96
|
+
apiRouter.post('/api/callbacks/confirm/:callbackId', eventHandler(async (event) => {
|
|
97
|
+
const params = getRouterParams(event);
|
|
98
|
+
const callbackId = params?.callbackId;
|
|
99
|
+
if (!callbackId) {
|
|
100
|
+
event.node.res.statusCode = 400;
|
|
101
|
+
return { error: 'Missing callback ID' };
|
|
102
|
+
}
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
handleConfirmCallback(event.node.req, {
|
|
105
|
+
writeHead: (code, headers) => {
|
|
106
|
+
event.node.res.statusCode = code;
|
|
107
|
+
Object.entries(headers).forEach(([k, v]) => setHeader(event, k, v));
|
|
108
|
+
},
|
|
109
|
+
end: (data) => resolve(JSON.parse(data))
|
|
110
|
+
}, decodeURIComponent(callbackId));
|
|
111
|
+
});
|
|
112
|
+
}));
|
|
113
|
+
// POST /api/callbacks/choice/:callbackId - Respond to choice dialog
|
|
114
|
+
apiRouter.post('/api/callbacks/choice/:callbackId', eventHandler(async (event) => {
|
|
115
|
+
const params = getRouterParams(event);
|
|
116
|
+
const callbackId = params?.callbackId;
|
|
117
|
+
if (!callbackId) {
|
|
118
|
+
event.node.res.statusCode = 400;
|
|
119
|
+
return { error: 'Missing callback ID' };
|
|
120
|
+
}
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
handleChoiceCallback(event.node.req, {
|
|
123
|
+
writeHead: (code, headers) => {
|
|
124
|
+
event.node.res.statusCode = code;
|
|
125
|
+
Object.entries(headers).forEach(([k, v]) => setHeader(event, k, v));
|
|
126
|
+
},
|
|
127
|
+
end: (data) => resolve(JSON.parse(data))
|
|
128
|
+
}, decodeURIComponent(callbackId));
|
|
129
|
+
});
|
|
130
|
+
}));
|
|
131
|
+
// POST /api/callbacks/input/:callbackId - Respond to input dialog
|
|
132
|
+
apiRouter.post('/api/callbacks/input/:callbackId', eventHandler(async (event) => {
|
|
133
|
+
const params = getRouterParams(event);
|
|
134
|
+
const callbackId = params?.callbackId;
|
|
135
|
+
if (!callbackId) {
|
|
136
|
+
event.node.res.statusCode = 400;
|
|
137
|
+
return { error: 'Missing callback ID' };
|
|
138
|
+
}
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
handleInputCallback(event.node.req, {
|
|
141
|
+
writeHead: (code, headers) => {
|
|
142
|
+
event.node.res.statusCode = code;
|
|
143
|
+
Object.entries(headers).forEach(([k, v]) => setHeader(event, k, v));
|
|
144
|
+
},
|
|
145
|
+
end: (data) => resolve(JSON.parse(data))
|
|
146
|
+
}, decodeURIComponent(callbackId));
|
|
147
|
+
});
|
|
148
|
+
}));
|
|
149
|
+
// GET /api/panes/:id/snapshot - Get current pane snapshot
|
|
150
|
+
apiRouter.get('/api/panes/:id/snapshot', eventHandler(async (event) => {
|
|
151
|
+
const params = getRouterParams(event);
|
|
152
|
+
const paneId = params?.id;
|
|
153
|
+
if (!paneId) {
|
|
154
|
+
event.node.res.statusCode = 400;
|
|
155
|
+
return { error: 'Missing pane ID' };
|
|
156
|
+
}
|
|
157
|
+
const pane = stateManager.getPaneById(decodeURIComponent(paneId));
|
|
158
|
+
if (!pane) {
|
|
159
|
+
event.node.res.statusCode = 404;
|
|
160
|
+
return { error: 'Pane not found' };
|
|
161
|
+
}
|
|
162
|
+
// Capture current pane state from tmux
|
|
163
|
+
const { execSync } = await import('child_process');
|
|
164
|
+
try {
|
|
165
|
+
// Get dimensions
|
|
166
|
+
const dimensionsOutput = execSync(`tmux display-message -p -t ${pane.paneId} -F "#{pane_width},#{pane_height}"`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
167
|
+
const [width, height] = dimensionsOutput.split(',').map(Number);
|
|
168
|
+
// Get content
|
|
169
|
+
const content = execSync(`tmux capture-pane -epJ -t ${pane.paneId}`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
170
|
+
// Get cursor position
|
|
171
|
+
const cursorOutput = execSync(`tmux display-message -p -t ${pane.paneId} -F "#{cursor_y},#{cursor_x}"`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
172
|
+
const [cursorRow, cursorCol] = cursorOutput.split(',').map(Number);
|
|
173
|
+
return {
|
|
174
|
+
width: width || 80,
|
|
175
|
+
height: height || 24,
|
|
176
|
+
content,
|
|
177
|
+
cursorRow: cursorRow || 0,
|
|
178
|
+
cursorCol: cursorCol || 0
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
event.node.res.statusCode = 500;
|
|
183
|
+
return { error: 'Failed to capture pane state' };
|
|
184
|
+
}
|
|
185
|
+
}));
|
|
186
|
+
// POST /api/panes/:id/actions - Execute action on pane
|
|
187
|
+
apiRouter.post('/api/panes/:id/actions', eventHandler(async (event) => {
|
|
188
|
+
const params = getRouterParams(event);
|
|
189
|
+
const paneId = params?.id;
|
|
190
|
+
if (!paneId) {
|
|
191
|
+
event.node.res.statusCode = 400;
|
|
192
|
+
return { error: 'Missing pane ID' };
|
|
193
|
+
}
|
|
194
|
+
const pane = stateManager.getPaneById(decodeURIComponent(paneId));
|
|
195
|
+
if (!pane) {
|
|
196
|
+
event.node.res.statusCode = 404;
|
|
197
|
+
return { error: 'Pane not found' };
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const action = await readBody(event);
|
|
201
|
+
// For now, just acknowledge the action
|
|
202
|
+
// Future: Implement actual pane actions (sendKeys, resize, etc.)
|
|
203
|
+
return {
|
|
204
|
+
status: 'acknowledged',
|
|
205
|
+
paneId,
|
|
206
|
+
action,
|
|
207
|
+
message: 'Action endpoints will be implemented in future versions'
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
event.node.res.statusCode = 400;
|
|
212
|
+
return { error: 'Invalid request body' };
|
|
213
|
+
}
|
|
214
|
+
}));
|
|
215
|
+
// GET /api/panes/:id - Get specific pane
|
|
216
|
+
apiRouter.get('/api/panes/:id', eventHandler(async (event) => {
|
|
217
|
+
const params = getRouterParams(event);
|
|
218
|
+
const paneId = params?.id;
|
|
219
|
+
if (!paneId) {
|
|
220
|
+
event.node.res.statusCode = 400;
|
|
221
|
+
return { error: 'Missing pane ID' };
|
|
222
|
+
}
|
|
223
|
+
const pane = stateManager.getPaneById(decodeURIComponent(paneId));
|
|
224
|
+
if (!pane) {
|
|
225
|
+
event.node.res.statusCode = 404;
|
|
226
|
+
return { error: 'Pane not found' };
|
|
227
|
+
}
|
|
228
|
+
return formatPaneResponse(pane);
|
|
229
|
+
}));
|
|
230
|
+
// GET /api/panes - List all panes
|
|
231
|
+
apiRouter.get('/api/panes', eventHandler(async (event) => {
|
|
232
|
+
const state = stateManager.getState();
|
|
233
|
+
return {
|
|
234
|
+
panes: state.panes.map(formatPaneResponse),
|
|
235
|
+
projectName: state.projectName,
|
|
236
|
+
sessionName: state.sessionName,
|
|
237
|
+
timestamp: Date.now()
|
|
238
|
+
};
|
|
239
|
+
}));
|
|
240
|
+
// GET /api/panes-stream - Stream pane updates via SSE
|
|
241
|
+
apiRouter.get('/api/panes-stream', eventHandler(async (event) => {
|
|
242
|
+
const eventStream = createEventStream(event);
|
|
243
|
+
// Send initial state
|
|
244
|
+
const initialState = stateManager.getState();
|
|
245
|
+
eventStream.push(JSON.stringify({
|
|
246
|
+
type: 'init',
|
|
247
|
+
data: {
|
|
248
|
+
panes: initialState.panes.map(formatPaneResponse),
|
|
249
|
+
projectName: initialState.projectName,
|
|
250
|
+
sessionName: initialState.sessionName,
|
|
251
|
+
timestamp: Date.now()
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
// Subscribe to state changes
|
|
255
|
+
const unsubscribe = stateManager.subscribe((state) => {
|
|
256
|
+
try {
|
|
257
|
+
eventStream.push(JSON.stringify({
|
|
258
|
+
type: 'update',
|
|
259
|
+
data: {
|
|
260
|
+
panes: state.panes.map(formatPaneResponse),
|
|
261
|
+
projectName: state.projectName,
|
|
262
|
+
sessionName: state.sessionName,
|
|
263
|
+
timestamp: Date.now()
|
|
264
|
+
}
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
catch (err) {
|
|
268
|
+
console.error('Failed to send SSE update:', err);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
// Send heartbeat every 30 seconds
|
|
272
|
+
const heartbeat = setInterval(() => {
|
|
273
|
+
try {
|
|
274
|
+
eventStream.push(JSON.stringify({
|
|
275
|
+
type: 'heartbeat',
|
|
276
|
+
timestamp: Date.now()
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
clearInterval(heartbeat);
|
|
281
|
+
}
|
|
282
|
+
}, 30000);
|
|
283
|
+
// Cleanup on disconnect
|
|
284
|
+
event.node.req.on('close', () => {
|
|
285
|
+
clearInterval(heartbeat);
|
|
286
|
+
unsubscribe();
|
|
287
|
+
eventStream.close();
|
|
288
|
+
});
|
|
289
|
+
return eventStream.send();
|
|
290
|
+
}));
|
|
291
|
+
// POST /api/panes - Create a new pane
|
|
292
|
+
apiRouter.post('/api/panes', eventHandler(async (event) => {
|
|
293
|
+
try {
|
|
294
|
+
const body = await readBody(event);
|
|
295
|
+
let { prompt, agent } = body;
|
|
296
|
+
console.error('[API] POST /api/panes called with:', { prompt, agent, body });
|
|
297
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
298
|
+
event.node.res.statusCode = 400;
|
|
299
|
+
return { error: 'Missing or invalid prompt' };
|
|
300
|
+
}
|
|
301
|
+
// Normalize agent to undefined if not provided or empty
|
|
302
|
+
if (!agent || agent === '') {
|
|
303
|
+
agent = undefined;
|
|
304
|
+
}
|
|
305
|
+
console.error('[API] After normalization, agent =', agent);
|
|
306
|
+
if (agent && agent !== 'claude' && agent !== 'opencode') {
|
|
307
|
+
event.node.res.statusCode = 400;
|
|
308
|
+
return { error: 'Invalid agent. Must be "claude" or "opencode"' };
|
|
309
|
+
}
|
|
310
|
+
// Get available agents using robust detection (same as TUI)
|
|
311
|
+
const { execSync } = await import('child_process');
|
|
312
|
+
const fsPromises = await import('fs/promises');
|
|
313
|
+
const availableAgents = [];
|
|
314
|
+
// Check for Claude
|
|
315
|
+
const hasClaude = await (async () => {
|
|
316
|
+
try {
|
|
317
|
+
const userShell = process.env.SHELL || '/bin/bash';
|
|
318
|
+
const result = execSync(`${userShell} -i -c "command -v claude 2>/dev/null || which claude 2>/dev/null"`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
319
|
+
if (result)
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
catch { }
|
|
323
|
+
const claudePaths = [
|
|
324
|
+
`${process.env.HOME}/.claude/local/claude`,
|
|
325
|
+
`${process.env.HOME}/.local/bin/claude`,
|
|
326
|
+
'/usr/local/bin/claude',
|
|
327
|
+
'/opt/homebrew/bin/claude',
|
|
328
|
+
'/usr/bin/claude',
|
|
329
|
+
`${process.env.HOME}/bin/claude`,
|
|
330
|
+
];
|
|
331
|
+
for (const p of claudePaths) {
|
|
332
|
+
try {
|
|
333
|
+
await fsPromises.access(p);
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
catch { }
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
})();
|
|
340
|
+
if (hasClaude)
|
|
341
|
+
availableAgents.push('claude');
|
|
342
|
+
// Check for opencode
|
|
343
|
+
const hasOpencode = await (async () => {
|
|
344
|
+
try {
|
|
345
|
+
const userShell = process.env.SHELL || '/bin/bash';
|
|
346
|
+
const result = execSync(`${userShell} -i -c "command -v opencode 2>/dev/null || which opencode 2>/dev/null"`, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
347
|
+
if (result)
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
catch { }
|
|
351
|
+
const opencodePaths = [
|
|
352
|
+
'/opt/homebrew/bin/opencode',
|
|
353
|
+
'/usr/local/bin/opencode',
|
|
354
|
+
`${process.env.HOME}/.local/bin/opencode`,
|
|
355
|
+
`${process.env.HOME}/bin/opencode`,
|
|
356
|
+
];
|
|
357
|
+
for (const p of opencodePaths) {
|
|
358
|
+
try {
|
|
359
|
+
await fsPromises.access(p);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
catch { }
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
})();
|
|
366
|
+
if (hasOpencode)
|
|
367
|
+
availableAgents.push('opencode');
|
|
368
|
+
console.error('[API] Available agents:', availableAgents);
|
|
369
|
+
if (availableAgents.length === 0) {
|
|
370
|
+
event.node.res.statusCode = 500;
|
|
371
|
+
return { error: 'No agents available. Install claude or opencode.' };
|
|
372
|
+
}
|
|
373
|
+
// If no agent specified and multiple available, return agent choice needed
|
|
374
|
+
if (!agent && availableAgents.length > 1) {
|
|
375
|
+
console.error('[API] Returning needsAgentChoice');
|
|
376
|
+
return {
|
|
377
|
+
needsAgentChoice: true,
|
|
378
|
+
availableAgents,
|
|
379
|
+
message: 'Please specify an agent (claude or opencode) in the request body',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
console.error('[API] Proceeding to create pane with agent:', agent);
|
|
383
|
+
// Import pane creation utility
|
|
384
|
+
const { createPane } = await import('../utils/paneCreation.js');
|
|
385
|
+
const state = stateManager.getState();
|
|
386
|
+
// Create the pane
|
|
387
|
+
const result = await createPane({
|
|
388
|
+
prompt,
|
|
389
|
+
agent: agent || (availableAgents.length === 1 ? availableAgents[0] : undefined),
|
|
390
|
+
projectName: state.projectName || 'unknown',
|
|
391
|
+
existingPanes: state.panes,
|
|
392
|
+
}, availableAgents);
|
|
393
|
+
// Check if agent choice is needed
|
|
394
|
+
if (result.needsAgentChoice) {
|
|
395
|
+
return {
|
|
396
|
+
needsAgentChoice: true,
|
|
397
|
+
availableAgents,
|
|
398
|
+
message: 'Please specify an agent (claude or opencode) in the request body',
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
// Save the new pane to the panes file
|
|
402
|
+
const fs = await import('fs/promises');
|
|
403
|
+
const path = await import('path');
|
|
404
|
+
// Get panes file path from state
|
|
405
|
+
const projectRoot = state.projectRoot || process.cwd();
|
|
406
|
+
const panesFile = path.join(projectRoot, '.dmux', `dmux.config.json`);
|
|
407
|
+
// Read existing panes
|
|
408
|
+
let existingPanes = [];
|
|
409
|
+
try {
|
|
410
|
+
const configContent = await fs.readFile(panesFile, 'utf-8');
|
|
411
|
+
const config = JSON.parse(configContent);
|
|
412
|
+
existingPanes = Array.isArray(config) ? config : config.panes || [];
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
// File doesn't exist yet, start with empty array
|
|
416
|
+
}
|
|
417
|
+
// Add new pane
|
|
418
|
+
const updatedPanes = [...existingPanes, result.pane];
|
|
419
|
+
// Write back to file - ConfigWatcher will update StateManager
|
|
420
|
+
await fs.writeFile(panesFile, JSON.stringify({ panes: updatedPanes }, null, 2));
|
|
421
|
+
return {
|
|
422
|
+
success: true,
|
|
423
|
+
pane: formatPaneResponse(result.pane),
|
|
424
|
+
message: 'Pane created successfully',
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
console.error('Failed to create pane:', err);
|
|
429
|
+
event.node.res.statusCode = 500;
|
|
430
|
+
return { error: 'Failed to create pane', details: err.message };
|
|
431
|
+
}
|
|
432
|
+
}));
|
|
433
|
+
// Mount the API router
|
|
434
|
+
app.use(apiRouter);
|
|
435
|
+
// GET /api/stream/:paneId - Stream terminal output
|
|
436
|
+
// MUST be before the catch-all route
|
|
437
|
+
app.use('/api/stream', eventHandler(async (event) => {
|
|
438
|
+
const url = event.node.req.url || '';
|
|
439
|
+
// When using app.use('/api/stream'), the URL is already stripped
|
|
440
|
+
// So we just need to remove the leading slash
|
|
441
|
+
const dmuxId = url.startsWith('/') ? url.substring(1) : url;
|
|
442
|
+
if (!dmuxId || dmuxId.includes('/')) {
|
|
443
|
+
event.node.res.statusCode = 404;
|
|
444
|
+
return { error: 'Invalid pane ID' };
|
|
445
|
+
}
|
|
446
|
+
// Find the pane by dmux ID
|
|
447
|
+
const pane = stateManager.getPaneById(decodeURIComponent(dmuxId));
|
|
448
|
+
if (!pane || !pane.paneId) {
|
|
449
|
+
event.node.res.statusCode = 404;
|
|
450
|
+
return { error: 'Pane not found' };
|
|
451
|
+
}
|
|
452
|
+
// Create a readable stream
|
|
453
|
+
const { Readable } = await import('stream');
|
|
454
|
+
const stream = new Readable({
|
|
455
|
+
read() { } // No-op, we'll push data as it arrives
|
|
456
|
+
});
|
|
457
|
+
const streamer = getTerminalStreamer();
|
|
458
|
+
// Start streaming - pass the stream object
|
|
459
|
+
await streamer.startStream(pane.id, pane.paneId, stream);
|
|
460
|
+
// Handle client disconnect
|
|
461
|
+
event.node.req.on('close', () => {
|
|
462
|
+
streamer.stopStream(pane.id, stream);
|
|
463
|
+
stream.destroy();
|
|
464
|
+
});
|
|
465
|
+
// Send heartbeat every 30 seconds to keep connection alive
|
|
466
|
+
const heartbeat = setInterval(() => {
|
|
467
|
+
try {
|
|
468
|
+
const heartbeatMessage = {
|
|
469
|
+
type: 'heartbeat',
|
|
470
|
+
timestamp: Date.now()
|
|
471
|
+
};
|
|
472
|
+
stream.push(formatStreamMessage(heartbeatMessage));
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
clearInterval(heartbeat);
|
|
476
|
+
}
|
|
477
|
+
}, 30000);
|
|
478
|
+
// Cleanup heartbeat on disconnect
|
|
479
|
+
event.node.req.on('close', () => {
|
|
480
|
+
clearInterval(heartbeat);
|
|
481
|
+
stream.destroy();
|
|
482
|
+
});
|
|
483
|
+
// Return the readable stream - h3 will pipe it to the response
|
|
484
|
+
return stream;
|
|
485
|
+
}));
|
|
486
|
+
// POST /api/keys/:paneId - Send keystrokes to pane
|
|
487
|
+
app.use('/api/keys', eventHandler(async (event) => {
|
|
488
|
+
// Only accept POST requests
|
|
489
|
+
if (event.node.req.method !== 'POST') {
|
|
490
|
+
event.node.res.statusCode = 405;
|
|
491
|
+
return { error: 'Method not allowed' };
|
|
492
|
+
}
|
|
493
|
+
const url = event.node.req.url || '';
|
|
494
|
+
const dmuxId = url.startsWith('/') ? url.substring(1) : url;
|
|
495
|
+
if (!dmuxId || dmuxId.includes('/')) {
|
|
496
|
+
event.node.res.statusCode = 404;
|
|
497
|
+
return { error: 'Invalid pane ID' };
|
|
498
|
+
}
|
|
499
|
+
// Find the pane by dmux ID
|
|
500
|
+
const pane = stateManager.getPaneById(decodeURIComponent(dmuxId));
|
|
501
|
+
if (!pane || !pane.paneId) {
|
|
502
|
+
event.node.res.statusCode = 404;
|
|
503
|
+
return { error: 'Pane not found' };
|
|
504
|
+
}
|
|
505
|
+
// Read the keystroke data from the request body
|
|
506
|
+
const body = await readBody(event);
|
|
507
|
+
if (!body || typeof body.key !== 'string') {
|
|
508
|
+
event.node.res.statusCode = 400;
|
|
509
|
+
return { error: 'Missing or invalid key data' };
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
const { execSync } = await import('child_process');
|
|
513
|
+
// Map special keys to tmux send-keys format
|
|
514
|
+
const key = body.key;
|
|
515
|
+
let tmuxKey = key;
|
|
516
|
+
// Handle special keys
|
|
517
|
+
const specialKeys = {
|
|
518
|
+
'Enter': 'Enter',
|
|
519
|
+
'Tab': 'Tab',
|
|
520
|
+
'Backspace': 'BSpace',
|
|
521
|
+
'Delete': 'DC', // Delete Character
|
|
522
|
+
'Escape': 'Escape',
|
|
523
|
+
'ArrowUp': 'Up',
|
|
524
|
+
'ArrowDown': 'Down',
|
|
525
|
+
'ArrowLeft': 'Left',
|
|
526
|
+
'ArrowRight': 'Right',
|
|
527
|
+
'Home': 'Home',
|
|
528
|
+
'End': 'End',
|
|
529
|
+
'PageUp': 'PageUp',
|
|
530
|
+
'PageDown': 'PageDown',
|
|
531
|
+
'Space': 'Space'
|
|
532
|
+
};
|
|
533
|
+
// Priority order: Ctrl/Alt combinations first, then special keys, then regular
|
|
534
|
+
// Handle Ctrl+ combinations with regular characters
|
|
535
|
+
if (body.ctrlKey && key.length === 1 && !specialKeys[key]) {
|
|
536
|
+
tmuxKey = `C-${key.toLowerCase()}`;
|
|
537
|
+
}
|
|
538
|
+
// Handle Alt+ combinations with regular characters
|
|
539
|
+
else if (body.altKey && key.length === 1 && !specialKeys[key]) {
|
|
540
|
+
tmuxKey = `M-${key.toLowerCase()}`;
|
|
541
|
+
}
|
|
542
|
+
// Handle Shift+Tab
|
|
543
|
+
else if (body.shiftKey && key === 'Tab') {
|
|
544
|
+
tmuxKey = 'BTab';
|
|
545
|
+
}
|
|
546
|
+
// Handle Shift+Enter - send the escape sequence using printf to handle escape character
|
|
547
|
+
else if (body.shiftKey && key === 'Enter') {
|
|
548
|
+
// Send ESC[13;2~ which is the standard Shift+Enter sequence
|
|
549
|
+
execSync(`printf '\\033[13;2~' | tmux load-buffer - && tmux paste-buffer -t ${pane.paneId}`, {
|
|
550
|
+
stdio: 'pipe',
|
|
551
|
+
shell: '/bin/bash'
|
|
552
|
+
});
|
|
553
|
+
return { success: true, key: 'Shift+Enter (CSI sequence)' };
|
|
554
|
+
}
|
|
555
|
+
// Handle Ctrl+ with special keys
|
|
556
|
+
else if (body.ctrlKey && specialKeys[key]) {
|
|
557
|
+
tmuxKey = `C-${specialKeys[key]}`;
|
|
558
|
+
}
|
|
559
|
+
// Handle Alt+ with special keys
|
|
560
|
+
else if (body.altKey && specialKeys[key]) {
|
|
561
|
+
tmuxKey = `M-${specialKeys[key]}`;
|
|
562
|
+
}
|
|
563
|
+
// Handle special keys alone
|
|
564
|
+
else if (specialKeys[key]) {
|
|
565
|
+
tmuxKey = specialKeys[key];
|
|
566
|
+
}
|
|
567
|
+
// Regular character - use -l flag to send literally
|
|
568
|
+
else if (key.length === 1) {
|
|
569
|
+
execSync(`tmux send-keys -t ${pane.paneId} -l ${JSON.stringify(key)}`, {
|
|
570
|
+
stdio: 'pipe'
|
|
571
|
+
});
|
|
572
|
+
return { success: true, key: key };
|
|
573
|
+
}
|
|
574
|
+
// Send the key to tmux (for all non-literal keys)
|
|
575
|
+
execSync(`tmux send-keys -t ${pane.paneId} ${tmuxKey}`, {
|
|
576
|
+
stdio: 'pipe'
|
|
577
|
+
});
|
|
578
|
+
return { success: true, key: tmuxKey };
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
console.error('Failed to send keys to pane:', error);
|
|
582
|
+
event.node.res.statusCode = 500;
|
|
583
|
+
return { error: 'Failed to send keys', details: error.message };
|
|
584
|
+
}
|
|
585
|
+
}));
|
|
586
|
+
// GET /api/stream-stats - Get streaming statistics
|
|
587
|
+
app.use('/api/stream-stats', eventHandler(async () => {
|
|
588
|
+
const streamer = getTerminalStreamer();
|
|
589
|
+
return streamer.getStats();
|
|
590
|
+
}));
|
|
591
|
+
// GET /api/test-stream - Simple test stream
|
|
592
|
+
app.use('/api/test-stream', eventHandler(async (event) => {
|
|
593
|
+
const { Readable } = await import('stream');
|
|
594
|
+
const stream = new Readable({
|
|
595
|
+
read() { }
|
|
596
|
+
});
|
|
597
|
+
// Send some test data
|
|
598
|
+
stream.push('TEST:First message\n');
|
|
599
|
+
setTimeout(() => stream.push('TEST:Second message\n'), 100);
|
|
600
|
+
setTimeout(() => stream.push('TEST:Third message\n'), 200);
|
|
601
|
+
setTimeout(() => stream.push(null), 300); // End stream
|
|
602
|
+
return stream;
|
|
603
|
+
}));
|
|
604
|
+
// Static files - Dashboard HTML
|
|
605
|
+
// IMPORTANT: This must be last to avoid catching API routes
|
|
606
|
+
app.use('/', eventHandler(async (event) => {
|
|
607
|
+
const path = event.node.req.url || '/';
|
|
608
|
+
// Skip API routes - let them 404 naturally
|
|
609
|
+
if (path.startsWith('/api/')) {
|
|
610
|
+
event.node.res.statusCode = 404;
|
|
611
|
+
return { error: 'API endpoint not found' };
|
|
612
|
+
}
|
|
613
|
+
if (path === '/' || path === '/index.html') {
|
|
614
|
+
setHeader(event, 'Content-Type', 'text/html');
|
|
615
|
+
return serveEmbeddedAsset('dashboard.html');
|
|
616
|
+
}
|
|
617
|
+
// Terminal viewer page
|
|
618
|
+
if (path.startsWith('/panes/')) {
|
|
619
|
+
setHeader(event, 'Content-Type', 'text/html');
|
|
620
|
+
return serveEmbeddedAsset('terminal.html');
|
|
621
|
+
}
|
|
622
|
+
// Serve any CSS file from root of dist/
|
|
623
|
+
if (path.endsWith('.css')) {
|
|
624
|
+
const filename = path.substring(1); // Remove leading /
|
|
625
|
+
const asset = getEmbeddedAsset(filename);
|
|
626
|
+
if (asset) {
|
|
627
|
+
setHeader(event, 'Content-Type', 'text/css');
|
|
628
|
+
return asset.content;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// Serve any JS file from root of dist/ (not in subdirectories)
|
|
632
|
+
if (path.endsWith('.js') && path.lastIndexOf('/') === 0) {
|
|
633
|
+
const filename = path.substring(1); // Remove leading /
|
|
634
|
+
const asset = getEmbeddedAsset(filename);
|
|
635
|
+
if (asset) {
|
|
636
|
+
setHeader(event, 'Content-Type', 'application/javascript');
|
|
637
|
+
return asset.content;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
// Serve chunk files
|
|
641
|
+
if (path.startsWith('/chunks/')) {
|
|
642
|
+
setHeader(event, 'Content-Type', 'application/javascript');
|
|
643
|
+
const filename = path.substring(1); // Remove leading /
|
|
644
|
+
return serveEmbeddedAsset(filename);
|
|
645
|
+
}
|
|
646
|
+
// 404 for unknown routes
|
|
647
|
+
event.node.res.statusCode = 404;
|
|
648
|
+
return 'Not Found';
|
|
649
|
+
}));
|
|
650
|
+
}
|
|
651
|
+
function formatPaneResponse(pane) {
|
|
652
|
+
return {
|
|
653
|
+
id: pane.id,
|
|
654
|
+
slug: pane.slug,
|
|
655
|
+
prompt: pane.prompt,
|
|
656
|
+
paneId: pane.paneId,
|
|
657
|
+
worktreePath: pane.worktreePath,
|
|
658
|
+
agent: pane.agent || 'unknown',
|
|
659
|
+
agentStatus: pane.agentStatus || 'idle',
|
|
660
|
+
testStatus: pane.testStatus,
|
|
661
|
+
testWindowId: pane.testWindowId,
|
|
662
|
+
devStatus: pane.devStatus,
|
|
663
|
+
devUrl: pane.devUrl,
|
|
664
|
+
devWindowId: pane.devWindowId,
|
|
665
|
+
lastAgentCheck: pane.lastAgentCheck,
|
|
666
|
+
optionsQuestion: pane.optionsQuestion,
|
|
667
|
+
options: pane.options,
|
|
668
|
+
potentialHarm: pane.potentialHarm,
|
|
669
|
+
agentSummary: pane.agentSummary
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
//# sourceMappingURL=routes.js.map
|