@cluesmith/codev 2.0.0-rc.71 → 2.0.0-rc.73
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/dashboard/dist/assets/{index-C7FtNK6Y.css → index-4n9zpWLY.css} +1 -1
- package/dashboard/dist/assets/{index-CDAINZKT.js → index-CH_utkcW.js} +32 -27
- package/dashboard/dist/assets/index-CH_utkcW.js.map +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +17 -7
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.d.ts +80 -0
- package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -0
- package/dist/agent-farm/commands/spawn-roles.js +278 -0
- package/dist/agent-farm/commands/spawn-roles.js.map +1 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts +96 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -0
- package/dist/agent-farm/commands/spawn-worktree.js +305 -0
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -0
- package/dist/agent-farm/commands/spawn.d.ts +5 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +65 -725
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts +82 -0
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-instances.js +441 -0
- package/dist/agent-farm/servers/tower-instances.js.map +1 -0
- package/dist/agent-farm/servers/tower-routes.d.ts +34 -0
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-routes.js +1445 -0
- package/dist/agent-farm/servers/tower-routes.js.map +1 -0
- package/dist/agent-farm/servers/tower-server.d.ts +5 -2
- package/dist/agent-farm/servers/tower-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +74 -2860
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +119 -0
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-terminals.js +629 -0
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -0
- package/dist/agent-farm/servers/tower-tunnel.d.ts +34 -0
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-tunnel.js +299 -0
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -0
- package/dist/agent-farm/servers/tower-types.d.ts +85 -0
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-types.js +6 -0
- package/dist/agent-farm/servers/tower-types.js.map +1 -0
- package/dist/agent-farm/servers/tower-utils.d.ts +51 -0
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-utils.js +161 -0
- package/dist/agent-farm/servers/tower-utils.js.map +1 -0
- package/dist/agent-farm/servers/tower-websocket.d.ts +25 -0
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-websocket.js +171 -0
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +1 -1
- package/package.json +1 -1
- package/templates/tower.html +11 -20
- package/dashboard/dist/assets/index-CDAINZKT.js.map +0 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud tunnel management for tower server.
|
|
3
|
+
* Spec 0105: Tower Server Decomposition — Phase 2
|
|
4
|
+
*
|
|
5
|
+
* Contains: tunnel client lifecycle, config file watching,
|
|
6
|
+
* metadata refresh, and tunnel API endpoint handling.
|
|
7
|
+
*/
|
|
8
|
+
import http from 'node:http';
|
|
9
|
+
import type { ProjectTerminals, InstanceStatus } from './tower-types.js';
|
|
10
|
+
import type { TerminalManager } from '../../terminal/pty-manager.js';
|
|
11
|
+
/** Minimal dependencies required by the tunnel module */
|
|
12
|
+
export interface TunnelDeps {
|
|
13
|
+
port: number;
|
|
14
|
+
log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void;
|
|
15
|
+
projectTerminals: Map<string, ProjectTerminals>;
|
|
16
|
+
terminalManager: TerminalManager | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Initialize the tunnel module. Reads cloud config and connects if registered.
|
|
20
|
+
* Starts config file watcher for credential changes.
|
|
21
|
+
*/
|
|
22
|
+
export declare function initTunnel(deps: TunnelDeps, callbacks: {
|
|
23
|
+
getInstances: () => Promise<InstanceStatus[]>;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Shut down the tunnel module. Disconnects client, stops watchers.
|
|
27
|
+
*/
|
|
28
|
+
export declare function shutdownTunnel(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Handle tunnel management endpoints (Spec 0097 Phase 4).
|
|
31
|
+
* Dispatches /api/tunnel/{connect,disconnect,status} requests.
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleTunnelEndpoint(req: http.IncomingMessage, res: http.ServerResponse, tunnelSub: string): Promise<void>;
|
|
34
|
+
//# sourceMappingURL=tower-tunnel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tower-tunnel.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;CACzC;AA+LD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE;IAAE,YAAY,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;CAAE,GAC3D,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAUrC;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAwEf"}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud tunnel management for tower server.
|
|
3
|
+
* Spec 0105: Tower Server Decomposition — Phase 2
|
|
4
|
+
*
|
|
5
|
+
* Contains: tunnel client lifecycle, config file watching,
|
|
6
|
+
* metadata refresh, and tunnel API endpoint handling.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { TunnelClient } from '../lib/tunnel-client.js';
|
|
11
|
+
import { readCloudConfig, getCloudConfigPath, maskApiKey } from '../lib/cloud-config.js';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Module-private state (lifecycle driven by orchestrator)
|
|
14
|
+
// ============================================================================
|
|
15
|
+
let tunnelClient = null;
|
|
16
|
+
let configWatcher = null;
|
|
17
|
+
let configWatchDebounce = null;
|
|
18
|
+
let metadataRefreshInterval = null;
|
|
19
|
+
const METADATA_REFRESH_MS = 30_000;
|
|
20
|
+
/** Stored references set by initTunnel() */
|
|
21
|
+
let _deps = null;
|
|
22
|
+
let _getInstances = null;
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Internal functions
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Gather current tower metadata (projects + terminals) for codevos.ai.
|
|
28
|
+
*/
|
|
29
|
+
async function gatherMetadata() {
|
|
30
|
+
if (!_deps || !_getInstances)
|
|
31
|
+
throw new Error('Tunnel not initialized');
|
|
32
|
+
const instances = await _getInstances();
|
|
33
|
+
const projects = instances.map((i) => ({
|
|
34
|
+
path: i.projectPath,
|
|
35
|
+
name: i.projectName,
|
|
36
|
+
}));
|
|
37
|
+
// Build reverse mapping: terminal ID → project path
|
|
38
|
+
const terminalToProject = new Map();
|
|
39
|
+
for (const [projectPath, entry] of _deps.projectTerminals) {
|
|
40
|
+
if (entry.architect)
|
|
41
|
+
terminalToProject.set(entry.architect, projectPath);
|
|
42
|
+
for (const termId of entry.builders.values())
|
|
43
|
+
terminalToProject.set(termId, projectPath);
|
|
44
|
+
for (const termId of entry.shells.values())
|
|
45
|
+
terminalToProject.set(termId, projectPath);
|
|
46
|
+
}
|
|
47
|
+
const manager = _deps.terminalManager;
|
|
48
|
+
const terminals = [];
|
|
49
|
+
if (manager) {
|
|
50
|
+
for (const session of manager.listSessions()) {
|
|
51
|
+
terminals.push({
|
|
52
|
+
id: session.id,
|
|
53
|
+
projectPath: terminalToProject.get(session.id) ?? '',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { projects, terminals };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Start periodic metadata refresh — re-gathers metadata and pushes to codevos.ai
|
|
61
|
+
* every METADATA_REFRESH_MS while the tunnel is connected.
|
|
62
|
+
*/
|
|
63
|
+
function startMetadataRefresh() {
|
|
64
|
+
stopMetadataRefresh();
|
|
65
|
+
metadataRefreshInterval = setInterval(async () => {
|
|
66
|
+
try {
|
|
67
|
+
if (tunnelClient && tunnelClient.getState() === 'connected') {
|
|
68
|
+
const metadata = await gatherMetadata();
|
|
69
|
+
tunnelClient.sendMetadata(metadata);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
_deps?.log('WARN', `Metadata refresh failed: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
}, METADATA_REFRESH_MS);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Stop the periodic metadata refresh.
|
|
79
|
+
*/
|
|
80
|
+
function stopMetadataRefresh() {
|
|
81
|
+
if (metadataRefreshInterval) {
|
|
82
|
+
clearInterval(metadataRefreshInterval);
|
|
83
|
+
metadataRefreshInterval = null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create or reconnect the tunnel client using the given config.
|
|
88
|
+
* Sets up state change listeners and sends initial metadata.
|
|
89
|
+
*/
|
|
90
|
+
async function connectTunnel(config) {
|
|
91
|
+
if (!_deps)
|
|
92
|
+
throw new Error('Tunnel not initialized');
|
|
93
|
+
// Disconnect existing client if any
|
|
94
|
+
if (tunnelClient) {
|
|
95
|
+
tunnelClient.disconnect();
|
|
96
|
+
}
|
|
97
|
+
const client = new TunnelClient({
|
|
98
|
+
serverUrl: config.server_url,
|
|
99
|
+
apiKey: config.api_key,
|
|
100
|
+
towerId: config.tower_id,
|
|
101
|
+
localPort: _deps.port,
|
|
102
|
+
});
|
|
103
|
+
client.onStateChange((state, prev) => {
|
|
104
|
+
_deps.log('INFO', `Tunnel: ${prev} → ${state}`);
|
|
105
|
+
if (state === 'connected') {
|
|
106
|
+
startMetadataRefresh();
|
|
107
|
+
}
|
|
108
|
+
else if (prev === 'connected') {
|
|
109
|
+
stopMetadataRefresh();
|
|
110
|
+
}
|
|
111
|
+
if (state === 'auth_failed') {
|
|
112
|
+
_deps.log('ERROR', 'Cloud connection failed: API key is invalid or revoked. Run \'af tower register --reauth\' to update credentials.');
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Gather and set initial metadata before connecting
|
|
116
|
+
const metadata = await gatherMetadata();
|
|
117
|
+
client.sendMetadata(metadata);
|
|
118
|
+
tunnelClient = client;
|
|
119
|
+
client.connect();
|
|
120
|
+
// Ensure config watcher is running — the config directory now exists.
|
|
121
|
+
startConfigWatcher();
|
|
122
|
+
return client;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Start watching cloud-config.json for changes.
|
|
126
|
+
* On change: reconnect with new credentials.
|
|
127
|
+
* On delete: disconnect tunnel.
|
|
128
|
+
*/
|
|
129
|
+
function startConfigWatcher() {
|
|
130
|
+
stopConfigWatcher();
|
|
131
|
+
const configPath = getCloudConfigPath();
|
|
132
|
+
const configDir = path.dirname(configPath);
|
|
133
|
+
const configFile = path.basename(configPath);
|
|
134
|
+
// Watch the directory (more reliable than watching the file directly)
|
|
135
|
+
try {
|
|
136
|
+
configWatcher = fs.watch(configDir, (eventType, filename) => {
|
|
137
|
+
if (filename !== configFile)
|
|
138
|
+
return;
|
|
139
|
+
// Debounce: multiple events fire for a single write
|
|
140
|
+
if (configWatchDebounce)
|
|
141
|
+
clearTimeout(configWatchDebounce);
|
|
142
|
+
configWatchDebounce = setTimeout(async () => {
|
|
143
|
+
configWatchDebounce = null;
|
|
144
|
+
try {
|
|
145
|
+
const config = readCloudConfig();
|
|
146
|
+
if (config) {
|
|
147
|
+
_deps?.log('INFO', `Cloud config changed, reconnecting tunnel (key: ${maskApiKey(config.api_key)})`);
|
|
148
|
+
// Reset circuit breaker in case previous key was invalid
|
|
149
|
+
if (tunnelClient)
|
|
150
|
+
tunnelClient.resetCircuitBreaker();
|
|
151
|
+
await connectTunnel(config);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Config deleted or invalid
|
|
155
|
+
_deps?.log('INFO', 'Cloud config removed or invalid, disconnecting tunnel');
|
|
156
|
+
if (tunnelClient) {
|
|
157
|
+
tunnelClient.disconnect();
|
|
158
|
+
tunnelClient = null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
_deps?.log('WARN', `Error handling config change: ${err.message}`);
|
|
164
|
+
}
|
|
165
|
+
}, 500);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Directory doesn't exist yet — that's fine, user hasn't registered
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Stop watching cloud-config.json.
|
|
174
|
+
*/
|
|
175
|
+
function stopConfigWatcher() {
|
|
176
|
+
if (configWatcher) {
|
|
177
|
+
configWatcher.close();
|
|
178
|
+
configWatcher = null;
|
|
179
|
+
}
|
|
180
|
+
if (configWatchDebounce) {
|
|
181
|
+
clearTimeout(configWatchDebounce);
|
|
182
|
+
configWatchDebounce = null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// Public API (called by orchestrator)
|
|
187
|
+
// ============================================================================
|
|
188
|
+
/**
|
|
189
|
+
* Initialize the tunnel module. Reads cloud config and connects if registered.
|
|
190
|
+
* Starts config file watcher for credential changes.
|
|
191
|
+
*/
|
|
192
|
+
export async function initTunnel(deps, callbacks) {
|
|
193
|
+
_deps = deps;
|
|
194
|
+
_getInstances = callbacks.getInstances;
|
|
195
|
+
// Auto-connect tunnel if registered
|
|
196
|
+
try {
|
|
197
|
+
const config = readCloudConfig();
|
|
198
|
+
if (config) {
|
|
199
|
+
deps.log('INFO', `Cloud config found, connecting tunnel (tower: ${config.tower_name}, key: ${maskApiKey(config.api_key)})`);
|
|
200
|
+
await connectTunnel(config);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
deps.log('INFO', 'No cloud config found, operating in local-only mode');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
deps.log('WARN', `Failed to read cloud config: ${err.message}. Operating in local-only mode.`);
|
|
208
|
+
}
|
|
209
|
+
// Start watching cloud-config.json for changes
|
|
210
|
+
startConfigWatcher();
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Shut down the tunnel module. Disconnects client, stops watchers.
|
|
214
|
+
*/
|
|
215
|
+
export function shutdownTunnel() {
|
|
216
|
+
stopMetadataRefresh();
|
|
217
|
+
stopConfigWatcher();
|
|
218
|
+
if (tunnelClient) {
|
|
219
|
+
_deps?.log('INFO', 'Disconnecting tunnel...');
|
|
220
|
+
tunnelClient.disconnect();
|
|
221
|
+
tunnelClient = null;
|
|
222
|
+
}
|
|
223
|
+
_deps = null;
|
|
224
|
+
_getInstances = null;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Handle tunnel management endpoints (Spec 0097 Phase 4).
|
|
228
|
+
* Dispatches /api/tunnel/{connect,disconnect,status} requests.
|
|
229
|
+
*/
|
|
230
|
+
export async function handleTunnelEndpoint(req, res, tunnelSub) {
|
|
231
|
+
// POST connect
|
|
232
|
+
if (req.method === 'POST' && tunnelSub === 'connect') {
|
|
233
|
+
if (!_deps) {
|
|
234
|
+
// Module not initialized yet (server still starting up)
|
|
235
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
236
|
+
res.end(JSON.stringify({ success: false, error: 'Tower is still starting up. Try again shortly.' }));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const config = readCloudConfig();
|
|
241
|
+
if (!config) {
|
|
242
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
243
|
+
res.end(JSON.stringify({ success: false, error: 'Not registered. Run \'af tower register\' first.' }));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (tunnelClient)
|
|
247
|
+
tunnelClient.resetCircuitBreaker();
|
|
248
|
+
const client = await connectTunnel(config);
|
|
249
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
250
|
+
res.end(JSON.stringify({ success: true, state: client.getState() }));
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
_deps?.log('ERROR', `Tunnel connect failed: ${err.message}`);
|
|
254
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
255
|
+
res.end(JSON.stringify({ success: false, error: err.message }));
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// POST disconnect
|
|
260
|
+
if (req.method === 'POST' && tunnelSub === 'disconnect') {
|
|
261
|
+
if (tunnelClient) {
|
|
262
|
+
tunnelClient.disconnect();
|
|
263
|
+
tunnelClient = null;
|
|
264
|
+
}
|
|
265
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
266
|
+
res.end(JSON.stringify({ success: true }));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// GET status
|
|
270
|
+
if (req.method === 'GET' && tunnelSub === 'status') {
|
|
271
|
+
let config = null;
|
|
272
|
+
try {
|
|
273
|
+
config = readCloudConfig();
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// Config file may be corrupted — treat as unregistered
|
|
277
|
+
}
|
|
278
|
+
const state = tunnelClient?.getState() ?? 'disconnected';
|
|
279
|
+
const uptime = tunnelClient?.getUptime() ?? null;
|
|
280
|
+
const response = {
|
|
281
|
+
registered: config !== null,
|
|
282
|
+
state,
|
|
283
|
+
uptime,
|
|
284
|
+
};
|
|
285
|
+
if (config) {
|
|
286
|
+
response.towerId = config.tower_id;
|
|
287
|
+
response.towerName = config.tower_name;
|
|
288
|
+
response.serverUrl = config.server_url;
|
|
289
|
+
response.accessUrl = `${config.server_url}/t/${config.tower_name}/`;
|
|
290
|
+
}
|
|
291
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
292
|
+
res.end(JSON.stringify(response));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Unknown tunnel endpoint
|
|
296
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
297
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=tower-tunnel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tower-tunnel.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-tunnel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for tower server modules.
|
|
3
|
+
* Spec 0105: Tower Server Decomposition
|
|
4
|
+
*/
|
|
5
|
+
import type http from 'node:http';
|
|
6
|
+
import type { WebSocketServer } from 'ws';
|
|
7
|
+
import type Database from 'better-sqlite3';
|
|
8
|
+
import type { TerminalManager } from '../../terminal/pty-manager.js';
|
|
9
|
+
import type { SessionManager } from '../../terminal/session-manager.js';
|
|
10
|
+
import type { GateWatcher } from '../utils/gate-watcher.js';
|
|
11
|
+
import type { TunnelClient } from '../lib/tunnel-client.js';
|
|
12
|
+
import type { FileTab } from '../utils/file-tabs.js';
|
|
13
|
+
import type { GateStatus } from '../utils/gate-status.js';
|
|
14
|
+
/**
|
|
15
|
+
* Shared context passed to all tower modules.
|
|
16
|
+
* The orchestrator (tower-server.ts) owns lifecycle — it creates
|
|
17
|
+
* dependencies in startup order and tears them down in gracefulShutdown.
|
|
18
|
+
*/
|
|
19
|
+
export interface TowerContext {
|
|
20
|
+
port: number;
|
|
21
|
+
log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void;
|
|
22
|
+
terminalManager: TerminalManager;
|
|
23
|
+
shepherdManager: SessionManager | null;
|
|
24
|
+
projectTerminals: Map<string, ProjectTerminals>;
|
|
25
|
+
db: () => Database.Database;
|
|
26
|
+
gateWatcher: GateWatcher;
|
|
27
|
+
broadcastNotification: (n: {
|
|
28
|
+
type: string;
|
|
29
|
+
title: string;
|
|
30
|
+
body: string;
|
|
31
|
+
project?: string;
|
|
32
|
+
}) => void;
|
|
33
|
+
tunnelClient: TunnelClient | null;
|
|
34
|
+
knownProjects: Set<string>;
|
|
35
|
+
server: http.Server;
|
|
36
|
+
terminalWss: WebSocketServer;
|
|
37
|
+
}
|
|
38
|
+
/** Tracks terminals belonging to a project */
|
|
39
|
+
export interface ProjectTerminals {
|
|
40
|
+
architect?: string;
|
|
41
|
+
builders: Map<string, string>;
|
|
42
|
+
shells: Map<string, string>;
|
|
43
|
+
fileTabs: Map<string, FileTab>;
|
|
44
|
+
}
|
|
45
|
+
/** SSE client connection for push notifications */
|
|
46
|
+
export interface SSEClient {
|
|
47
|
+
res: http.ServerResponse;
|
|
48
|
+
id: string;
|
|
49
|
+
}
|
|
50
|
+
/** Rate limiting entry for activation requests */
|
|
51
|
+
export interface RateLimitEntry {
|
|
52
|
+
count: number;
|
|
53
|
+
windowStart: number;
|
|
54
|
+
}
|
|
55
|
+
/** Terminal entry returned to tower UI */
|
|
56
|
+
export interface TerminalEntry {
|
|
57
|
+
type: 'architect' | 'builder' | 'shell' | 'file';
|
|
58
|
+
id: string;
|
|
59
|
+
label: string;
|
|
60
|
+
url: string;
|
|
61
|
+
active: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** Instance status returned to tower UI */
|
|
64
|
+
export interface InstanceStatus {
|
|
65
|
+
projectPath: string;
|
|
66
|
+
projectName: string;
|
|
67
|
+
running: boolean;
|
|
68
|
+
proxyUrl: string;
|
|
69
|
+
architectUrl: string;
|
|
70
|
+
terminals: TerminalEntry[];
|
|
71
|
+
gateStatus?: GateStatus;
|
|
72
|
+
}
|
|
73
|
+
/** SQLite terminal session row shape */
|
|
74
|
+
export interface DbTerminalSession {
|
|
75
|
+
id: string;
|
|
76
|
+
project_path: string;
|
|
77
|
+
type: 'architect' | 'builder' | 'shell';
|
|
78
|
+
role_id: string | null;
|
|
79
|
+
pid: number | null;
|
|
80
|
+
shepherd_socket: string | null;
|
|
81
|
+
shepherd_pid: number | null;
|
|
82
|
+
shepherd_start_time: number | null;
|
|
83
|
+
created_at: string;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=tower-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tower-types.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tower-types.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for tower server.
|
|
3
|
+
* Spec 0105: Tower Server Decomposition — Phase 1
|
|
4
|
+
*
|
|
5
|
+
* Contains: rate limiting, path normalization, temp directory detection,
|
|
6
|
+
* project name extraction, MIME types, and static file serving.
|
|
7
|
+
*/
|
|
8
|
+
import type { ServerResponse } from 'node:http';
|
|
9
|
+
/**
|
|
10
|
+
* Check if a client has exceeded the rate limit for activations.
|
|
11
|
+
* Returns true if rate limit exceeded, false if allowed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRateLimited(clientIp: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Clean up old rate limit entries.
|
|
16
|
+
*/
|
|
17
|
+
export declare function cleanupRateLimits(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Start periodic cleanup of stale rate limit entries.
|
|
20
|
+
* Returns the interval handle so the orchestrator can clear it on shutdown.
|
|
21
|
+
*/
|
|
22
|
+
export declare function startRateLimitCleanup(): ReturnType<typeof setInterval>;
|
|
23
|
+
/**
|
|
24
|
+
* Normalize a project path to its canonical form for consistent SQLite storage.
|
|
25
|
+
* Uses realpath to resolve symlinks and relative paths.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeProjectPath(projectPath: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Get project name from path.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getProjectName(projectPath: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a project path points to a temp directory.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isTempDirectory(projectPath: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Get language identifier for syntax highlighting.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getLanguageForExt(ext: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Get MIME type for a file path (by extension).
|
|
42
|
+
*/
|
|
43
|
+
export declare function getMimeTypeForFile(filePath: string): string;
|
|
44
|
+
/** MIME types for static file serving */
|
|
45
|
+
export declare const MIME_TYPES: Record<string, string>;
|
|
46
|
+
/**
|
|
47
|
+
* Serve a static file from the React dashboard dist.
|
|
48
|
+
* Returns true if the file was served, false otherwise.
|
|
49
|
+
*/
|
|
50
|
+
export declare function serveStaticFile(filePath: string, res: ServerResponse): boolean;
|
|
51
|
+
//# sourceMappingURL=tower-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tower-utils.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAYhD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAgBvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAOxC;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,UAAU,CAAC,OAAO,WAAW,CAAC,CAEtE;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAOhE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE1D;AAYD;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAO5D;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQrD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS3D;AAMD,yCAAyC;AACzC,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAc7C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAgB9E"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for tower server.
|
|
3
|
+
* Spec 0105: Tower Server Decomposition — Phase 1
|
|
4
|
+
*
|
|
5
|
+
* Contains: rate limiting, path normalization, temp directory detection,
|
|
6
|
+
* project name extraction, MIME types, and static file serving.
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Rate Limiting
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
|
|
15
|
+
const RATE_LIMIT_MAX = 10;
|
|
16
|
+
const activationRateLimits = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Check if a client has exceeded the rate limit for activations.
|
|
19
|
+
* Returns true if rate limit exceeded, false if allowed.
|
|
20
|
+
*/
|
|
21
|
+
export function isRateLimited(clientIp) {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const entry = activationRateLimits.get(clientIp);
|
|
24
|
+
if (!entry || now - entry.windowStart >= RATE_LIMIT_WINDOW_MS) {
|
|
25
|
+
// New window
|
|
26
|
+
activationRateLimits.set(clientIp, { count: 1, windowStart: now });
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (entry.count >= RATE_LIMIT_MAX) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
entry.count++;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Clean up old rate limit entries.
|
|
37
|
+
*/
|
|
38
|
+
export function cleanupRateLimits() {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
for (const [ip, entry] of activationRateLimits.entries()) {
|
|
41
|
+
if (now - entry.windowStart >= RATE_LIMIT_WINDOW_MS * 2) {
|
|
42
|
+
activationRateLimits.delete(ip);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Start periodic cleanup of stale rate limit entries.
|
|
48
|
+
* Returns the interval handle so the orchestrator can clear it on shutdown.
|
|
49
|
+
*/
|
|
50
|
+
export function startRateLimitCleanup() {
|
|
51
|
+
return setInterval(cleanupRateLimits, 5 * 60 * 1000);
|
|
52
|
+
}
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Path Utilities
|
|
55
|
+
// ============================================================================
|
|
56
|
+
/**
|
|
57
|
+
* Normalize a project path to its canonical form for consistent SQLite storage.
|
|
58
|
+
* Uses realpath to resolve symlinks and relative paths.
|
|
59
|
+
*/
|
|
60
|
+
export function normalizeProjectPath(projectPath) {
|
|
61
|
+
try {
|
|
62
|
+
return fs.realpathSync(projectPath);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Path doesn't exist yet, normalize without realpath
|
|
66
|
+
return path.resolve(projectPath);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get project name from path.
|
|
71
|
+
*/
|
|
72
|
+
export function getProjectName(projectPath) {
|
|
73
|
+
return path.basename(projectPath);
|
|
74
|
+
}
|
|
75
|
+
// Resolve once at module load: both symlinked and real temp dir paths
|
|
76
|
+
const _tmpDir = tmpdir();
|
|
77
|
+
const _tmpDirResolved = (() => {
|
|
78
|
+
try {
|
|
79
|
+
return fs.realpathSync(_tmpDir);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return _tmpDir;
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
85
|
+
/**
|
|
86
|
+
* Check if a project path points to a temp directory.
|
|
87
|
+
*/
|
|
88
|
+
export function isTempDirectory(projectPath) {
|
|
89
|
+
return (projectPath.startsWith(_tmpDir + '/') ||
|
|
90
|
+
projectPath.startsWith(_tmpDirResolved + '/') ||
|
|
91
|
+
projectPath.startsWith('/tmp/') ||
|
|
92
|
+
projectPath.startsWith('/private/tmp/'));
|
|
93
|
+
}
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Language & MIME Detection
|
|
96
|
+
// ============================================================================
|
|
97
|
+
/**
|
|
98
|
+
* Get language identifier for syntax highlighting.
|
|
99
|
+
*/
|
|
100
|
+
export function getLanguageForExt(ext) {
|
|
101
|
+
const langMap = {
|
|
102
|
+
js: 'javascript', ts: 'typescript', jsx: 'javascript', tsx: 'typescript',
|
|
103
|
+
py: 'python', sh: 'bash', bash: 'bash', md: 'markdown',
|
|
104
|
+
html: 'markup', css: 'css', json: 'json', yaml: 'yaml', yml: 'yaml',
|
|
105
|
+
rs: 'rust', go: 'go', java: 'java', c: 'c', cpp: 'cpp', h: 'c',
|
|
106
|
+
};
|
|
107
|
+
return langMap[ext] || ext || 'plaintext';
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get MIME type for a file path (by extension).
|
|
111
|
+
*/
|
|
112
|
+
export function getMimeTypeForFile(filePath) {
|
|
113
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
114
|
+
const mimeTypes = {
|
|
115
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
|
|
116
|
+
gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml',
|
|
117
|
+
mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime',
|
|
118
|
+
pdf: 'application/pdf', txt: 'text/plain',
|
|
119
|
+
};
|
|
120
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
121
|
+
}
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Static File Serving
|
|
124
|
+
// ============================================================================
|
|
125
|
+
/** MIME types for static file serving */
|
|
126
|
+
export const MIME_TYPES = {
|
|
127
|
+
'.html': 'text/html',
|
|
128
|
+
'.js': 'application/javascript',
|
|
129
|
+
'.css': 'text/css',
|
|
130
|
+
'.json': 'application/json',
|
|
131
|
+
'.png': 'image/png',
|
|
132
|
+
'.jpg': 'image/jpeg',
|
|
133
|
+
'.gif': 'image/gif',
|
|
134
|
+
'.svg': 'image/svg+xml',
|
|
135
|
+
'.ico': 'image/x-icon',
|
|
136
|
+
'.woff': 'font/woff',
|
|
137
|
+
'.woff2': 'font/woff2',
|
|
138
|
+
'.ttf': 'font/ttf',
|
|
139
|
+
'.map': 'application/json',
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Serve a static file from the React dashboard dist.
|
|
143
|
+
* Returns true if the file was served, false otherwise.
|
|
144
|
+
*/
|
|
145
|
+
export function serveStaticFile(filePath, res) {
|
|
146
|
+
if (!fs.existsSync(filePath)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
const ext = path.extname(filePath);
|
|
150
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
151
|
+
try {
|
|
152
|
+
const content = fs.readFileSync(filePath);
|
|
153
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
154
|
+
res.end(content);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=tower-utils.js.map
|