Ferrell Synthetic Intelligence commited on
Commit ·
cf06504
1
Parent(s): d505970
Add safe Training Room job execution
Browse files- app.js +12 -0
- daemon/server.mjs +13 -0
- daemon/test-training-manager.mjs +15 -0
- daemon/training-manager.mjs +45 -0
- index.html +4 -0
- package.json +1 -1
app.js
CHANGED
|
@@ -281,6 +281,16 @@ async function startTool(kind, id, label) {
|
|
| 281 |
}
|
| 282 |
}
|
| 283 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
async function digestFile(file) {
|
| 285 |
const buffer = await file.arrayBuffer();
|
| 286 |
const digest = await crypto.subtle.digest('SHA-256', buffer);
|
|
@@ -345,6 +355,8 @@ async function boot() {
|
|
| 345 |
$('#community-add').onclick = addCommunityIssue;
|
| 346 |
$('#lsp-button').onclick = () => startTool('lsp', 'typescript', 'TypeScript LSP');
|
| 347 |
$('#dap-button').onclick = () => startTool('dap', 'python-debugpy', 'Python DAP');
|
|
|
|
|
|
|
| 348 |
loadCommunity();
|
| 349 |
$('#input').onkeydown = event => { if (event.key === 'Enter') sendChat(); };
|
| 350 |
}
|
|
|
|
| 281 |
}
|
| 282 |
}
|
| 283 |
|
| 284 |
+
async function trainingRequest(action, payload = {}) {
|
| 285 |
+
try {
|
| 286 |
+
const response = await fetch(`http://127.0.0.1:4777/api/training/${action}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
|
| 287 |
+
const result = await response.json();
|
| 288 |
+
if (!response.ok) throw new Error(result.error || 'Training Room request failed');
|
| 289 |
+
$('#training-status').textContent = result.status === 'running' ? `Running: ${result.id}` : `Training Room: ${result.status}`;
|
| 290 |
+
appendLog('TRAINING', JSON.stringify(result));
|
| 291 |
+
} catch (error) { $('#training-status').textContent = `Blocked: ${error.message}`; appendLog('TRAINING', error.message, 'warning'); }
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
async function digestFile(file) {
|
| 295 |
const buffer = await file.arrayBuffer();
|
| 296 |
const digest = await crypto.subtle.digest('SHA-256', buffer);
|
|
|
|
| 355 |
$('#community-add').onclick = addCommunityIssue;
|
| 356 |
$('#lsp-button').onclick = () => startTool('lsp', 'typescript', 'TypeScript LSP');
|
| 357 |
$('#dap-button').onclick = () => startTool('dap', 'python-debugpy', 'Python DAP');
|
| 358 |
+
$('#training-verify').onclick = () => trainingRequest('start', { id: 'verify-release', approved: true });
|
| 359 |
+
$('#training-stop').onclick = () => trainingRequest('stop');
|
| 360 |
loadCommunity();
|
| 361 |
$('#input').onkeydown = event => { if (event.key === 'Enter') sendChat(); };
|
| 362 |
}
|
daemon/server.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import { CommunityStore } from '../community/store.mjs';
|
|
| 7 |
import { LspManager } from './lsp-manager.mjs';
|
| 8 |
import { DapManager } from './dap-manager.mjs';
|
| 9 |
import { WorkspaceManager } from './workspace-manager.mjs';
|
|
|
|
| 10 |
|
| 11 |
const HOST = '127.0.0.1';
|
| 12 |
const PORT = Number(process.env.AIDE_DAEMON_PORT || 4777);
|
|
@@ -22,6 +23,8 @@ await lspManager.load().catch(() => {});
|
|
| 22 |
const dapManager = new DapManager({ manifestPath: path.join(WORKSPACE, 'debuggers', 'manifest.json'), workspace: WORKSPACE });
|
| 23 |
await dapManager.load().catch(() => {});
|
| 24 |
const workspaceManager = new WorkspaceManager(WORKSPACE);
|
|
|
|
|
|
|
| 25 |
|
| 26 |
function json(response, status, body) {
|
| 27 |
const payload = JSON.stringify(body);
|
|
@@ -101,6 +104,16 @@ const server = http.createServer(async (request, response) => {
|
|
| 101 |
if (request.method === 'GET' && request.url === '/api/dap/status') {
|
| 102 |
return json(response, 200, { adapters: dapManager.status() });
|
| 103 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
if (request.method === 'POST' && request.url === '/api/dap/start') {
|
| 105 |
return json(response, 200, await dapManager.start((await body(request)).id));
|
| 106 |
}
|
|
|
|
| 7 |
import { LspManager } from './lsp-manager.mjs';
|
| 8 |
import { DapManager } from './dap-manager.mjs';
|
| 9 |
import { WorkspaceManager } from './workspace-manager.mjs';
|
| 10 |
+
import { TrainingManager } from './training-manager.mjs';
|
| 11 |
|
| 12 |
const HOST = '127.0.0.1';
|
| 13 |
const PORT = Number(process.env.AIDE_DAEMON_PORT || 4777);
|
|
|
|
| 23 |
const dapManager = new DapManager({ manifestPath: path.join(WORKSPACE, 'debuggers', 'manifest.json'), workspace: WORKSPACE });
|
| 24 |
await dapManager.load().catch(() => {});
|
| 25 |
const workspaceManager = new WorkspaceManager(WORKSPACE);
|
| 26 |
+
const trainingManager = new TrainingManager({ manifestPath: path.join(WORKSPACE, 'training', 'manifest.json'), workspace: WORKSPACE });
|
| 27 |
+
await trainingManager.load().catch(() => {});
|
| 28 |
|
| 29 |
function json(response, status, body) {
|
| 30 |
const payload = JSON.stringify(body);
|
|
|
|
| 104 |
if (request.method === 'GET' && request.url === '/api/dap/status') {
|
| 105 |
return json(response, 200, { adapters: dapManager.status() });
|
| 106 |
}
|
| 107 |
+
if (request.method === 'GET' && request.url === '/api/training/status') {
|
| 108 |
+
return json(response, 200, trainingManager.status());
|
| 109 |
+
}
|
| 110 |
+
if (request.method === 'POST' && request.url === '/api/training/start') {
|
| 111 |
+
const input = await body(request);
|
| 112 |
+
return json(response, 200, trainingManager.start(input.id, input.approved));
|
| 113 |
+
}
|
| 114 |
+
if (request.method === 'POST' && request.url === '/api/training/stop') {
|
| 115 |
+
return json(response, 200, trainingManager.stop());
|
| 116 |
+
}
|
| 117 |
if (request.method === 'POST' && request.url === '/api/dap/start') {
|
| 118 |
return json(response, 200, await dapManager.start((await body(request)).id));
|
| 119 |
}
|
daemon/test-training-manager.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import assert from 'node:assert/strict';
|
| 2 |
+
import { mkdtemp, writeFile } from 'node:fs/promises';
|
| 3 |
+
import { tmpdir } from 'node:os';
|
| 4 |
+
import path from 'node:path';
|
| 5 |
+
import { TrainingManager } from './training-manager.mjs';
|
| 6 |
+
|
| 7 |
+
const root = await mkdtemp(path.join(tmpdir(), 'aide-training-'));
|
| 8 |
+
await writeFile(path.join(root, 'manifest.json'), JSON.stringify({ jobs: [{ id: 'safe', name: 'Safe', command: 'node', args: ['--version'] }] }));
|
| 9 |
+
const manager = new TrainingManager({ manifestPath: path.join(root, 'manifest.json'), workspace: root, spawnProcess: () => ({ stdout: { on() {} }, stderr: { on() {} }, once() {}, kill() {} }) });
|
| 10 |
+
await manager.load();
|
| 11 |
+
await assert.rejects(Promise.resolve().then(() => manager.start('safe', false)), /approval/);
|
| 12 |
+
assert.deepEqual(manager.start('safe', true), { id: 'safe', status: 'running' });
|
| 13 |
+
await assert.rejects(Promise.resolve().then(() => manager.start('safe', true)), /already running/);
|
| 14 |
+
assert.deepEqual(manager.stop(), { status: 'stopped' });
|
| 15 |
+
console.log('training manager test passed');
|
daemon/training-manager.mjs
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { promises as fs } from 'node:fs';
|
| 2 |
+
import { spawn } from 'node:child_process';
|
| 3 |
+
|
| 4 |
+
export class TrainingManager {
|
| 5 |
+
constructor({ manifestPath, workspace, spawnProcess = spawn } = {}) {
|
| 6 |
+
this.manifestPath = manifestPath;
|
| 7 |
+
this.workspace = workspace;
|
| 8 |
+
this.spawnProcess = spawnProcess;
|
| 9 |
+
this.jobs = new Map();
|
| 10 |
+
this.active = null;
|
| 11 |
+
this.logs = [];
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
async load() {
|
| 15 |
+
const manifest = JSON.parse(await fs.readFile(this.manifestPath, 'utf8'));
|
| 16 |
+
this.jobs = new Map(manifest.jobs.map(job => [job.id, job]));
|
| 17 |
+
return manifest;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
status() {
|
| 21 |
+
return { active: this.active ? { id: this.active.id, status: this.active.status } : null, logs: this.logs.slice(-100), jobs: [...this.jobs.values()] };
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
start(id, approved) {
|
| 25 |
+
if (approved !== true) throw new Error('explicit training approval required');
|
| 26 |
+
if (this.active) throw new Error('another Training Room job is already running');
|
| 27 |
+
const job = this.jobs.get(id);
|
| 28 |
+
if (!job) throw new Error('training job is not allowlisted');
|
| 29 |
+
const child = this.spawnProcess(job.command, job.args, { cwd: this.workspace, stdio: ['ignore', 'pipe', 'pipe'] });
|
| 30 |
+
this.active = { id, status: 'running', child };
|
| 31 |
+
const record = line => { this.logs.push({ id, line: String(line).trimEnd(), at: new Date().toISOString() }); };
|
| 32 |
+
child.stdout?.on('data', data => String(data).split('\n').filter(Boolean).forEach(record));
|
| 33 |
+
child.stderr?.on('data', data => String(data).split('\n').filter(Boolean).forEach(line => record(`stderr: ${line}`)));
|
| 34 |
+
child.once('exit', code => { record(`job exited with code ${code}`); this.active = null; });
|
| 35 |
+
return { id, status: 'running' };
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
stop() {
|
| 39 |
+
if (!this.active) return { status: 'idle' };
|
| 40 |
+
this.active.child.kill('SIGTERM');
|
| 41 |
+
this.logs.push({ id: this.active.id, line: 'job cancelled by user', at: new Date().toISOString() });
|
| 42 |
+
this.active = null;
|
| 43 |
+
return { status: 'stopped' };
|
| 44 |
+
}
|
| 45 |
+
}
|
index.html
CHANGED
|
@@ -70,6 +70,10 @@
|
|
| 70 |
<button id="lsp-button" class="secondary" style="width:100%;padding:6px">START TYPESCRIPT LSP</button>
|
| 71 |
<button id="dap-button" class="secondary" style="width:100%;margin-top:5px;padding:6px">START PYTHON DAP</button>
|
| 72 |
<div id="tool-status" style="color:#718994;font:9px/1.6 ui-monospace,monospace;margin-top:6px">LSP/DAP status unknown.</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
</aside>
|
| 74 |
|
| 75 |
<section class="editor-column">
|
|
|
|
| 70 |
<button id="lsp-button" class="secondary" style="width:100%;padding:6px">START TYPESCRIPT LSP</button>
|
| 71 |
<button id="dap-button" class="secondary" style="width:100%;margin-top:5px;padding:6px">START PYTHON DAP</button>
|
| 72 |
<div id="tool-status" style="color:#718994;font:9px/1.6 ui-monospace,monospace;margin-top:6px">LSP/DAP status unknown.</div>
|
| 73 |
+
<div class="section-heading divider">TRAINING ROOM</div>
|
| 74 |
+
<button id="training-verify" class="secondary" style="width:100%;padding:6px">RUN VERITAS JOB</button>
|
| 75 |
+
<button id="training-stop" class="secondary" style="width:100%;margin-top:5px;padding:6px">STOP TRAINING JOB</button>
|
| 76 |
+
<div id="training-status" style="color:#718994;font:9px/1.6 ui-monospace,monospace;margin-top:6px">Training Room idle.</div>
|
| 77 |
</aside>
|
| 78 |
|
| 79 |
<section class="editor-column">
|
package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
"description": "Local-first sovereign development workbench",
|
| 6 |
"type": "module",
|
| 7 |
"scripts": {
|
| 8 |
-
"test": "node tests/smoke.mjs && node harness/test-orchestrator.mjs && node daemon/test-model-manager.mjs && node daemon/test-community-store.mjs && node daemon/test-lsp-manager.mjs && node daemon/test-dap-manager.mjs && node daemon/test-workspace-manager.mjs && node benchmarks/test-run.mjs && node capsules/test-create.mjs",
|
| 9 |
"check": "node --check app.js && node --check daemon/server.mjs && node --check harness/orchestrator.mjs && node --check harness/checks.mjs",
|
| 10 |
"veritas": "node harness/run-veritas.mjs",
|
| 11 |
"benchmarks": "node benchmarks/run.mjs",
|
|
|
|
| 5 |
"description": "Local-first sovereign development workbench",
|
| 6 |
"type": "module",
|
| 7 |
"scripts": {
|
| 8 |
+
"test": "node tests/smoke.mjs && node harness/test-orchestrator.mjs && node daemon/test-model-manager.mjs && node daemon/test-community-store.mjs && node daemon/test-lsp-manager.mjs && node daemon/test-dap-manager.mjs && node daemon/test-workspace-manager.mjs && node daemon/test-training-manager.mjs && node benchmarks/test-run.mjs && node capsules/test-create.mjs",
|
| 9 |
"check": "node --check app.js && node --check daemon/server.mjs && node --check harness/orchestrator.mjs && node --check harness/checks.mjs",
|
| 10 |
"veritas": "node harness/run-veritas.mjs",
|
| 11 |
"benchmarks": "node benchmarks/run.mjs",
|