Ferrell Synthetic Intelligence commited on
Commit
cf06504
·
1 Parent(s): d505970

Add safe Training Room job execution

Browse files
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",