@cluesmith/codev 1.5.26 → 1.5.28
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-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +93 -7
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +2 -1
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +1 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js +1 -0
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/servers/dashboard-server.js +102 -0
- package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +37 -5
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +5 -3
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +23 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +94 -22
- package/dist/commands/doctor.js.map +1 -1
- package/dist/lib/scaffold.js +1 -1
- package/dist/lib/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/skeleton/roles/architect.md +39 -22
- package/skeleton/roles/builder.md +174 -98
- package/skeleton/templates/cheatsheet.md +170 -0
- package/skeleton/templates/lifecycle.md +147 -0
- package/templates/dashboard/css/layout.css +9 -0
- package/templates/dashboard/index.html +17 -0
- package/templates/dashboard/js/dialogs.js +86 -2
- package/templates/dashboard/js/main.js +5 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tower.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/commands/tower.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"tower.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/commands/tower.ts"],"names":[],"mappings":"AAAA;;GAEG;AAuBH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA6FD;;GAEG;AACH,wBAAsB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwF/E;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB7E;AAGD,eAAO,MAAM,KAAK,mBAAa,CAAC"}
|
|
@@ -2,14 +2,35 @@
|
|
|
2
2
|
* Tower command - launches the tower dashboard showing all instances
|
|
3
3
|
*/
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
|
-
import { existsSync } from 'node:fs';
|
|
5
|
+
import { existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
6
7
|
import net from 'node:net';
|
|
8
|
+
import http from 'node:http';
|
|
7
9
|
import { logger, fatal } from '../utils/logger.js';
|
|
8
10
|
import { spawnDetached, openBrowser } from '../utils/shell.js';
|
|
9
11
|
import { getConfig } from '../utils/config.js';
|
|
10
12
|
import { execSync } from 'node:child_process';
|
|
13
|
+
// Log file location
|
|
14
|
+
const LOG_DIR = resolve(homedir(), '.agent-farm');
|
|
15
|
+
const LOG_FILE = resolve(LOG_DIR, 'tower.log');
|
|
11
16
|
// Default port for tower dashboard
|
|
12
17
|
const DEFAULT_TOWER_PORT = 4100;
|
|
18
|
+
// Startup verification settings
|
|
19
|
+
const STARTUP_TIMEOUT_MS = 5000;
|
|
20
|
+
const STARTUP_CHECK_INTERVAL_MS = 200;
|
|
21
|
+
/**
|
|
22
|
+
* Write to the tower log file
|
|
23
|
+
*/
|
|
24
|
+
function logToFile(message) {
|
|
25
|
+
try {
|
|
26
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
27
|
+
const timestamp = new Date().toISOString();
|
|
28
|
+
appendFileSync(LOG_FILE, `[${timestamp}] ${message}\n`);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Ignore logging errors
|
|
32
|
+
}
|
|
33
|
+
}
|
|
13
34
|
/**
|
|
14
35
|
* Check if a port is already in use
|
|
15
36
|
*/
|
|
@@ -30,6 +51,41 @@ async function isPortInUse(port) {
|
|
|
30
51
|
server.listen(port, '127.0.0.1');
|
|
31
52
|
});
|
|
32
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Check if the tower server is actually responding
|
|
56
|
+
*/
|
|
57
|
+
async function isServerResponding(port) {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const req = http.request({
|
|
60
|
+
hostname: '127.0.0.1',
|
|
61
|
+
port,
|
|
62
|
+
path: '/api/status',
|
|
63
|
+
method: 'GET',
|
|
64
|
+
timeout: 2000,
|
|
65
|
+
}, (res) => {
|
|
66
|
+
resolve(res.statusCode === 200);
|
|
67
|
+
});
|
|
68
|
+
req.on('error', () => resolve(false));
|
|
69
|
+
req.on('timeout', () => {
|
|
70
|
+
req.destroy();
|
|
71
|
+
resolve(false);
|
|
72
|
+
});
|
|
73
|
+
req.end();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Wait for the server to start responding
|
|
78
|
+
*/
|
|
79
|
+
async function waitForServer(port) {
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
while (Date.now() - startTime < STARTUP_TIMEOUT_MS) {
|
|
82
|
+
if (await isServerResponding(port)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
await new Promise((r) => setTimeout(r, STARTUP_CHECK_INTERVAL_MS));
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
33
89
|
/**
|
|
34
90
|
* Get all PIDs of processes listening on a port
|
|
35
91
|
*/
|
|
@@ -51,13 +107,30 @@ function getProcessesOnPort(port) {
|
|
|
51
107
|
*/
|
|
52
108
|
export async function towerStart(options = {}) {
|
|
53
109
|
const port = options.port || DEFAULT_TOWER_PORT;
|
|
54
|
-
// Check if already running
|
|
55
|
-
if (await
|
|
110
|
+
// Check if already running and responding
|
|
111
|
+
if (await isServerResponding(port)) {
|
|
56
112
|
const dashboardUrl = `http://localhost:${port}`;
|
|
57
113
|
logger.info(`Tower already running at ${dashboardUrl}`);
|
|
58
114
|
await openBrowser(dashboardUrl);
|
|
59
115
|
return;
|
|
60
116
|
}
|
|
117
|
+
// Check if port is in use but not responding (zombie process?)
|
|
118
|
+
if (await isPortInUse(port)) {
|
|
119
|
+
logger.warn(`Port ${port} is in use but tower not responding. Attempting cleanup...`);
|
|
120
|
+
logToFile(`Port ${port} in use but not responding, attempting cleanup`);
|
|
121
|
+
const pids = getProcessesOnPort(port);
|
|
122
|
+
for (const pid of pids) {
|
|
123
|
+
try {
|
|
124
|
+
process.kill(pid, 'SIGTERM');
|
|
125
|
+
logToFile(`Killed process ${pid} on port ${port}`);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Process may have already exited
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Wait for port to be released
|
|
132
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
133
|
+
}
|
|
61
134
|
const config = getConfig();
|
|
62
135
|
// Find tower server script
|
|
63
136
|
const tsScript = resolve(config.serversDir, 'tower-server.ts');
|
|
@@ -67,28 +140,41 @@ export async function towerStart(options = {}) {
|
|
|
67
140
|
if (existsSync(tsScript)) {
|
|
68
141
|
// Dev mode: run with tsx
|
|
69
142
|
command = 'npx';
|
|
70
|
-
args = ['tsx', tsScript, String(port)];
|
|
143
|
+
args = ['tsx', tsScript, String(port), '--log-file', LOG_FILE];
|
|
71
144
|
}
|
|
72
145
|
else if (existsSync(jsScript)) {
|
|
73
146
|
// Prod mode: run compiled JS
|
|
74
147
|
command = 'node';
|
|
75
|
-
args = [jsScript, String(port)];
|
|
148
|
+
args = [jsScript, String(port), '--log-file', LOG_FILE];
|
|
76
149
|
}
|
|
77
150
|
else {
|
|
78
151
|
fatal('Tower server not found');
|
|
79
152
|
}
|
|
80
153
|
logger.header('Starting Tower');
|
|
81
154
|
logger.kv('Port', port);
|
|
155
|
+
logger.kv('Log file', LOG_FILE);
|
|
156
|
+
logToFile(`Starting tower server on port ${port}`);
|
|
157
|
+
logToFile(`Command: ${command} ${args.join(' ')}`);
|
|
82
158
|
// Start tower server
|
|
83
159
|
const serverProcess = spawnDetached(command, args, {
|
|
84
160
|
cwd: process.cwd(),
|
|
85
161
|
});
|
|
86
162
|
if (!serverProcess.pid) {
|
|
163
|
+
logToFile('Failed to spawn tower server process');
|
|
87
164
|
fatal('Failed to start tower server');
|
|
88
165
|
}
|
|
89
|
-
|
|
90
|
-
|
|
166
|
+
logToFile(`Spawned tower server with PID ${serverProcess.pid}`);
|
|
167
|
+
// Wait for server to actually start responding
|
|
168
|
+
logger.info('Waiting for server to start...');
|
|
169
|
+
const started = await waitForServer(port);
|
|
170
|
+
if (!started) {
|
|
171
|
+
logToFile(`Tower server failed to respond within ${STARTUP_TIMEOUT_MS}ms`);
|
|
172
|
+
logger.error(`Tower server failed to start within ${STARTUP_TIMEOUT_MS / 1000}s`);
|
|
173
|
+
logger.error(`Check logs at: ${LOG_FILE}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
91
176
|
const dashboardUrl = `http://localhost:${port}`;
|
|
177
|
+
logToFile(`Tower server started successfully at ${dashboardUrl}`);
|
|
92
178
|
logger.blank();
|
|
93
179
|
logger.success('Tower started!');
|
|
94
180
|
logger.kv('Dashboard', dashboardUrl);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tower.js","sourceRoot":"","sources":["../../../src/agent-farm/commands/tower.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"tower.js","sourceRoot":"","sources":["../../../src/agent-farm/commands/tower.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Local state schema (state.db)
|
|
8
8
|
* Stores dashboard state: architect, builders, utils, annotations
|
|
9
9
|
*/
|
|
10
|
-
export declare const LOCAL_SCHEMA = "\n-- Schema versioning\nCREATE TABLE IF NOT EXISTS _migrations (\n version INTEGER PRIMARY KEY,\n applied_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Architect session (singleton)\nCREATE TABLE IF NOT EXISTS architect (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n pid INTEGER NOT NULL,\n port INTEGER NOT NULL,\n cmd TEXT NOT NULL,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n tmux_session TEXT\n);\n\n-- Builder sessions\nCREATE TABLE IF NOT EXISTS builders (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n port INTEGER NOT NULL UNIQUE,\n pid INTEGER NOT NULL,\n status TEXT NOT NULL DEFAULT 'spawning'\n CHECK(status IN ('spawning', 'implementing', 'blocked', 'pr-ready', 'complete')),\n phase TEXT NOT NULL DEFAULT '',\n worktree TEXT NOT NULL,\n branch TEXT NOT NULL,\n tmux_session TEXT,\n type TEXT NOT NULL DEFAULT 'spec'\n CHECK(type IN ('spec', 'task', 'protocol', 'shell', 'worktree')),\n task_text TEXT,\n protocol_name TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Indexes for common queries\nCREATE INDEX IF NOT EXISTS idx_builders_status ON builders(status);\nCREATE INDEX IF NOT EXISTS idx_builders_port ON builders(port);\n\n-- Utility terminals\nCREATE TABLE IF NOT EXISTS utils (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n port INTEGER NOT NULL UNIQUE,\n pid INTEGER NOT NULL,\n tmux_session TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Annotations (file viewers)\nCREATE TABLE IF NOT EXISTS annotations (\n id TEXT PRIMARY KEY,\n file TEXT NOT NULL,\n port INTEGER NOT NULL UNIQUE,\n pid INTEGER NOT NULL,\n parent_type TEXT NOT NULL CHECK(parent_type IN ('architect', 'builder', 'util')),\n parent_id TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Trigger to update updated_at on builders\nCREATE TRIGGER IF NOT EXISTS builders_updated_at\n AFTER UPDATE ON builders\n FOR EACH ROW\n BEGIN\n UPDATE builders SET updated_at = datetime('now') WHERE id = NEW.id;\n END;\n";
|
|
10
|
+
export declare const LOCAL_SCHEMA = "\n-- Schema versioning\nCREATE TABLE IF NOT EXISTS _migrations (\n version INTEGER PRIMARY KEY,\n applied_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Architect session (singleton)\nCREATE TABLE IF NOT EXISTS architect (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n pid INTEGER NOT NULL,\n port INTEGER NOT NULL,\n cmd TEXT NOT NULL,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n tmux_session TEXT\n);\n\n-- Builder sessions\nCREATE TABLE IF NOT EXISTS builders (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n port INTEGER NOT NULL UNIQUE,\n pid INTEGER NOT NULL,\n status TEXT NOT NULL DEFAULT 'spawning'\n CHECK(status IN ('spawning', 'implementing', 'blocked', 'pr-ready', 'complete')),\n phase TEXT NOT NULL DEFAULT '',\n worktree TEXT NOT NULL,\n branch TEXT NOT NULL,\n tmux_session TEXT,\n type TEXT NOT NULL DEFAULT 'spec'\n CHECK(type IN ('spec', 'task', 'protocol', 'shell', 'worktree', 'bugfix')),\n task_text TEXT,\n protocol_name TEXT,\n issue_number INTEGER,\n started_at TEXT NOT NULL DEFAULT (datetime('now')),\n updated_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Indexes for common queries\nCREATE INDEX IF NOT EXISTS idx_builders_status ON builders(status);\nCREATE INDEX IF NOT EXISTS idx_builders_port ON builders(port);\n\n-- Utility terminals\nCREATE TABLE IF NOT EXISTS utils (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n port INTEGER NOT NULL UNIQUE,\n pid INTEGER NOT NULL,\n tmux_session TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Annotations (file viewers)\nCREATE TABLE IF NOT EXISTS annotations (\n id TEXT PRIMARY KEY,\n file TEXT NOT NULL,\n port INTEGER NOT NULL UNIQUE,\n pid INTEGER NOT NULL,\n parent_type TEXT NOT NULL CHECK(parent_type IN ('architect', 'builder', 'util')),\n parent_id TEXT,\n started_at TEXT NOT NULL DEFAULT (datetime('now'))\n);\n\n-- Trigger to update updated_at on builders\nCREATE TRIGGER IF NOT EXISTS builders_updated_at\n AFTER UPDATE ON builders\n FOR EACH ROW\n BEGIN\n UPDATE builders SET updated_at = datetime('now') WHERE id = NEW.id;\n END;\n";
|
|
11
11
|
/**
|
|
12
12
|
* Global registry schema (global.db)
|
|
13
13
|
* Stores port allocations across all projects
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/db/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/db/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,YAAY,ykEAsExB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,wkBAkBzB,CAAC"}
|
|
@@ -37,9 +37,10 @@ CREATE TABLE IF NOT EXISTS builders (
|
|
|
37
37
|
branch TEXT NOT NULL,
|
|
38
38
|
tmux_session TEXT,
|
|
39
39
|
type TEXT NOT NULL DEFAULT 'spec'
|
|
40
|
-
CHECK(type IN ('spec', 'task', 'protocol', 'shell', 'worktree')),
|
|
40
|
+
CHECK(type IN ('spec', 'task', 'protocol', 'shell', 'worktree', 'bugfix')),
|
|
41
41
|
task_text TEXT,
|
|
42
42
|
protocol_name TEXT,
|
|
43
|
+
issue_number INTEGER,
|
|
43
44
|
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
44
45
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
45
46
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/agent-farm/db/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/agent-farm/db/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsE3B,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;CAkB5B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/db/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AAElG;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAQ5E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/db/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AAElG;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,WAAW,GAAG,cAAc,CAQ5E;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAgB1D;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAQ9D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,YAAY,GAAG,UAAU,CAWtE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/agent-farm/db/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/agent-farm/db/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyEH;;GAEG;AACH,MAAM,UAAU,2BAA2B,CAAC,GAAgB;IAC1D,OAAO;QACL,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,WAAW,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;KAC3C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAc;IAC/C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,MAAM,EAAE,GAAG,CAAC,MAA2B;QACvC,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,WAAW,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;QAC1C,IAAI,EAAE,GAAG,CAAC,IAAmB;QAC7B,QAAQ,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;QACpC,YAAY,EAAE,GAAG,CAAC,aAAa,IAAI,SAAS;QAC5C,WAAW,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;KAC3C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,WAAW,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;KAC3C,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAAiB;IACxD,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,MAAM,EAAE;YACN,IAAI,EAAE,GAAG,CAAC,WAA2C;YACrD,EAAE,EAAE,GAAG,CAAC,SAAS,IAAI,SAAS;SAC/B;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1210,6 +1210,46 @@ const server = http.createServer(async (req, res) => {
|
|
|
1210
1210
|
res.end(JSON.stringify({ success: true, id, port: utilPort, name: utilName }));
|
|
1211
1211
|
return;
|
|
1212
1212
|
}
|
|
1213
|
+
// API: Check if tab process is running (Bugfix #132)
|
|
1214
|
+
if (req.method === 'GET' && url.pathname.match(/^\/api\/tabs\/[^/]+\/running$/)) {
|
|
1215
|
+
const match = url.pathname.match(/^\/api\/tabs\/([^/]+)\/running$/);
|
|
1216
|
+
if (!match) {
|
|
1217
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
1218
|
+
res.end('Invalid tab ID');
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
const tabId = decodeURIComponent(match[1]);
|
|
1222
|
+
let running = false;
|
|
1223
|
+
let found = false;
|
|
1224
|
+
// Check if it's a shell tab
|
|
1225
|
+
if (tabId.startsWith('shell-')) {
|
|
1226
|
+
const utilId = tabId.replace('shell-', '');
|
|
1227
|
+
const tabUtils = getUtils();
|
|
1228
|
+
const util = tabUtils.find((u) => u.id === utilId);
|
|
1229
|
+
if (util) {
|
|
1230
|
+
found = true;
|
|
1231
|
+
running = isProcessRunning(util.pid);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
// Check if it's a builder tab
|
|
1235
|
+
if (tabId.startsWith('builder-')) {
|
|
1236
|
+
const builderId = tabId.replace('builder-', '');
|
|
1237
|
+
const builder = getBuilder(builderId);
|
|
1238
|
+
if (builder) {
|
|
1239
|
+
found = true;
|
|
1240
|
+
running = isProcessRunning(builder.pid);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (found) {
|
|
1244
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1245
|
+
res.end(JSON.stringify({ running }));
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1249
|
+
res.end(JSON.stringify({ running: false }));
|
|
1250
|
+
}
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1213
1253
|
// API: Close tab
|
|
1214
1254
|
if (req.method === 'DELETE' && url.pathname.startsWith('/api/tabs/')) {
|
|
1215
1255
|
const tabId = decodeURIComponent(url.pathname.replace('/api/tabs/', ''));
|
|
@@ -1512,6 +1552,68 @@ const server = http.createServer(async (req, res) => {
|
|
|
1512
1552
|
res.end(JSON.stringify(tree));
|
|
1513
1553
|
return;
|
|
1514
1554
|
}
|
|
1555
|
+
// API: Create a new file (Bugfix #131)
|
|
1556
|
+
if (req.method === 'POST' && url.pathname === '/api/files') {
|
|
1557
|
+
const body = await parseJsonBody(req);
|
|
1558
|
+
const filePath = body.path;
|
|
1559
|
+
const content = body.content || '';
|
|
1560
|
+
if (!filePath) {
|
|
1561
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1562
|
+
res.end(JSON.stringify({ error: 'Missing path' }));
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
// Validate path is within project root (prevent path traversal)
|
|
1566
|
+
const fullPath = validatePathWithinProject(filePath);
|
|
1567
|
+
if (!fullPath) {
|
|
1568
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
1569
|
+
res.end(JSON.stringify({ error: 'Path must be within project directory' }));
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
// Check if file already exists
|
|
1573
|
+
if (fs.existsSync(fullPath)) {
|
|
1574
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
1575
|
+
res.end(JSON.stringify({ error: 'File already exists' }));
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
// Additional security: validate parent directories don't symlink outside project
|
|
1579
|
+
// Find the deepest existing parent and ensure it's within project
|
|
1580
|
+
let checkDir = path.dirname(fullPath);
|
|
1581
|
+
while (checkDir !== projectRoot && !fs.existsSync(checkDir)) {
|
|
1582
|
+
checkDir = path.dirname(checkDir);
|
|
1583
|
+
}
|
|
1584
|
+
if (fs.existsSync(checkDir) && checkDir !== projectRoot) {
|
|
1585
|
+
try {
|
|
1586
|
+
const realParent = fs.realpathSync(checkDir);
|
|
1587
|
+
if (!realParent.startsWith(projectRoot + path.sep) && realParent !== projectRoot) {
|
|
1588
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
1589
|
+
res.end(JSON.stringify({ error: 'Path must be within project directory' }));
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
catch {
|
|
1594
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
1595
|
+
res.end(JSON.stringify({ error: 'Cannot resolve path' }));
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
try {
|
|
1600
|
+
// Create parent directories if they don't exist
|
|
1601
|
+
const parentDir = path.dirname(fullPath);
|
|
1602
|
+
if (!fs.existsSync(parentDir)) {
|
|
1603
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
1604
|
+
}
|
|
1605
|
+
// Write the file
|
|
1606
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
1607
|
+
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
1608
|
+
res.end(JSON.stringify({ success: true, path: filePath }));
|
|
1609
|
+
}
|
|
1610
|
+
catch (err) {
|
|
1611
|
+
console.error('Error creating file:', err.message);
|
|
1612
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1613
|
+
res.end(JSON.stringify({ error: 'Failed to create file: ' + err.message }));
|
|
1614
|
+
}
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1515
1617
|
// API: Get daily activity summary (Spec 0059)
|
|
1516
1618
|
if (req.method === 'GET' && url.pathname === '/api/activity-summary') {
|
|
1517
1619
|
try {
|