4runr-os 1.0.15 → 1.0.22
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/animations.d.ts +26 -0
- package/dist/animations.d.ts.map +1 -0
- package/dist/animations.js +111 -0
- package/dist/animations.js.map +1 -0
- package/dist/copy.d.ts +62 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +70 -0
- package/dist/copy.js.map +1 -0
- package/dist/index.js +697 -265
- package/dist/index.js.map +1 -1
- package/dist/state.d.ts +28 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +87 -0
- package/dist/state.js.map +1 -0
- package/dist/store.d.ts +69 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +86 -0
- package/dist/store.js.map +1 -0
- package/dist/theme.d.ts +21 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +31 -0
- package/dist/theme.js.map +1 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/home-view.d.ts +27 -0
- package/dist/ui/home-view.d.ts.map +1 -0
- package/dist/ui/home-view.js +127 -0
- package/dist/ui/home-view.js.map +1 -0
- package/dist/ui-primitives.d.ts +53 -0
- package/dist/ui-primitives.d.ts.map +1 -0
- package/dist/ui-primitives.js +156 -0
- package/dist/ui-primitives.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +35 -0
- package/dist/utils.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,39 +11,55 @@ import { GatewayClient } from './gateway-client.js';
|
|
|
11
11
|
import { executeLocalModel, verifyLocalModelServer } from './local-model-executor.js';
|
|
12
12
|
import { checkOllamaInstallation, installOllama, startOllamaServer, downloadModel, verifyLocalExecution } from './local-setup.js';
|
|
13
13
|
import { loadSession, clearSession, createSession, isSessionValid, getSessionTimeRemaining, getUserAgentsPath, getUserToolsPath } from './auth.js';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
const { cyan, green, yellow, blue, magenta, red, gray, bright, reset, dim, brightGreen, brightCyan, brightMagenta, purpleRGB, purpleBright, purpleDim, pink, orange, teal } = colors;
|
|
14
|
+
import { C_BRAND, C_ACCENT, C_TEXT, C_MUTED, C_SUCCESS, C_WARN, C_ERROR, C_DIM, C_LINK, C_BRAND_BRIGHT, C_ACCENT_BRIGHT, C_TEXT_BRIGHT, RESET, BOLD, } from './theme.js';
|
|
15
|
+
import { Header, StatusBar, Section, Table, Alert, truncateId, truncateText, } from './ui-primitives.js';
|
|
16
|
+
import { getLastAgent, setLastAgent, setLastConnectionTarget, setLastMode, } from './state.js';
|
|
17
|
+
import { Copy } from './copy.js';
|
|
18
|
+
import { createProgress, stopAllAnimations } from './animations.js';
|
|
19
|
+
import { getState, updateAppState } from './store.js';
|
|
20
|
+
import { renderHomeView } from './ui/home-view.js';
|
|
21
|
+
// Legacy color aliases for backward compatibility during refactor
|
|
22
|
+
const cyan = C_ACCENT;
|
|
23
|
+
const green = C_SUCCESS;
|
|
24
|
+
const yellow = C_WARN;
|
|
25
|
+
const red = C_ERROR;
|
|
26
|
+
const gray = C_MUTED;
|
|
27
|
+
const bright = BOLD;
|
|
28
|
+
const reset = RESET;
|
|
29
|
+
const dim = C_DIM;
|
|
30
|
+
const brightGreen = C_SUCCESS + BOLD;
|
|
31
|
+
const brightCyan = C_ACCENT_BRIGHT;
|
|
32
|
+
const purpleRGB = C_BRAND;
|
|
33
|
+
const purpleBright = C_BRAND_BRIGHT;
|
|
34
|
+
const teal = C_ACCENT;
|
|
35
|
+
const magenta = C_BRAND;
|
|
36
|
+
const blue = C_ACCENT;
|
|
37
|
+
let currentView = 'BOOT';
|
|
38
|
+
// Legacy globals (being migrated to state store)
|
|
41
39
|
let client = null;
|
|
42
40
|
let systemReady = false;
|
|
43
41
|
let gatewayConnected = false;
|
|
44
42
|
let localMode = false; // Run without gateway (local-only)
|
|
45
43
|
let currentUser = null; // Current logged-in user
|
|
46
44
|
let sessionCheckInterval = null; // Session expiry checker
|
|
45
|
+
/**
|
|
46
|
+
* Sync legacy globals to state store
|
|
47
|
+
* Call this after any global state change to keep store in sync
|
|
48
|
+
* TODO: Remove this once all code uses state store directly
|
|
49
|
+
*/
|
|
50
|
+
function syncStateToStore() {
|
|
51
|
+
updateAppState({
|
|
52
|
+
currentView,
|
|
53
|
+
gatewayClient: client,
|
|
54
|
+
gatewayConnected,
|
|
55
|
+
gatewayUrl: gatewayUrl || null,
|
|
56
|
+
localMode,
|
|
57
|
+
currentUser,
|
|
58
|
+
sessionCheckInterval,
|
|
59
|
+
systemReady,
|
|
60
|
+
// Note: customAgents and runHistory are synced separately when they change
|
|
61
|
+
});
|
|
62
|
+
}
|
|
47
63
|
// Gateway URL - configurable via GATEWAY_URL environment variable or config file
|
|
48
64
|
// Default: Official 4Runr server (but connection requires login)
|
|
49
65
|
// Override: Set GATEWAY_URL environment variable
|
|
@@ -64,7 +80,10 @@ let multilineMode = false;
|
|
|
64
80
|
let multilineResolver = null;
|
|
65
81
|
let multilineLines = [];
|
|
66
82
|
let multilineEndMarker = 'END';
|
|
83
|
+
// Custom agents storage
|
|
84
|
+
// NOTE: Using types from types.ts now
|
|
67
85
|
const customAgents = new Map();
|
|
86
|
+
// Custom tools storage
|
|
68
87
|
const customTools = new Map();
|
|
69
88
|
// Get config directory path
|
|
70
89
|
function getConfigDir() {
|
|
@@ -149,122 +168,128 @@ const aiConfig = {
|
|
|
149
168
|
model: null,
|
|
150
169
|
apiKey: null
|
|
151
170
|
};
|
|
152
|
-
//
|
|
171
|
+
// Track if this is first run completion (for brand moment)
|
|
172
|
+
let firstRunCompleted = false;
|
|
173
|
+
// BOOT: Sequential, deterministic, never fails hard (max 2s animation)
|
|
153
174
|
async function bootSequence() {
|
|
154
175
|
console.clear();
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
currentView = 'BOOT';
|
|
177
|
+
const bootStartTime = Date.now();
|
|
178
|
+
// 1. Header - render immediately, no delays
|
|
179
|
+
const mode = localMode ? 'LOCAL' : 'REMOTE';
|
|
180
|
+
console.log(Header('2.0.0', mode));
|
|
181
|
+
console.log();
|
|
182
|
+
// 2. Boot sequence messages (sequential, calm, max 2s total)
|
|
183
|
+
const checkResults = [];
|
|
184
|
+
// Step 1: Initializing runtime
|
|
185
|
+
const progress1 = createProgress(Copy.boot.initializing, 200);
|
|
186
|
+
progress1.start();
|
|
187
|
+
await sleep(150);
|
|
188
|
+
progress1.stop(true);
|
|
189
|
+
checkResults.push({ label: 'Runtime', status: 'OK' });
|
|
190
|
+
// Step 2: Loading configuration
|
|
191
|
+
const progress2 = createProgress(Copy.boot.loadingConfig, 200);
|
|
192
|
+
progress2.start();
|
|
193
|
+
await sleep(150);
|
|
169
194
|
const savedSession = loadSession();
|
|
170
|
-
|
|
195
|
+
const configLoaded = savedSession && isSessionValid(savedSession);
|
|
196
|
+
if (configLoaded) {
|
|
171
197
|
currentUser = savedSession;
|
|
172
|
-
console.log(`${dim}[BOOT] Session: ${green}${savedSession.username}${reset}${dim} (${getSessionTimeRemaining(savedSession)}m)${reset}`);
|
|
173
|
-
// Try to reconnect to gateway and verify components
|
|
174
|
-
if (!localMode && (savedSession.gatewayUrl || gatewayUrl)) {
|
|
175
|
-
const urlToUse = savedSession.gatewayUrl || gatewayUrl;
|
|
176
|
-
console.log(`${purpleRGB}Connecting...${reset}`);
|
|
177
|
-
try {
|
|
178
|
-
client = new GatewayClient({ gatewayUrl: urlToUse });
|
|
179
|
-
const health = await client.health();
|
|
180
|
-
gatewayConnected = true;
|
|
181
|
-
gatewayUrl = urlToUse;
|
|
182
|
-
// Clean component verification
|
|
183
|
-
console.log(`${dim}Verifying components...${reset}\n`);
|
|
184
|
-
// Gateway
|
|
185
|
-
process.stdout.write(`${cyan}Gateway${reset}${gray} ${reset}`);
|
|
186
|
-
await sleep(80);
|
|
187
|
-
console.log(`${green}✓${reset} ${dim}${health.status}${reset}`);
|
|
188
|
-
// Database
|
|
189
|
-
process.stdout.write(`${cyan}Database${reset}${gray} ${reset}`);
|
|
190
|
-
await sleep(80);
|
|
191
|
-
console.log(`${green}✓${reset} ${dim}${health.persistence}${reset}`);
|
|
192
|
-
// Sentinel
|
|
193
|
-
process.stdout.write(`${cyan}Sentinel${reset}${gray} ${reset}`);
|
|
194
|
-
await sleep(80);
|
|
195
|
-
try {
|
|
196
|
-
const sentinelRes = await fetch(`${gatewayUrl}/sentinel/health`);
|
|
197
|
-
if (sentinelRes.ok) {
|
|
198
|
-
const sentinel = await sentinelRes.json();
|
|
199
|
-
console.log(`${green}✓${reset} ${dim}${sentinel?.status || 'active'}${reset}`);
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
console.log(`${yellow}⚠${reset} ${dim}offline${reset}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
console.log(`${yellow}⚠${reset} ${dim}offline${reset}`);
|
|
207
|
-
}
|
|
208
|
-
// Metrics
|
|
209
|
-
process.stdout.write(`${cyan}Metrics${reset}${gray} ${reset}`);
|
|
210
|
-
await sleep(80);
|
|
211
|
-
try {
|
|
212
|
-
const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
|
|
213
|
-
if (metricsRes.ok) {
|
|
214
|
-
console.log(`${green}✓${reset}`);
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
console.log(`${yellow}⚠${reset}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
console.log(`${yellow}⚠${reset}`);
|
|
222
|
-
}
|
|
223
|
-
// AI Providers
|
|
224
|
-
process.stdout.write(`${cyan}AI Providers${reset}${gray} ${reset}`);
|
|
225
|
-
await sleep(80);
|
|
226
|
-
console.log(`${green}✓${reset} ${dim}ready${reset}\n`);
|
|
227
|
-
}
|
|
228
|
-
catch (error) {
|
|
229
|
-
console.log(`${yellow}✗${reset} ${dim}(mainframe unavailable)${reset}`);
|
|
230
|
-
gatewayConnected = false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
198
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
199
|
+
progress2.stop(!!configLoaded);
|
|
200
|
+
checkResults.push({ label: 'Configuration', status: configLoaded ? 'OK' : 'WARN' });
|
|
201
|
+
// Step 3: Checking gateway
|
|
202
|
+
const progress3 = createProgress(Copy.boot.checkingGateway, 300);
|
|
203
|
+
progress3.start();
|
|
204
|
+
await sleep(200);
|
|
205
|
+
let gatewayStatus = 'WARN';
|
|
206
|
+
if (!localMode && (savedSession?.gatewayUrl || gatewayUrl)) {
|
|
207
|
+
const urlToUse = savedSession?.gatewayUrl || gatewayUrl;
|
|
208
|
+
try {
|
|
209
|
+
client = new GatewayClient({ gatewayUrl: urlToUse });
|
|
210
|
+
const health = await client.health();
|
|
211
|
+
gatewayConnected = true;
|
|
212
|
+
gatewayUrl = urlToUse;
|
|
213
|
+
gatewayStatus = 'OK';
|
|
214
|
+
setLastConnectionTarget(urlToUse);
|
|
238
215
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
console.log(`${dim}Or run with ${purpleBright}--local${reset}${dim} for offline mode${reset}\n`);
|
|
216
|
+
catch {
|
|
217
|
+
gatewayStatus = 'WARN';
|
|
218
|
+
gatewayConnected = false;
|
|
243
219
|
}
|
|
244
220
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
221
|
+
else if (localMode) {
|
|
222
|
+
gatewayStatus = 'OK';
|
|
223
|
+
}
|
|
224
|
+
progress3.stop(gatewayStatus === 'OK');
|
|
225
|
+
checkResults.push({ label: 'Gateway', status: gatewayStatus });
|
|
226
|
+
// Step 4: Checking database
|
|
227
|
+
const progress4 = createProgress(Copy.boot.checkingDatabase, 200);
|
|
228
|
+
progress4.start();
|
|
229
|
+
await sleep(150);
|
|
230
|
+
let dbStatus = 'WARN';
|
|
231
|
+
if (gatewayConnected && client) {
|
|
232
|
+
try {
|
|
233
|
+
const health = await client.health();
|
|
234
|
+
dbStatus = health.persistence === 'enabled' ? 'OK' : 'WARN';
|
|
251
235
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
console.log(`${dim}Remote features unavailable${reset}\n`);
|
|
236
|
+
catch {
|
|
237
|
+
dbStatus = 'WARN';
|
|
255
238
|
}
|
|
256
239
|
}
|
|
257
|
-
else if (
|
|
258
|
-
|
|
240
|
+
else if (localMode) {
|
|
241
|
+
dbStatus = 'OK'; // Local mode doesn't need DB
|
|
242
|
+
}
|
|
243
|
+
progress4.stop(dbStatus === 'OK');
|
|
244
|
+
checkResults.push({ label: 'Database', status: dbStatus });
|
|
245
|
+
// Step 5: Loading agent registry
|
|
246
|
+
const progress5 = createProgress(Copy.boot.loadingAgents, 200);
|
|
247
|
+
progress5.start();
|
|
248
|
+
await sleep(150);
|
|
249
|
+
loadCustomAgents();
|
|
250
|
+
const agentsLoaded = customAgents.size > 0;
|
|
251
|
+
progress5.stop(agentsLoaded);
|
|
252
|
+
checkResults.push({ label: 'Agent registry', status: agentsLoaded ? 'OK' : 'WARN' });
|
|
253
|
+
// Ensure total boot time ≤ 2s
|
|
254
|
+
const elapsed = Date.now() - bootStartTime;
|
|
255
|
+
if (elapsed < 2000) {
|
|
256
|
+
await sleep(2000 - elapsed);
|
|
257
|
+
}
|
|
258
|
+
// Summary
|
|
259
|
+
const checkLines = [];
|
|
260
|
+
checkResults.forEach(result => {
|
|
261
|
+
const statusColor = result.status === 'OK' ? C_SUCCESS :
|
|
262
|
+
result.status === 'WARN' ? C_WARN : C_ERROR;
|
|
263
|
+
const statusText = result.status === 'OK' ? 'OK' :
|
|
264
|
+
result.status === 'WARN' ? 'WARN' : 'ERROR';
|
|
265
|
+
checkLines.push(`${result.label}: ${statusColor}${statusText}${RESET}`);
|
|
266
|
+
});
|
|
267
|
+
console.log(Section('INITIALIZATION', checkLines));
|
|
268
|
+
console.log();
|
|
269
|
+
// Final status (ALL CAPS for system state)
|
|
270
|
+
let finalStatus;
|
|
271
|
+
if (gatewayStatus === 'OK' && gatewayConnected) {
|
|
272
|
+
finalStatus = `${C_SUCCESS}${Copy.status.ready}${RESET}`;
|
|
273
|
+
// Brand moment: boot complete
|
|
274
|
+
console.log(`${C_MUTED}${Copy.brand.bootComplete}${RESET}`);
|
|
275
|
+
}
|
|
276
|
+
else if (localMode) {
|
|
277
|
+
finalStatus = `${C_SUCCESS}${Copy.status.offlineMode}${RESET}`;
|
|
259
278
|
}
|
|
260
279
|
else {
|
|
261
|
-
|
|
280
|
+
finalStatus = `${C_WARN}${Copy.status.notConnected}${RESET}`;
|
|
262
281
|
}
|
|
282
|
+
console.log(finalStatus);
|
|
263
283
|
systemReady = true;
|
|
264
284
|
// Start session expiry checker
|
|
265
285
|
if (currentUser && !localMode) {
|
|
266
286
|
startSessionChecker();
|
|
267
287
|
}
|
|
288
|
+
// Save mode to state
|
|
289
|
+
setLastMode(localMode ? 'LOCAL' : 'REMOTE');
|
|
290
|
+
// Auto transition to HOME
|
|
291
|
+
await sleep(200);
|
|
292
|
+
await showHomeView();
|
|
268
293
|
}
|
|
269
294
|
/**
|
|
270
295
|
* Start session expiry checker
|
|
@@ -308,14 +333,103 @@ async function progress(items) {
|
|
|
308
333
|
function sleep(ms) {
|
|
309
334
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
310
335
|
}
|
|
311
|
-
// Show
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
console.log(
|
|
318
|
-
console.log(
|
|
336
|
+
// Helper: Show agent config (read-only)
|
|
337
|
+
async function showAgentConfig(agent) {
|
|
338
|
+
// 2. AGENT CONFIG (Read-Only)
|
|
339
|
+
const identityLines = [];
|
|
340
|
+
identityLines.push(`Name: ${C_TEXT}${agent.name}${RESET}`);
|
|
341
|
+
identityLines.push(`Description: ${C_MUTED}${agent.description || 'No description'}${RESET}`);
|
|
342
|
+
console.log(Section('IDENTITY', identityLines));
|
|
343
|
+
console.log();
|
|
344
|
+
const modelLines = [];
|
|
345
|
+
const modelShort = agent.baseAgent.includes(':')
|
|
346
|
+
? agent.baseAgent.split(':').pop() || 'unknown'
|
|
347
|
+
: agent.baseAgent;
|
|
348
|
+
modelLines.push(`Model: ${C_ACCENT}${modelShort}${RESET}`);
|
|
349
|
+
if (agent.temperature !== undefined) {
|
|
350
|
+
modelLines.push(`Temperature: ${C_ACCENT}${agent.temperature}${RESET}`);
|
|
351
|
+
}
|
|
352
|
+
console.log(Section('MODEL', modelLines));
|
|
353
|
+
console.log();
|
|
354
|
+
const toolsLines = [];
|
|
355
|
+
if (agent.tools && agent.tools.length > 0) {
|
|
356
|
+
toolsLines.push(`Enabled: ${C_ACCENT}${agent.tools.length}${RESET}`);
|
|
357
|
+
agent.tools.forEach(tool => {
|
|
358
|
+
toolsLines.push(`${C_TEXT}${tool}${RESET}`);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
toolsLines.push(`${C_MUTED}No tools enabled${RESET}`);
|
|
363
|
+
}
|
|
364
|
+
console.log(Section('TOOLS', toolsLines));
|
|
365
|
+
console.log();
|
|
366
|
+
const memoryLines = [];
|
|
367
|
+
memoryLines.push(`Memory type: ${C_ACCENT}NONE${RESET} ${C_MUTED}(not implemented)${RESET}`);
|
|
368
|
+
console.log(Section('MEMORY', memoryLines));
|
|
369
|
+
console.log();
|
|
370
|
+
// Tags (if we had them)
|
|
371
|
+
const tagsLines = [];
|
|
372
|
+
tagsLines.push(`${C_MUTED}No tags${RESET}`);
|
|
373
|
+
console.log(Section('TAGS', tagsLines));
|
|
374
|
+
console.log();
|
|
375
|
+
}
|
|
376
|
+
// Track boot time for uptime calculation
|
|
377
|
+
const bootTime = Date.now();
|
|
378
|
+
const runHistory = [];
|
|
379
|
+
function formatRelativeTime(timestamp) {
|
|
380
|
+
const seconds = Math.floor((Date.now() - timestamp) / 1000);
|
|
381
|
+
if (seconds < 60)
|
|
382
|
+
return `${seconds}s ago`;
|
|
383
|
+
const minutes = Math.floor(seconds / 60);
|
|
384
|
+
if (minutes < 60)
|
|
385
|
+
return `${minutes}m ago`;
|
|
386
|
+
const hours = Math.floor(minutes / 60);
|
|
387
|
+
if (hours < 24)
|
|
388
|
+
return `${hours}h ago`;
|
|
389
|
+
const days = Math.floor(hours / 24);
|
|
390
|
+
return `${days}d ago`;
|
|
391
|
+
}
|
|
392
|
+
function formatUptime() {
|
|
393
|
+
const seconds = Math.floor((Date.now() - bootTime) / 1000);
|
|
394
|
+
if (seconds < 60)
|
|
395
|
+
return `${seconds}s`;
|
|
396
|
+
const minutes = Math.floor(seconds / 60);
|
|
397
|
+
if (minutes < 60)
|
|
398
|
+
return `${minutes}m`;
|
|
399
|
+
const hours = Math.floor(minutes / 60);
|
|
400
|
+
return `${hours}h ${minutes % 60}m`;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* HOME: Situation Awareness
|
|
404
|
+
*
|
|
405
|
+
* REFACTORED: Now uses state store + UI renderer pattern
|
|
406
|
+
*
|
|
407
|
+
* Pattern:
|
|
408
|
+
* 1. Update state (view change)
|
|
409
|
+
* 2. Sync legacy globals to store
|
|
410
|
+
* 3. Call pure renderer function
|
|
411
|
+
* 4. Output result
|
|
412
|
+
*
|
|
413
|
+
* This is the pattern all views should follow.
|
|
414
|
+
*/
|
|
415
|
+
async function showHomeView() {
|
|
416
|
+
// 1. Update state
|
|
417
|
+
currentView = 'HOME';
|
|
418
|
+
// 2. Sync legacy globals to state store
|
|
419
|
+
// TODO: Once all code uses state store, this sync won't be needed
|
|
420
|
+
syncStateToStore();
|
|
421
|
+
// 3. Sync customAgents and runHistory to store
|
|
422
|
+
const state = getState();
|
|
423
|
+
updateAppState({
|
|
424
|
+
customAgents: customAgents,
|
|
425
|
+
runHistory: runHistory,
|
|
426
|
+
});
|
|
427
|
+
// 4. Call pure renderer (state → string)
|
|
428
|
+
const newState = getState();
|
|
429
|
+
const output = renderHomeView(newState);
|
|
430
|
+
// 5. Output result
|
|
431
|
+
console.log();
|
|
432
|
+
console.log(output);
|
|
319
433
|
}
|
|
320
434
|
// Interactive prompt helper
|
|
321
435
|
function prompt(rl, question) {
|
|
@@ -349,49 +463,42 @@ const commands = {
|
|
|
349
463
|
if (args.length > 0) {
|
|
350
464
|
const cmd = commands[args[0]];
|
|
351
465
|
if (cmd) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
466
|
+
const lines = [];
|
|
467
|
+
lines.push(`${C_TEXT_BRIGHT}${args[0]}${RESET}`);
|
|
468
|
+
lines.push(cmd.description);
|
|
469
|
+
lines.push(`Usage: ${C_LINK}${cmd.usage}${RESET}`);
|
|
470
|
+
console.log(Section('COMMAND', lines));
|
|
471
|
+
console.log();
|
|
355
472
|
}
|
|
356
473
|
else {
|
|
357
|
-
console.log(`${
|
|
474
|
+
console.log(Alert('error', `${Copy.error.invalidCommand}: ${args[0]}`, undefined, 'help'));
|
|
475
|
+
console.log();
|
|
358
476
|
}
|
|
359
477
|
}
|
|
360
478
|
else {
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
console.log(
|
|
372
|
-
console.log(
|
|
373
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}agents${reset}${gray} ${dim}List available agents${reset} ${purpleRGB}║${reset}`);
|
|
374
|
-
console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
|
|
375
|
-
console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}Run Management${reset} ${purpleRGB}║${reset}`);
|
|
376
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}create${reset}${gray} ${dim}Create a new run${reset} ${purpleRGB}║${reset}`);
|
|
377
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}start${reset}${gray} ${dim}Start a run${reset} ${purpleRGB}║${reset}`);
|
|
378
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}get${reset}${gray} ${dim}Get run details${reset} ${purpleRGB}║${reset}`);
|
|
379
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}list${reset}${gray} ${dim}List all runs${reset} ${purpleRGB}║${reset}`);
|
|
380
|
-
console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
|
|
381
|
-
console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}Connection${reset} ${purpleRGB}║${reset}`);
|
|
382
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}connect${reset}${gray} ${dim}Connect to mainframe (required for remote features)${reset} ${purpleRGB}║${reset}`);
|
|
383
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}disconnect${reset}${gray} ${dim}Disconnect from mainframe${reset} ${purpleRGB}║${reset}`);
|
|
384
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}whoami${reset}${gray} ${dim}Show current connection and session info${reset} ${purpleRGB}║${reset}`);
|
|
385
|
-
console.log(`${purpleRGB}║${reset} ${purpleRGB}║${reset}`);
|
|
386
|
-
console.log(`${purpleRGB}║${reset} ${purpleBright}${bright}System${reset} ${purpleRGB}║${reset}`);
|
|
387
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}status${reset}${gray} ${dim}Show system status${reset} ${purpleRGB}║${reset}`);
|
|
388
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}gateway${reset}${gray} ${dim}Manage gateway connection${reset} ${purpleRGB}║${reset}`);
|
|
389
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}sentinel${reset}${gray} ${dim}Show Sentinel monitoring${reset} ${purpleRGB}║${reset}`);
|
|
390
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}metrics${reset}${gray} ${dim}Show system metrics${reset} ${purpleRGB}║${reset}`);
|
|
391
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}clear${reset}${gray} ${dim}Clear the screen${reset} ${purpleRGB}║${reset}`);
|
|
392
|
-
console.log(`${purpleRGB}║${reset} ${cyan}${bright}exit${reset}${gray} ${dim}Exit 4Runr OS${reset} ${purpleRGB}║${reset}`);
|
|
393
|
-
console.log(`${purpleRGB}╚═══════════════════════════════════════════════════════════════════════════╝${reset}\n`);
|
|
479
|
+
// Clean, precise help menu
|
|
480
|
+
const commandLines = [];
|
|
481
|
+
commandLines.push(`${C_LINK}home${RESET} Mission control overview`);
|
|
482
|
+
commandLines.push(`${C_LINK}connect${RESET} Connect to remote`);
|
|
483
|
+
commandLines.push(`${C_LINK}build${RESET} Create or edit agent`);
|
|
484
|
+
commandLines.push(`${C_LINK}start${RESET} Begin execution`);
|
|
485
|
+
commandLines.push(`${C_LINK}metrics${RESET} Performance and history`);
|
|
486
|
+
commandLines.push(`${C_LINK}system${RESET} Infrastructure status`);
|
|
487
|
+
commandLines.push(`${C_LINK}help${RESET} Show this help`);
|
|
488
|
+
commandLines.push(`${C_LINK}exit${RESET} Quit`);
|
|
489
|
+
console.log(Section('COMMANDS', commandLines));
|
|
490
|
+
console.log();
|
|
394
491
|
}
|
|
492
|
+
// StatusBar
|
|
493
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
494
|
+
console.log(StatusBar({
|
|
495
|
+
mode: mode,
|
|
496
|
+
connected: gatewayConnected,
|
|
497
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
498
|
+
workspace: currentUser?.username,
|
|
499
|
+
lastAction: 'help',
|
|
500
|
+
}));
|
|
501
|
+
console.log();
|
|
395
502
|
}
|
|
396
503
|
},
|
|
397
504
|
config: {
|
|
@@ -537,9 +644,74 @@ const commands = {
|
|
|
537
644
|
}
|
|
538
645
|
},
|
|
539
646
|
build: {
|
|
540
|
-
description: '
|
|
541
|
-
usage: 'build',
|
|
647
|
+
description: 'Create or edit agent',
|
|
648
|
+
usage: 'build [new|edit <agent>]',
|
|
542
649
|
handler: async (args, rl) => {
|
|
650
|
+
currentView = 'AGENT_LAB';
|
|
651
|
+
console.log();
|
|
652
|
+
const action = args[0] || 'list';
|
|
653
|
+
const agentName = args[1];
|
|
654
|
+
// 1. SELECT AGENT
|
|
655
|
+
if (action === 'list' || (!action && !agentName)) {
|
|
656
|
+
// Show agent list/table
|
|
657
|
+
const agentRows = [];
|
|
658
|
+
if (customAgents.size > 0) {
|
|
659
|
+
Array.from(customAgents.entries()).forEach(([key, agent]) => {
|
|
660
|
+
const agentId = truncateId(key, 8);
|
|
661
|
+
const modelShort = agent.baseAgent.includes(':')
|
|
662
|
+
? agent.baseAgent.split(':').pop()?.substring(0, 12) || 'unknown'
|
|
663
|
+
: truncateText(agent.baseAgent, 12);
|
|
664
|
+
const lastModified = 'N/A'; // Would need file modification time
|
|
665
|
+
const status = 'IDLE';
|
|
666
|
+
agentRows.push([
|
|
667
|
+
agentId,
|
|
668
|
+
truncateText(agent.name, 20),
|
|
669
|
+
modelShort,
|
|
670
|
+
lastModified,
|
|
671
|
+
status,
|
|
672
|
+
]);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
const agentTable = agentRows.length > 0
|
|
676
|
+
? Table(['ID', 'NAME', 'MODEL', 'LAST MODIFIED', 'STATUS'], agentRows, 10)
|
|
677
|
+
: `${C_MUTED}${Copy.agent.noAgents}${RESET}`;
|
|
678
|
+
console.log(Section('SELECT AGENT', [agentTable]));
|
|
679
|
+
console.log();
|
|
680
|
+
// If only one agent, auto-select it
|
|
681
|
+
if (customAgents.size === 1) {
|
|
682
|
+
const singleAgent = Array.from(customAgents.values())[0];
|
|
683
|
+
// Show config for single agent
|
|
684
|
+
await showAgentConfig(singleAgent);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
// Show actions
|
|
688
|
+
const actionLines = [];
|
|
689
|
+
actionLines.push(`${C_LINK}build new${RESET} ${C_MUTED}Create new agent${RESET}`);
|
|
690
|
+
if (customAgents.size > 0) {
|
|
691
|
+
actionLines.push(`${C_LINK}build edit <name>${RESET} ${C_MUTED}Edit existing agent${RESET}`);
|
|
692
|
+
}
|
|
693
|
+
console.log(Section('ACTIONS', actionLines));
|
|
694
|
+
console.log();
|
|
695
|
+
currentView = 'HOME';
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
// Handle edit action
|
|
699
|
+
if (action === 'edit' && agentName) {
|
|
700
|
+
const agent = customAgents.get(agentName.toLowerCase());
|
|
701
|
+
if (!agent) {
|
|
702
|
+
console.log(Alert('error', `Agent not found: ${agentName}`, undefined, 'build'));
|
|
703
|
+
console.log();
|
|
704
|
+
currentView = 'HOME';
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
await showAgentConfig(agent);
|
|
708
|
+
// Then proceed to edit flow (would be implemented)
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
// Handle new action
|
|
712
|
+
if (action === 'new' || (!action && !agentName)) {
|
|
713
|
+
// Proceed with creation flow
|
|
714
|
+
}
|
|
543
715
|
// Check if API keys are stored on server
|
|
544
716
|
let providers = [];
|
|
545
717
|
try {
|
|
@@ -550,12 +722,17 @@ const commands = {
|
|
|
550
722
|
}
|
|
551
723
|
}
|
|
552
724
|
catch (error) {
|
|
553
|
-
console.log(`${red}
|
|
725
|
+
console.log(`${red}Providers unavailable${reset}`);
|
|
726
|
+
console.log(`${dim}Reason: ${error.message}${reset}`);
|
|
727
|
+
console.log(`${dim}Next: ${cyan}config${reset}${dim} to configure providers${reset}\n`);
|
|
728
|
+
currentView = 'HOME';
|
|
554
729
|
return;
|
|
555
730
|
}
|
|
556
731
|
if (providers.length === 0) {
|
|
557
|
-
console.log(`${red}No
|
|
558
|
-
console.log(`${
|
|
732
|
+
console.log(`${red}No providers configured${reset}`);
|
|
733
|
+
console.log(`${dim}Reason: No API keys found${reset}`);
|
|
734
|
+
console.log(`${dim}Next: ${cyan}config${reset}${dim} to add API keys${reset}\n`);
|
|
735
|
+
currentView = 'HOME';
|
|
559
736
|
return;
|
|
560
737
|
}
|
|
561
738
|
// Auto-select provider/model if not set
|
|
@@ -590,9 +767,19 @@ const commands = {
|
|
|
590
767
|
return;
|
|
591
768
|
}
|
|
592
769
|
}
|
|
593
|
-
|
|
594
|
-
console.log(`${
|
|
595
|
-
|
|
770
|
+
// Show agent list
|
|
771
|
+
console.log(`${purpleRGB}Agent List${reset}`);
|
|
772
|
+
if (customAgents.size > 0) {
|
|
773
|
+
customAgents.forEach((agent) => {
|
|
774
|
+
console.log(` ${cyan}${agent.name}${reset} ${dim}${agent.description || 'No description'}${reset}`);
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
console.log(` ${dim}None yet${reset}`);
|
|
779
|
+
}
|
|
780
|
+
console.log();
|
|
781
|
+
// Agent configuration summary (will be shown after creation)
|
|
782
|
+
console.log(`${purpleRGB}Creating new agent${reset}\n`);
|
|
596
783
|
// Model type selection (local vs remote)
|
|
597
784
|
console.log(`${bright}Model Type:${reset}`);
|
|
598
785
|
console.log(` 1. ${cyan}Remote${reset} (OpenAI, Anthropic - requires API key)`);
|
|
@@ -853,8 +1040,24 @@ const commands = {
|
|
|
853
1040
|
};
|
|
854
1041
|
customAgents.set(name.toLowerCase(), customAgent);
|
|
855
1042
|
saveCustomAgents();
|
|
856
|
-
|
|
857
|
-
|
|
1043
|
+
// Save as last agent
|
|
1044
|
+
setLastAgent(name);
|
|
1045
|
+
// Show success and return to agent config view
|
|
1046
|
+
console.log(Alert('success', `${Copy.agent.created}: ${name}`, undefined, `start ${name}`));
|
|
1047
|
+
console.log();
|
|
1048
|
+
// Show the created agent's config (read-only)
|
|
1049
|
+
const createdAgent = customAgents.get(name.toLowerCase());
|
|
1050
|
+
if (createdAgent) {
|
|
1051
|
+
await showAgentConfig(createdAgent);
|
|
1052
|
+
}
|
|
1053
|
+
// Show actions
|
|
1054
|
+
const actionLines = [];
|
|
1055
|
+
actionLines.push(`${C_LINK}start ${name}${RESET} ${C_MUTED}Begin execution${RESET}`);
|
|
1056
|
+
actionLines.push(`${C_LINK}home${RESET} ${C_MUTED}Return to overview${RESET}`);
|
|
1057
|
+
console.log(Section('ACTIONS', actionLines));
|
|
1058
|
+
console.log();
|
|
1059
|
+
// Transition: AGENT_LAB → HOME
|
|
1060
|
+
currentView = 'HOME';
|
|
858
1061
|
}
|
|
859
1062
|
},
|
|
860
1063
|
agents: {
|
|
@@ -975,6 +1178,91 @@ const commands = {
|
|
|
975
1178
|
rl.prompt();
|
|
976
1179
|
}
|
|
977
1180
|
},
|
|
1181
|
+
system: {
|
|
1182
|
+
description: 'Show infrastructure readiness and risks',
|
|
1183
|
+
usage: 'system',
|
|
1184
|
+
handler: async () => {
|
|
1185
|
+
currentView = 'SYSTEM';
|
|
1186
|
+
console.log();
|
|
1187
|
+
// 1. INFRASTRUCTURE
|
|
1188
|
+
const infraLines = [];
|
|
1189
|
+
// Gateway
|
|
1190
|
+
if (gatewayConnected && client) {
|
|
1191
|
+
try {
|
|
1192
|
+
const health = await client.health();
|
|
1193
|
+
const endpoint = truncateText(gatewayUrl || '', 40);
|
|
1194
|
+
infraLines.push(`Gateway: ${C_SUCCESS}UP${RESET} ${C_MUTED}${endpoint}${RESET}`);
|
|
1195
|
+
}
|
|
1196
|
+
catch {
|
|
1197
|
+
infraLines.push(`Gateway: ${C_ERROR}DOWN${RESET} ${C_MUTED}unhealthy${RESET}`);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
else {
|
|
1201
|
+
infraLines.push(`Gateway: ${C_ERROR}DOWN${RESET} ${C_MUTED}not connected${RESET}`);
|
|
1202
|
+
}
|
|
1203
|
+
// Database
|
|
1204
|
+
let dbType = 'none';
|
|
1205
|
+
let dbStatus = 'N/A';
|
|
1206
|
+
if (gatewayConnected && client) {
|
|
1207
|
+
try {
|
|
1208
|
+
const health = await client.health();
|
|
1209
|
+
dbType = health.persistence === 'enabled' ? 'sqlite' : 'none';
|
|
1210
|
+
dbStatus = health.persistence === 'enabled' ? 'connected' : 'disabled';
|
|
1211
|
+
const statusColor = dbStatus === 'connected' ? C_SUCCESS : C_WARN;
|
|
1212
|
+
infraLines.push(`Database: ${statusColor}${dbStatus.toUpperCase()}${RESET} ${C_MUTED}Type: ${dbType}${RESET}`);
|
|
1213
|
+
}
|
|
1214
|
+
catch {
|
|
1215
|
+
infraLines.push(`Database: ${C_WARN}UNKNOWN${RESET} ${C_MUTED}Type: ${dbType}${RESET}`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
infraLines.push(`Database: ${C_WARN}MISSING${RESET} ${C_MUTED}Type: ${dbType}${RESET}`);
|
|
1220
|
+
}
|
|
1221
|
+
// External APIs (simplified for v1)
|
|
1222
|
+
if (gatewayConnected) {
|
|
1223
|
+
try {
|
|
1224
|
+
const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
|
|
1225
|
+
infraLines.push(`Metrics API: ${metricsRes.ok ? C_SUCCESS + 'UP' : C_WARN + 'DOWN' + RESET}`);
|
|
1226
|
+
}
|
|
1227
|
+
catch {
|
|
1228
|
+
infraLines.push(`Metrics API: ${C_WARN}DOWN${RESET}`);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
else {
|
|
1232
|
+
infraLines.push(`Metrics API: ${C_WARN}N/A${RESET} ${C_MUTED}not connected${RESET}`);
|
|
1233
|
+
}
|
|
1234
|
+
console.log(Section('INFRASTRUCTURE', infraLines));
|
|
1235
|
+
console.log();
|
|
1236
|
+
// 2. SECURITY
|
|
1237
|
+
const securityLines = [];
|
|
1238
|
+
// Auth mode
|
|
1239
|
+
let authMode = 'NONE';
|
|
1240
|
+
if (currentUser) {
|
|
1241
|
+
authMode = 'BASIC'; // Simplified - would check actual auth type
|
|
1242
|
+
}
|
|
1243
|
+
securityLines.push(`Auth mode: ${C_ACCENT}${authMode}${RESET}`);
|
|
1244
|
+
// Rate limiting
|
|
1245
|
+
const rateLimitEnabled = false; // v1: not implemented
|
|
1246
|
+
securityLines.push(`Rate limiting: ${rateLimitEnabled ? C_SUCCESS + 'ENABLED' : C_WARN + 'DISABLED' + RESET}`);
|
|
1247
|
+
// Environment
|
|
1248
|
+
const env = process.env.NODE_ENV === 'production' ? 'PROD' : 'DEV';
|
|
1249
|
+
const envColor = env === 'PROD' ? C_SUCCESS : C_WARN;
|
|
1250
|
+
securityLines.push(`Environment: ${envColor}${env}${RESET}`);
|
|
1251
|
+
console.log(Section('SECURITY', securityLines));
|
|
1252
|
+
console.log();
|
|
1253
|
+
// StatusBar
|
|
1254
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
1255
|
+
console.log(StatusBar({
|
|
1256
|
+
mode: mode,
|
|
1257
|
+
connected: gatewayConnected,
|
|
1258
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
1259
|
+
workspace: currentUser?.username,
|
|
1260
|
+
lastAction: 'system',
|
|
1261
|
+
}));
|
|
1262
|
+
console.log();
|
|
1263
|
+
currentView = 'HOME';
|
|
1264
|
+
}
|
|
1265
|
+
},
|
|
978
1266
|
status: {
|
|
979
1267
|
description: 'Show system status',
|
|
980
1268
|
usage: 'status',
|
|
@@ -1075,27 +1363,116 @@ const commands = {
|
|
|
1075
1363
|
}
|
|
1076
1364
|
},
|
|
1077
1365
|
start: {
|
|
1078
|
-
description: '
|
|
1079
|
-
usage: 'start
|
|
1080
|
-
handler: async (args) => {
|
|
1081
|
-
if
|
|
1082
|
-
|
|
1366
|
+
description: 'Begin execution',
|
|
1367
|
+
usage: 'start [agent]',
|
|
1368
|
+
handler: async (args, rl) => {
|
|
1369
|
+
// Auto-redirect: if no agents exist, go to agent lab
|
|
1370
|
+
if (customAgents.size === 0) {
|
|
1371
|
+
console.log(Alert('info', Copy.agent.noAgents, 'Create one', 'build'));
|
|
1372
|
+
console.log();
|
|
1373
|
+
// Transition to AGENT_LAB
|
|
1374
|
+
currentView = 'AGENT_LAB';
|
|
1375
|
+
await commands.build.handler([], rl);
|
|
1083
1376
|
return;
|
|
1084
1377
|
}
|
|
1378
|
+
currentView = 'RUN_CONSOLE';
|
|
1379
|
+
console.log();
|
|
1380
|
+
let agentName;
|
|
1381
|
+
let agent;
|
|
1085
1382
|
if (args.length === 0) {
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1383
|
+
// Shortcut: use last agent if available
|
|
1384
|
+
const lastAgent = getLastAgent();
|
|
1385
|
+
if (lastAgent) {
|
|
1386
|
+
agentName = lastAgent;
|
|
1387
|
+
agent = customAgents.get(lastAgent.toLowerCase());
|
|
1388
|
+
}
|
|
1389
|
+
// If no last agent or last agent not found, auto-select or prompt
|
|
1390
|
+
if (!agent) {
|
|
1391
|
+
if (customAgents.size === 1) {
|
|
1392
|
+
// Auto-select if only one agent
|
|
1393
|
+
agent = Array.from(customAgents.values())[0];
|
|
1394
|
+
agentName = agent.name;
|
|
1395
|
+
}
|
|
1396
|
+
else {
|
|
1397
|
+
// Multiple agents: list and prompt
|
|
1398
|
+
console.log(Section('AVAILABLE AGENTS', []));
|
|
1399
|
+
const agentList = Array.from(customAgents.values());
|
|
1400
|
+
agentList.forEach((a, i) => {
|
|
1401
|
+
console.log(` ${i + 1}. ${C_ACCENT}${a.name}${RESET} ${C_MUTED}${a.description || 'No description'}${RESET}`);
|
|
1402
|
+
});
|
|
1403
|
+
console.log();
|
|
1404
|
+
const choice = await prompt(rl, `${C_ACCENT}Select agent (1-${agentList.length}):${RESET} `);
|
|
1405
|
+
const index = parseInt(choice) - 1;
|
|
1406
|
+
if (index >= 0 && index < agentList.length) {
|
|
1407
|
+
agent = agentList[index];
|
|
1408
|
+
agentName = agent.name;
|
|
1409
|
+
}
|
|
1410
|
+
else {
|
|
1411
|
+
console.log(Alert('error', Copy.error.invalidCommand, undefined, 'start'));
|
|
1412
|
+
console.log();
|
|
1413
|
+
currentView = 'HOME';
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1095
1418
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1419
|
+
else {
|
|
1420
|
+
agentName = args.join(' ').trim();
|
|
1421
|
+
agent = customAgents.get(agentName.toLowerCase());
|
|
1422
|
+
if (!agent) {
|
|
1423
|
+
console.log(Alert('error', `Agent not found: ${agentName}`, undefined, 'build'));
|
|
1424
|
+
console.log();
|
|
1425
|
+
currentView = 'HOME';
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1098
1428
|
}
|
|
1429
|
+
// Save last agent
|
|
1430
|
+
if (agentName) {
|
|
1431
|
+
setLastAgent(agentName);
|
|
1432
|
+
}
|
|
1433
|
+
// 1. RUN INFO
|
|
1434
|
+
const runId = truncateId(`run-${Date.now()}`, 12);
|
|
1435
|
+
const startedTime = new Date().toISOString();
|
|
1436
|
+
const runInfoLines = [];
|
|
1437
|
+
runInfoLines.push(`Run ID: ${C_ACCENT}${runId}${RESET}`);
|
|
1438
|
+
runInfoLines.push(`Agent: ${C_TEXT}${agent.name}${RESET}`);
|
|
1439
|
+
runInfoLines.push(`Started: ${C_MUTED}${startedTime}${RESET}`);
|
|
1440
|
+
runInfoLines.push(`Target: ${C_ACCENT}${localMode ? 'LOCAL' : 'REMOTE'}${RESET}`);
|
|
1441
|
+
console.log(Section('RUN INFO', runInfoLines));
|
|
1442
|
+
console.log();
|
|
1443
|
+
// 2. LIVE STREAM
|
|
1444
|
+
const streamLines = [];
|
|
1445
|
+
streamLines.push(`${C_MUTED}${Copy.agent.awaitingOutput}${RESET}`);
|
|
1446
|
+
console.log(Section('LIVE STREAM', streamLines));
|
|
1447
|
+
console.log();
|
|
1448
|
+
// 3. STATUS
|
|
1449
|
+
const statusLines = [];
|
|
1450
|
+
statusLines.push(`Current phase: ${C_ACCENT}initializing${RESET}`);
|
|
1451
|
+
statusLines.push(`Step count: ${C_ACCENT}0${RESET}`);
|
|
1452
|
+
statusLines.push(`Time elapsed: ${C_ACCENT}0s${RESET}`);
|
|
1453
|
+
console.log(Section('STATUS', statusLines));
|
|
1454
|
+
console.log();
|
|
1455
|
+
// Note: Actual execution would stream events here
|
|
1456
|
+
// For now, simulate a brief execution
|
|
1457
|
+
await sleep(500);
|
|
1458
|
+
// Update stream with simulated events (short, declarative)
|
|
1459
|
+
console.log(`${C_MUTED}[1]${RESET} ${C_TEXT}Initializing context${RESET}`);
|
|
1460
|
+
await sleep(300);
|
|
1461
|
+
console.log(`${C_MUTED}[2]${RESET} ${C_TEXT}Processing${RESET}`);
|
|
1462
|
+
await sleep(300);
|
|
1463
|
+
console.log(`${C_MUTED}[3]${RESET} ${C_TEXT}Generating response${RESET}`);
|
|
1464
|
+
console.log();
|
|
1465
|
+
// StatusBar
|
|
1466
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
1467
|
+
console.log(StatusBar({
|
|
1468
|
+
mode: mode,
|
|
1469
|
+
connected: gatewayConnected,
|
|
1470
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
1471
|
+
workspace: currentUser?.username,
|
|
1472
|
+
lastAction: `start ${agent.name}`,
|
|
1473
|
+
}));
|
|
1474
|
+
console.log();
|
|
1475
|
+
currentView = 'HOME';
|
|
1099
1476
|
}
|
|
1100
1477
|
},
|
|
1101
1478
|
get: {
|
|
@@ -1176,24 +1553,71 @@ const commands = {
|
|
|
1176
1553
|
}
|
|
1177
1554
|
},
|
|
1178
1555
|
metrics: {
|
|
1179
|
-
description: 'Show
|
|
1180
|
-
usage: 'metrics',
|
|
1181
|
-
handler: async () => {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1556
|
+
description: 'Show performance and history',
|
|
1557
|
+
usage: 'metrics [recent|last|agent <name>]',
|
|
1558
|
+
handler: async (args) => {
|
|
1559
|
+
currentView = 'METRICS';
|
|
1560
|
+
console.log();
|
|
1561
|
+
// 1. SUMMARY
|
|
1562
|
+
const totalRuns = runHistory.length;
|
|
1563
|
+
const successCount = runHistory.filter(r => r.status === 'SUCCESS').length;
|
|
1564
|
+
const successRate = totalRuns > 0 ? Math.round((successCount / totalRuns) * 100) : 0;
|
|
1565
|
+
const summaryLines = [];
|
|
1566
|
+
if (totalRuns > 0) {
|
|
1567
|
+
summaryLines.push(`Total runs: ${C_ACCENT}${totalRuns}${RESET}`);
|
|
1568
|
+
summaryLines.push(`Success rate: ${C_ACCENT}${successRate}%${RESET}`);
|
|
1569
|
+
// Calculate average duration (simplified - would need actual duration tracking)
|
|
1570
|
+
summaryLines.push(`Avg duration: ${C_ACCENT}N/A${RESET} ${C_MUTED}(not tracked yet)${RESET}`);
|
|
1185
1571
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
const metrics = await client.metrics();
|
|
1189
|
-
console.log(`${bright}System Metrics:${reset}`);
|
|
1190
|
-
console.log(` Timestamp: ${metrics.timestamp}`);
|
|
1191
|
-
console.log(` Total metrics collected: ${Object.keys(metrics.metrics).length}`);
|
|
1192
|
-
console.log(`\n${gray}Use: curl ${gatewayUrl}/metrics for full details${reset}\n`);
|
|
1572
|
+
else {
|
|
1573
|
+
summaryLines.push(`${C_MUTED}Not enough data${RESET}`);
|
|
1193
1574
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1575
|
+
console.log(Section('SUMMARY', summaryLines));
|
|
1576
|
+
console.log();
|
|
1577
|
+
// 2. BY AGENT
|
|
1578
|
+
if (totalRuns > 0) {
|
|
1579
|
+
// Group runs by agent
|
|
1580
|
+
const agentStats = new Map();
|
|
1581
|
+
runHistory.forEach(run => {
|
|
1582
|
+
const stats = agentStats.get(run.agentName) || { total: 0, success: 0, lastRun: 0 };
|
|
1583
|
+
stats.total++;
|
|
1584
|
+
if (run.status === 'SUCCESS')
|
|
1585
|
+
stats.success++;
|
|
1586
|
+
if (run.timestamp > stats.lastRun)
|
|
1587
|
+
stats.lastRun = run.timestamp;
|
|
1588
|
+
agentStats.set(run.agentName, stats);
|
|
1589
|
+
});
|
|
1590
|
+
// Convert to table rows, sorted by activity
|
|
1591
|
+
const agentRows = Array.from(agentStats.entries())
|
|
1592
|
+
.sort((a, b) => b[1].total - a[1].total) // Most runs first
|
|
1593
|
+
.map(([name, stats]) => {
|
|
1594
|
+
const successPct = stats.total > 0 ? Math.round((stats.success / stats.total) * 100) : 0;
|
|
1595
|
+
const lastRunTime = formatRelativeTime(stats.lastRun);
|
|
1596
|
+
return [
|
|
1597
|
+
truncateText(name, 20),
|
|
1598
|
+
`${stats.total}`,
|
|
1599
|
+
`${successPct}%`,
|
|
1600
|
+
lastRunTime,
|
|
1601
|
+
];
|
|
1602
|
+
});
|
|
1603
|
+
const agentTable = Table(['AGENT', 'TOTAL RUNS', 'SUCCESS %', 'LAST RUN'], agentRows, 10);
|
|
1604
|
+
console.log(Section('BY AGENT', [agentTable]));
|
|
1196
1605
|
}
|
|
1606
|
+
else {
|
|
1607
|
+
console.log(Section('BY AGENT', [`${C_MUTED}No agent data available${RESET}`]));
|
|
1608
|
+
}
|
|
1609
|
+
console.log();
|
|
1610
|
+
// StatusBar
|
|
1611
|
+
const mode = localMode ? 'LOCAL' : (gatewayConnected ? 'REMOTE' : 'OFFLINE');
|
|
1612
|
+
console.log(StatusBar({
|
|
1613
|
+
mode: mode,
|
|
1614
|
+
connected: gatewayConnected,
|
|
1615
|
+
target: gatewayUrl ? truncateText(gatewayUrl, 30) : undefined,
|
|
1616
|
+
workspace: currentUser?.username,
|
|
1617
|
+
lastAction: 'metrics',
|
|
1618
|
+
}));
|
|
1619
|
+
console.log();
|
|
1620
|
+
currentView = 'HOME';
|
|
1197
1621
|
}
|
|
1198
1622
|
},
|
|
1199
1623
|
clear: {
|
|
@@ -1201,7 +1625,7 @@ const commands = {
|
|
|
1201
1625
|
usage: 'clear',
|
|
1202
1626
|
handler: async () => {
|
|
1203
1627
|
console.clear();
|
|
1204
|
-
|
|
1628
|
+
await showHomeView();
|
|
1205
1629
|
}
|
|
1206
1630
|
},
|
|
1207
1631
|
exit: {
|
|
@@ -1231,10 +1655,10 @@ const commands = {
|
|
|
1231
1655
|
return;
|
|
1232
1656
|
}
|
|
1233
1657
|
const username = args.join(' ').trim();
|
|
1234
|
-
console.log(
|
|
1235
|
-
await sleep(500);
|
|
1236
|
-
console.log(`${dim}[ ${brightGreen}AUTH${reset}${dim} ] Authenticating ${cyan}${username}${reset}${dim}...${reset}`);
|
|
1658
|
+
console.log(`${C_ACCENT}${Copy.connection.linking}${RESET}`);
|
|
1237
1659
|
await sleep(300);
|
|
1660
|
+
console.log(`${C_MUTED}${Copy.connection.authenticating} ${C_TEXT}${username}${RESET}`);
|
|
1661
|
+
await sleep(200);
|
|
1238
1662
|
// Create session
|
|
1239
1663
|
currentUser = createSession(username, gatewayUrl);
|
|
1240
1664
|
// Load user-specific data
|
|
@@ -1246,56 +1670,31 @@ const commands = {
|
|
|
1246
1670
|
client = new GatewayClient({ gatewayUrl });
|
|
1247
1671
|
const health = await client.health();
|
|
1248
1672
|
gatewayConnected = true;
|
|
1249
|
-
console.log(
|
|
1250
|
-
console.log(
|
|
1251
|
-
//
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
// Database
|
|
1255
|
-
process.stdout.write(`${gray} - Database: ${reset}`);
|
|
1256
|
-
console.log(`${green}✓${reset} ${dim}(${health.persistence})${reset}`);
|
|
1257
|
-
// Sentinel
|
|
1258
|
-
process.stdout.write(`${gray} - Sentinel: ${reset}`);
|
|
1259
|
-
try {
|
|
1260
|
-
const sentinelRes = await fetch(`${gatewayUrl}/sentinel/health`);
|
|
1261
|
-
if (sentinelRes.ok) {
|
|
1262
|
-
const sentinel = await sentinelRes.json();
|
|
1263
|
-
console.log(`${green}✓${reset} ${dim}(${sentinel?.status || 'active'})${reset}`);
|
|
1264
|
-
}
|
|
1265
|
-
else {
|
|
1266
|
-
console.log(`${yellow}⚠${reset} ${dim}(not responding)${reset}`);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
catch {
|
|
1270
|
-
console.log(`${yellow}⚠${reset} ${dim}(unavailable)${reset}`);
|
|
1271
|
-
}
|
|
1272
|
-
// Metrics
|
|
1273
|
-
process.stdout.write(`${gray} - Metrics: ${reset}`);
|
|
1274
|
-
try {
|
|
1275
|
-
const metricsRes = await fetch(`${gatewayUrl}/api/metrics`);
|
|
1276
|
-
if (metricsRes.ok) {
|
|
1277
|
-
console.log(`${green}✓${reset}`);
|
|
1278
|
-
}
|
|
1279
|
-
else {
|
|
1280
|
-
console.log(`${yellow}⚠${reset}`);
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
catch {
|
|
1284
|
-
console.log(`${yellow}⚠${reset}`);
|
|
1285
|
-
}
|
|
1286
|
-
console.log(`${dim}User: ${green}${username}${reset}${dim} | Session: ${getSessionTimeRemaining(currentUser)}m${reset}\n`);
|
|
1673
|
+
console.log(Alert('success', 'Connected', `Gateway: ${gatewayUrl}`));
|
|
1674
|
+
console.log();
|
|
1675
|
+
// Save connection target to state
|
|
1676
|
+
setLastConnectionTarget(gatewayUrl);
|
|
1677
|
+
setLastMode('REMOTE');
|
|
1287
1678
|
// Start session checker
|
|
1288
1679
|
startSessionChecker();
|
|
1680
|
+
// Transition: SYSTEM → HOME
|
|
1681
|
+
currentView = 'SYSTEM';
|
|
1682
|
+
await sleep(200);
|
|
1683
|
+
await showHomeView();
|
|
1289
1684
|
}
|
|
1290
1685
|
catch (error) {
|
|
1291
|
-
console.log(
|
|
1292
|
-
console.log(
|
|
1686
|
+
console.log(Alert('warn', 'Connected, but gateway unavailable', 'Local features only', 'system diagnose'));
|
|
1687
|
+
console.log();
|
|
1293
1688
|
gatewayConnected = false;
|
|
1689
|
+
setLastMode('LOCAL');
|
|
1690
|
+
await showHomeView();
|
|
1294
1691
|
}
|
|
1295
1692
|
}
|
|
1296
1693
|
else {
|
|
1297
|
-
console.log(
|
|
1298
|
-
console.log(
|
|
1694
|
+
console.log(Alert('success', 'Connected', 'No gateway URL configured'));
|
|
1695
|
+
console.log();
|
|
1696
|
+
setLastMode('LOCAL');
|
|
1697
|
+
await showHomeView();
|
|
1299
1698
|
}
|
|
1300
1699
|
}
|
|
1301
1700
|
},
|
|
@@ -1308,11 +1707,11 @@ const commands = {
|
|
|
1308
1707
|
return;
|
|
1309
1708
|
}
|
|
1310
1709
|
const username = currentUser.username;
|
|
1311
|
-
console.log(
|
|
1312
|
-
await sleep(
|
|
1710
|
+
console.log(`${C_ACCENT}Terminating link${RESET}`);
|
|
1711
|
+
await sleep(200);
|
|
1313
1712
|
handleLogout();
|
|
1314
|
-
console.log(`${
|
|
1315
|
-
console.log(
|
|
1713
|
+
console.log(`${C_MUTED}${Copy.connection.terminated}${RESET}`);
|
|
1714
|
+
console.log();
|
|
1316
1715
|
}
|
|
1317
1716
|
},
|
|
1318
1717
|
whoami: {
|
|
@@ -1626,7 +2025,7 @@ async function startREPL() {
|
|
|
1626
2025
|
output: process.stdout,
|
|
1627
2026
|
prompt: `${cyan}4runr>${reset} `
|
|
1628
2027
|
});
|
|
1629
|
-
|
|
2028
|
+
// Don't show welcome - boot sequence transitions to HOME
|
|
1630
2029
|
rl.prompt();
|
|
1631
2030
|
rl.on('line', async (line) => {
|
|
1632
2031
|
// Handle multiline mode FIRST (before any command processing)
|
|
@@ -1790,22 +2189,55 @@ async function startREPL() {
|
|
|
1790
2189
|
if (cmd) {
|
|
1791
2190
|
try {
|
|
1792
2191
|
await cmd.handler(args, rl);
|
|
2192
|
+
// Return to stable view after command (if not already in a view)
|
|
2193
|
+
if (currentView === 'BOOT') {
|
|
2194
|
+
await showHomeView();
|
|
2195
|
+
}
|
|
1793
2196
|
}
|
|
1794
2197
|
catch (error) {
|
|
1795
|
-
|
|
2198
|
+
// Actionable errors: what failed, why, what to do next
|
|
2199
|
+
console.log(`\n${red}Failed: ${error.message}${reset}`);
|
|
2200
|
+
if (error.cause) {
|
|
2201
|
+
console.log(`${dim}Reason: ${error.cause}${reset}`);
|
|
2202
|
+
}
|
|
2203
|
+
// Suggest next action based on error type
|
|
2204
|
+
if (error.message.includes('connect') || error.message.includes('connection')) {
|
|
2205
|
+
console.log(`${dim}Next: ${cyan}connect <username>${reset}${dim} or run with ${cyan}--local${reset}\n`);
|
|
2206
|
+
}
|
|
2207
|
+
else if (error.message.includes('agent') || error.message.includes('build')) {
|
|
2208
|
+
console.log(`${dim}Next: ${cyan}build${reset}${dim} to create an agent${reset}\n`);
|
|
2209
|
+
}
|
|
2210
|
+
else {
|
|
2211
|
+
console.log(`${dim}Next: ${cyan}help${reset}${dim} for available commands${reset}\n`);
|
|
2212
|
+
}
|
|
2213
|
+
// Return to previous stable view
|
|
2214
|
+
if (currentView !== 'HOME' && currentView !== 'BOOT') {
|
|
2215
|
+
await showHomeView();
|
|
2216
|
+
}
|
|
1796
2217
|
}
|
|
1797
2218
|
}
|
|
1798
2219
|
else {
|
|
1799
|
-
console.log(`${
|
|
1800
|
-
console.log(
|
|
2220
|
+
console.log(Alert('error', `${Copy.error.invalidCommand}: ${command}`, undefined, 'help'));
|
|
2221
|
+
console.log();
|
|
1801
2222
|
}
|
|
1802
2223
|
rl.prompt();
|
|
1803
2224
|
});
|
|
1804
2225
|
rl.on('close', () => {
|
|
1805
|
-
|
|
2226
|
+
stopAllAnimations();
|
|
2227
|
+
console.log(`\n${C_ACCENT}Goodbye${RESET}\n`);
|
|
1806
2228
|
process.exit(0);
|
|
1807
2229
|
});
|
|
1808
2230
|
}
|
|
2231
|
+
// Cleanup on process termination
|
|
2232
|
+
process.on('SIGINT', () => {
|
|
2233
|
+
stopAllAnimations();
|
|
2234
|
+
console.log(`\n${C_MUTED}Terminated${RESET}\n`);
|
|
2235
|
+
process.exit(0);
|
|
2236
|
+
});
|
|
2237
|
+
process.on('SIGTERM', () => {
|
|
2238
|
+
stopAllAnimations();
|
|
2239
|
+
process.exit(0);
|
|
2240
|
+
});
|
|
1809
2241
|
// Start the OS
|
|
1810
2242
|
async function main() {
|
|
1811
2243
|
// Load custom agents on startup
|