#!/usr/bin/env node
/**
* network-ai console — Interactive terminal dashboard for the agent runtime.
*
* Usage:
* npx network-ai-console [options]
* npx network-ai console (via CLI sub-command)
*
* Options:
* --base-path
Base directory for agent sandbox (default: cwd)
* --auto-approve Auto-approve all operations (DANGEROUS)
* --allow Comma-separated command whitelist (e.g. "npm *,node *,git status")
* --budget Budget ceiling in tokens (default: 100000)
* --pipe Pipe mode: read JSON commands from stdin, write JSON to stdout
* --board Named blackboard to use (default: main)
*/
import * as path from 'path';
import { ConsoleUI } from '../lib/console-ui';
import {
AgentRuntime,
RuntimePolicyError,
RuntimeApprovalError,
} from '../lib/agent-runtime';
import { LockedBlackboard } from '../lib/locked-blackboard';
import { FederatedBudget } from '../lib/federated-budget';
import { JourneyFSM } from '../lib/fsm-journey';
import { AdapterRegistry } from '../adapters/adapter-registry';
import { createSwarmOrchestrator } from '../index';
// ── Parse args ────────────────────────────────────────────────────────────────
function parseArgs(argv: string[]): {
basePath: string;
autoApprove: boolean;
allowedCommands: string[];
budgetCeiling: number;
pipe: boolean;
board: string;
} {
let basePath = process.cwd();
let autoApprove = false;
let allowedCommands: string[] = ['npm *', 'node *', 'npx *', 'git status', 'git diff*', 'git log*', 'ls*', 'dir*', 'cat *', 'type *', 'echo *'];
let budgetCeiling = 100_000;
let pipe = false;
let board = 'main';
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (arg === '--base-path' && argv[i + 1]) {
basePath = path.resolve(argv[++i]);
} else if (arg === '--auto-approve') {
autoApprove = true;
} else if (arg === '--allow' && argv[i + 1]) {
allowedCommands = argv[++i].split(',').map(s => s.trim());
} else if (arg === '--budget' && argv[i + 1]) {
budgetCeiling = parseInt(argv[++i], 10) || 100_000;
} else if (arg === '--pipe') {
pipe = true;
} else if (arg === '--board' && argv[i + 1]) {
board = argv[++i];
}
}
return { basePath, autoApprove, allowedCommands, budgetCeiling, pipe, board };
}
// ── Version ───────────────────────────────────────────────────────────────────
const pkg = (() => {
try { return require('../package.json'); } catch {
try { return require('../../package.json'); } catch { return { version: '0.0.0' }; }
}
})() as { version: string };
// ── Main ──────────────────────────────────────────────────────────────────────
async function main(): Promise {
const args = parseArgs(process.argv.slice(2));
// In pipe mode, redirect console.log to stderr so stdout is reserved for JSON
if (args.pipe) {
console.log = (...a: unknown[]) => process.stderr.write(a.join(' ') + '\n');
}
// ── Shared Orchestrator — same instances as MCP server ───────────────────
const orchestrator = createSwarmOrchestrator();
const blackboard = new LockedBlackboard(args.basePath);
const budget = new FederatedBudget({ ceiling: args.budgetCeiling });
const adapters = orchestrator.adapters;
// JourneyFSM requires at least one state — use a sensible default
const fsm = new JourneyFSM({
states: [
{ name: 'IDLE', authorizedAgents: ['*'], authorizedTools: { '*': ['*'] } },
{ name: 'PLANNING', authorizedAgents: ['*'], authorizedTools: { '*': ['*'] } },
{ name: 'EXECUTING', authorizedAgents: ['*'], authorizedTools: { '*': ['*'] } },
{ name: 'REVIEWING', authorizedAgents: ['*'], authorizedTools: { '*': ['*'] } },
{ name: 'DONE', authorizedAgents: ['orchestrator'], authorizedTools: { orchestrator: ['*'] } },
],
transitions: [
{ from: 'IDLE', event: 'plan', to: 'PLANNING', allowedBy: '*' },
{ from: 'PLANNING', event: 'execute', to: 'EXECUTING', allowedBy: '*' },
{ from: 'EXECUTING', event: 'review', to: 'REVIEWING', allowedBy: '*' },
{ from: 'REVIEWING', event: 'approve', to: 'DONE', allowedBy: '*' },
{ from: 'REVIEWING', event: 'revise', to: 'EXECUTING', allowedBy: '*' },
{ from: 'DONE', event: 'reset', to: 'IDLE', allowedBy: '*' },
],
initialState: 'IDLE',
});
// Create the runtime
const runtime = new AgentRuntime({
policy: {
basePath: args.basePath,
allowedCommands: args.allowedCommands,
allowedPaths: ['.'],
autoApproveReads: true,
},
autoApproveAll: args.autoApprove,
onApproval: args.autoApprove
? undefined
: args.pipe
// Pipe mode has no interactive approver — fail closed. Approval-required
// operations (rm, git push, npm publish, …) are denied unless the operator
// explicitly opts in with --auto-approve, so untrusted stdin cannot trigger
// a high-risk command silently.
? async (req) => ({
approved: false,
reason: `Operation requires approval ([${req.type}] ${req.target}); pipe mode has no interactive approver. Re-run with --auto-approve to permit it.`,
})
: async (req) => {
// Interactive approval via console
ui.log(`APPROVAL NEEDED: [${req.type}] ${req.target} (risk: ${req.risk})`, 'approval');
ui.log(`Type 'approve' or 'deny '`, 'approval');
// Store pending approval for the approve/deny commands
pendingApproval = {
resolve: (decision) => decision,
request: req,
};
return new Promise<{ approved: boolean; approvedBy?: string; reason?: string }>((resolve) => {
pendingApproval = { resolve, request: req };
});
},
});
let pendingApproval: {
resolve: (decision: { approved: boolean; approvedBy?: string; reason?: string }) => void;
request: { type: string; target: string; agentId: string };
} | null = null;
// Create the console UI
const ui = new ConsoleUI({
title: 'Network-AI',
version: pkg.version,
prompt: '> ',
});
// ── Register commands ─────────────────────────────────────────────────────
ui.command('status', () => {
// Sync status from real orchestrator components
const adapterList = adapters.listAdapters();
const readyCount = adapterList.filter(a => a.ready).length;
const spent = budget.getTotalSpent();
const ceiling = budget.getCeiling();
const pct = ceiling > 0 ? Math.round((spent / ceiling) * 100) : 0;
ui.updateStatus({
agents: { active: readyCount, total: adapterList.length },
budget: { usedPercent: pct },
fsm: { state: fsm.state },
});
const s = ui.getStatus();
const audit = runtime.getAuditLog();
ui.log(`Agents: ${s.agents.active}/${s.agents.total} | Budget: ${spent.toLocaleString()}/${ceiling.toLocaleString()} (${pct}%) | FSM: ${fsm.state}`);
ui.log(`Blackboard keys: ${blackboard.listKeys().length} | Pending changes: ${blackboard.listPendingChanges().length}`);
ui.log(`Audit entries: ${audit.length} | Shell processes: ${runtime.shell.running}`);
ui.log(`Pending approvals: ${s.pendingApprovals}`);
}, 'Show runtime status');
ui.command('exec', async (cmdArgs) => {
if (!cmdArgs) { ui.log('Usage: exec ', 'warn'); return; }
// Validate against the sandbox policy allowlist before dispatch.
// runtime.exec() enforces the same check internally, but making it
// explicit at the call site keeps the security boundary visible.
if (!runtime.policy.isCommandAllowed(cmdArgs)) {
ui.log(`Command not in allowlist: ${cmdArgs}`, 'error');
return;
}
try {
const result = await runtime.exec(cmdArgs, 'console-user');
if (result.stdout) ui.log(result.stdout.trim());
if (result.stderr) ui.log(result.stderr.trim(), 'warn');
ui.log(`Exit ${result.exitCode} (${result.durationMs}ms)${result.timedOut ? ' [TIMED OUT]' : ''}`, result.exitCode === 0 ? 'success' : 'error');
} catch (err) {
if (err instanceof RuntimePolicyError) {
ui.log(err.message, 'error');
} else if (err instanceof RuntimeApprovalError) {
ui.log(err.message, 'warn');
} else {
ui.log(String(err), 'error');
}
}
}, 'Execute a shell command (exec )');
ui.command('read', async (filePath) => {
if (!filePath) { ui.log('Usage: read ', 'warn'); return; }
const result = await runtime.readFile(filePath.trim(), 'console-user');
if (result.success && result.content) {
ui.log(`── ${result.path} ──`);
const lines = result.content.split('\n');
const preview = lines.slice(0, 30).join('\n');
ui.log(preview);
if (lines.length > 30) ui.log(`... (${lines.length - 30} more lines)`, 'info');
} else {
ui.log(result.error ?? 'Failed to read file', 'error');
}
}, 'Read a file (read )');
ui.command('ls', async (dirPath) => {
const target = dirPath?.trim() || '.';
const result = await runtime.listDir(target, 'console-user');
if (result.success && result.entries) {
ui.log(`── ${result.path} ──`);
for (const entry of result.entries) {
ui.log(` ${entry}`);
}
} else {
ui.log(result.error ?? 'Failed to list directory', 'error');
}
}, 'List directory contents (ls [path])');
ui.command('approve', (_args) => {
if (!pendingApproval) {
ui.log('No pending approval', 'warn');
return;
}
const req = pendingApproval.request;
pendingApproval.resolve({ approved: true, approvedBy: 'console-user' });
pendingApproval = null;
ui.log(`Approved: ${req.type} ${req.target}`, 'success');
ui.updateStatus({ pendingApprovals: Math.max(0, ui.getStatus().pendingApprovals - 1) });
}, 'Approve a pending operation');
ui.command('deny', (reason) => {
if (!pendingApproval) {
ui.log('No pending approval', 'warn');
return;
}
const req = pendingApproval.request;
pendingApproval.resolve({ approved: false, reason: reason || 'Denied by operator' });
pendingApproval = null;
ui.log(`Denied: ${req.type} ${req.target} — ${reason || 'no reason'}`, 'warn');
ui.updateStatus({ pendingApprovals: Math.max(0, ui.getStatus().pendingApprovals - 1) });
}, 'Deny a pending operation (deny [reason])');
ui.command('audit', (args) => {
const count = parseInt(args) || 10;
const entries = runtime.getAuditLog();
const recent = entries.slice(-count);
if (recent.length === 0) {
ui.log('No audit entries yet', 'info');
return;
}
for (const e of recent) {
const time = e.timestamp.split('T')[1]?.slice(0, 8) ?? '';
ui.log(`[${time}] ${e.action} | ${e.agentId} | ${e.target} → ${e.result}`);
}
}, 'Show recent audit entries (audit [count])');
ui.command('policy', (args) => {
const config = runtime.policy.getConfig();
if (!args || args === 'show') {
ui.log(`Base path: ${config.basePath}`);
ui.log(`Allowed commands: ${config.allowedCommands.join(', ') || '(none)'}`);
ui.log(`Blocked commands: ${config.blockedCommands.length} patterns`);
ui.log(`Allowed paths: ${config.allowedPaths.join(', ')}`);
ui.log(`Max concurrent: ${config.maxConcurrentProcesses}`);
ui.log(`Timeout: ${config.defaultTimeoutMs}ms`);
ui.log(`Auto-approve reads: ${config.autoApproveReads}`);
} else if (args.startsWith('allow ')) {
const pattern = args.slice(6).trim();
runtime.policy.allowCommand(pattern);
ui.log(`Added to allowed commands: ${pattern}`, 'success');
} else if (args.startsWith('block ')) {
const pattern = args.slice(6).trim();
runtime.policy.disallowCommand(pattern);
ui.log(`Removed from allowed commands: ${pattern}`, 'success');
} else {
ui.log('Usage: policy [show|allow |block ]', 'warn');
}
}, 'View or modify sandbox policy');
// ── Orchestrator commands ─────────────────────────────────────────────────
ui.command('agents', async () => {
const adapterList = adapters.listAdapters();
if (adapterList.length === 0) {
ui.log('No adapters registered. Use "spawn" to execute agents via adapters.', 'info');
return;
}
ui.log(`── Registered Adapters (${adapterList.length}) ──`);
for (const a of adapterList) {
const status = a.ready ? 'ready' : (a.deferred ? 'deferred' : 'not ready');
ui.log(` ${a.name} v${a.version} [${status}]`);
}
try {
const discovered = await adapters.discoverAgents();
if (discovered.length > 0) {
ui.log(`── Discovered Agents (${discovered.length}) ──`);
for (const agent of discovered) {
ui.log(` ${agent.id} (${agent.adapter}) — ${agent.description ?? 'no description'}`);
}
}
} catch {
// discoverAgents may fail if no adapters are initialized
}
}, 'List registered adapters and discovered agents');
ui.command('spawn', async (spawnArgs) => {
if (!spawnArgs) { ui.log('Usage: spawn [input text]', 'warn'); return; }
const [agentId, ...rest] = spawnArgs.split(/\s+/);
const input = rest.join(' ') || 'execute';
ui.log(`Spawning agent "${agentId}"...`, 'info');
try {
const result = await adapters.executeAgent(
agentId,
{ action: 'execute', params: { input } },
{ agentId: 'console-user', taskId: `console-${Date.now()}`, sessionId: 'console' },
);
ui.log(`Agent "${agentId}" completed — success: ${result.success}`, result.success ? 'success' : 'error');
if (result.data) {
const text = typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2);
const lines = text.split('\n');
const preview = lines.slice(0, 20).join('\n');
ui.log(preview);
if (lines.length > 20) ui.log(`... (${lines.length - 20} more lines)`, 'info');
}
if (result.error) ui.log(`Error: ${result.error.message}`, 'error');
} catch (err) {
ui.log(`Spawn failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
}
}, 'Execute an agent (spawn [input])');
ui.command('stop', async (adapterName) => {
if (!adapterName) { ui.log('Usage: stop ', 'warn'); return; }
try {
await adapters.removeAdapter(adapterName.trim());
ui.log(`Adapter "${adapterName.trim()}" removed`, 'success');
} catch (err) {
ui.log(`Stop failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
}
}, 'Remove an adapter (stop )');
ui.command('bb', (bbArgs) => {
if (!bbArgs) { ui.log('Usage: bb [args]', 'warn'); return; }
const parts = bbArgs.split(/\s+/);
const sub = parts[0];
if (sub === 'list') {
const keys = blackboard.listKeys();
if (keys.length === 0) { ui.log('Blackboard is empty', 'info'); return; }
ui.log(`── Blackboard Keys (${keys.length}) ──`);
for (const key of keys) ui.log(` ${key}`);
} else if (sub === 'read') {
const key = parts[1];
if (!key) { ui.log('Usage: bb read ', 'warn'); return; }
const entry = blackboard.read(key);
if (!entry) { ui.log(`Key "${key}" not found`, 'warn'); return; }
ui.log(`── ${key} ──`);
ui.log(` Value: ${JSON.stringify(entry.value)}`);
ui.log(` Source: ${entry.source_agent} | Updated: ${entry.timestamp}`);
} else if (sub === 'write') {
const key = parts[1];
const value = parts.slice(2).join(' ');
if (!key || !value) { ui.log('Usage: bb write ', 'warn'); return; }
let parsed: unknown;
try { parsed = JSON.parse(value); } catch { parsed = value; }
blackboard.write(key, parsed, 'console-user');
ui.log(`Wrote "${key}" to blackboard`, 'success');
} else if (sub === 'delete') {
const key = parts[1];
if (!key) { ui.log('Usage: bb delete ', 'warn'); return; }
const deleted = blackboard.delete(key);
ui.log(deleted ? `Deleted "${key}"` : `Key "${key}" not found`, deleted ? 'success' : 'warn');
} else if (sub === 'propose') {
const key = parts[1];
const value = parts.slice(2).join(' ');
if (!key || !value) { ui.log('Usage: bb propose ', 'warn'); return; }
let parsed: unknown;
try { parsed = JSON.parse(value); } catch { parsed = value; }
const changeId = blackboard.propose(key, parsed, 'console-user');
ui.log(`Proposed change: ${changeId}`, 'success');
} else if (sub === 'validate') {
const changeId = parts[1];
if (!changeId) { ui.log('Usage: bb validate ', 'warn'); return; }
const valid = blackboard.validate(changeId, 'console-user');
ui.log(`Validation: ${valid ? 'passed' : 'failed'}`, valid ? 'success' : 'error');
} else if (sub === 'commit') {
const changeId = parts[1];
if (!changeId) { ui.log('Usage: bb commit ', 'warn'); return; }
const result = blackboard.commit(changeId);
ui.log(`Commit: ${result.success ? 'success' : 'failed'}${result.message ? ' — ' + result.message : ''}`, result.success ? 'success' : 'error');
} else if (sub === 'pending') {
const pending = blackboard.listPendingChanges();
if (pending.length === 0) { ui.log('No pending changes', 'info'); return; }
ui.log(`── Pending Changes (${pending.length}) ──`);
for (const p of pending) {
ui.log(` ${p.change_id}: ${p.key} by ${p.source_agent} (${p.status})`);
}
} else {
ui.log('Usage: bb [args]', 'warn');
}
}, 'Blackboard operations (bb [args])');
ui.command('budget', (budgetArgs) => {
const sub = budgetArgs?.split(/\s+/)[0];
if (!sub || sub === 'show') {
const spent = budget.getTotalSpent();
const ceiling = budget.getCeiling();
const remaining = budget.remaining();
const pct = ceiling > 0 ? Math.round((spent / ceiling) * 100) : 0;
ui.log(`── Budget ──`);
ui.log(` Ceiling: ${ceiling.toLocaleString()} tokens`);
ui.log(` Spent: ${spent.toLocaleString()} tokens (${pct}%)`);
ui.log(` Remaining: ${remaining.toLocaleString()} tokens`);
const log = budget.getSpendLog();
const agents = Object.entries(log);
if (agents.length > 0) {
ui.log(` Per-agent:`);
for (const [agentId, tokens] of agents) {
ui.log(` ${agentId}: ${tokens.toLocaleString()}`);
}
}
// Sync status bar
ui.updateStatus({ budget: { usedPercent: pct } });
} else if (sub === 'spend') {
const parts = budgetArgs.split(/\s+/);
const agentId = parts[1];
const tokens = parseInt(parts[2], 10);
if (!agentId || !tokens || tokens <= 0) { ui.log('Usage: budget spend ', 'warn'); return; }
const result = budget.spend(agentId, tokens);
ui.log(
result.allowed
? `Spent ${tokens} tokens for "${agentId}" (remaining: ${result.remaining})`
: `Denied: ${result.deniedReason ?? 'ceiling exceeded'} (remaining: ${result.remaining})`,
result.allowed ? 'success' : 'error',
);
const pct = budget.getCeiling() > 0 ? Math.round((budget.getTotalSpent() / budget.getCeiling()) * 100) : 0;
ui.updateStatus({ budget: { usedPercent: pct } });
} else if (sub === 'reset') {
budget.reset();
ui.log('Budget reset to zero', 'success');
ui.updateStatus({ budget: { usedPercent: 0 } });
} else {
ui.log('Usage: budget [show|spend |reset]', 'warn');
}
}, 'Budget tracking (budget [show|spend|reset])');
ui.command('fsm', (fsmArgs) => {
const parts = fsmArgs?.split(/\s+/) ?? [];
const sub = parts[0];
if (!sub || sub === 'show') {
ui.log(`── FSM State ──`);
ui.log(` Current: ${fsm.state}`);
ui.log(` Time in state: ${fsm.timeInCurrentState}ms`);
ui.log(` Available events: ${fsm.availableEvents().join(', ') || '(none)'}`);
ui.updateStatus({ fsm: { state: fsm.state } });
} else if (sub === 'transition' || sub === 'go') {
const event = parts[1];
if (!event) { ui.log('Usage: fsm transition ', 'warn'); return; }
const result = fsm.transition(event, 'console-user');
if (result.success) {
ui.log(`Transitioned: ${result.previousState} → ${result.currentState} (event: ${event})`, 'success');
ui.updateStatus({ fsm: { state: fsm.state } });
} else {
ui.log(`Transition failed: ${result.reason ?? 'unknown'}`, 'error');
}
} else if (sub === 'events') {
const events = fsm.availableEvents();
if (events.length === 0) { ui.log('No available events from current state', 'info'); return; }
ui.log(`Available events: ${events.join(', ')}`);
} else if (sub === 'history') {
const history = fsm.transitionHistory;
if (history.length === 0) { ui.log('No transition history', 'info'); return; }
ui.log(`── Transition History (${history.length}) ──`);
for (const h of history) {
const enteredAt = new Date(h.enteredAt).toISOString().split('T')[1]?.slice(0, 8) ?? '';
ui.log(` [${enteredAt}] ${h.state}${h.triggeredBy ? ` (by ${h.triggeredBy})` : ''}`);
}
} else if (sub === 'reset') {
fsm.reset();
ui.log('FSM reset to initial state', 'success');
ui.updateStatus({ fsm: { state: fsm.state } });
} else {
ui.log('Usage: fsm [show|transition |events|history|reset]', 'warn');
}
}, 'FSM workflow control (fsm [show|transition|events|history|reset])');
ui.command('health', async () => {
ui.log('Running health check...', 'info');
try {
const results = await adapters.healthCheck();
const entries = Object.entries(results);
if (entries.length === 0) { ui.log('No adapters to check', 'info'); return; }
for (const [name, check] of entries) {
ui.log(` ${name}: ${check.healthy ? 'healthy' : 'unhealthy'}${check.details ? ' — ' + check.details : ''}`, check.healthy ? 'success' : 'error');
}
} catch (err) {
ui.log(`Health check failed: ${err instanceof Error ? err.message : String(err)}`, 'error');
}
}, 'Run adapter health checks');
// ── Wire runtime events to feed ──────────────────────────────────────────
runtime.on('command:start', (agentId: string, cmd: string) => {
ui.log(`${agentId} → exec "${cmd}"`, 'info');
});
runtime.on('command:complete', (agentId: string, cmd: string, result: { exitCode: number; durationMs: number }) => {
const icon = result.exitCode === 0 ? 'success' : 'error';
ui.log(`${agentId} ← exit ${result.exitCode} (${result.durationMs}ms)`, icon as 'success' | 'error');
});
runtime.on('policy:violation', (agentId: string, target: string, reason: string) => {
ui.log(`BLOCKED: ${agentId} tried ${target} — ${reason}`, 'error');
});
runtime.on('approval:requested', () => {
ui.updateStatus({ pendingApprovals: ui.getStatus().pendingApprovals + 1 });
});
// ── Start ────────────────────────────────────────────────────────────────
if (args.pipe) {
// ── Pipe mode: JSON in → JSON out ────────────────────────────────────
await runPipeMode(ui, { blackboard, budget, fsm, adapters, runtime });
} else {
// ── Interactive TUI mode ─────────────────────────────────────────────
ui.log('Console ready. Type "help" for commands.', 'success');
await ui.start();
}
}
// ============================================================================
// PIPE MODE — JSON protocol for AI agents
// ============================================================================
interface PipeCommand {
/** Command name (same as console commands: status, bb, budget, fsm, etc.) */
command: string;
/** Arguments string (same format as interactive mode) */
args?: string;
/** Optional request ID for correlation */
id?: string | number;
}
interface PipeResponse {
/** Whether the command succeeded */
success: boolean;
/** Command that was executed */
command: string;
/** Structured response data */
data?: unknown;
/** Error message if failed */
error?: string;
/** Correlated request ID */
id?: string | number;
}
async function runPipeMode(
ui: ConsoleUI,
ctx: {
blackboard: LockedBlackboard;
budget: FederatedBudget;
fsm: JourneyFSM;
adapters: AdapterRegistry;
runtime: AgentRuntime;
},
): Promise {
const { createInterface } = await import('readline');
const rl = createInterface({ input: process.stdin, terminal: false });
const respond = (res: PipeResponse): void => {
process.stdout.write(JSON.stringify(res) + '\n');
};
rl.on('line', async (line: string) => {
const trimmed = line.trim();
if (!trimmed) return;
let cmd: PipeCommand;
try {
cmd = JSON.parse(trimmed);
} catch {
respond({ success: false, command: '', error: 'Invalid JSON' });
return;
}
if (!cmd.command || typeof cmd.command !== 'string') {
respond({ success: false, command: '', error: 'Missing "command" field', id: cmd.id });
return;
}
try {
const data = await executePipeCommand(cmd.command, cmd.args ?? '', ctx);
respond({ success: true, command: cmd.command, data, id: cmd.id });
} catch (err) {
respond({
success: false,
command: cmd.command,
error: err instanceof Error ? err.message : String(err),
id: cmd.id,
});
}
});
rl.on('close', () => process.exit(0));
// Keep alive
await new Promise(() => {});
}
async function executePipeCommand(
command: string,
args: string,
ctx: {
blackboard: LockedBlackboard;
budget: FederatedBudget;
fsm: JourneyFSM;
adapters: AdapterRegistry;
runtime: AgentRuntime;
},
): Promise {
const { blackboard, budget, fsm, adapters, runtime } = ctx;
switch (command) {
case 'status': {
const adapterList = adapters.listAdapters();
return {
agents: { active: adapterList.filter(a => a.ready).length, total: adapterList.length },
budget: { spent: budget.getTotalSpent(), ceiling: budget.getCeiling(), remaining: budget.remaining() },
fsm: { state: fsm.state, availableEvents: fsm.availableEvents() },
blackboard: { keys: blackboard.listKeys().length, pending: blackboard.listPendingChanges().length },
runtime: { shellProcesses: runtime.shell.running, auditEntries: runtime.getAuditLog().length },
};
}
case 'bb': {
const parts = args.split(/\s+/);
const sub = parts[0];
if (sub === 'list') return { keys: blackboard.listKeys() };
if (sub === 'read') {
const entry = blackboard.read(parts[1]);
if (!entry) throw new Error(`Key "${parts[1]}" not found`);
return entry;
}
if (sub === 'write') {
let parsed: unknown;
const value = parts.slice(2).join(' ');
try { parsed = JSON.parse(value); } catch { parsed = value; }
blackboard.write(parts[1], parsed, 'pipe-agent');
return { key: parts[1], written: true };
}
if (sub === 'delete') {
return { key: parts[1], deleted: blackboard.delete(parts[1]) };
}
if (sub === 'propose') {
let parsed: unknown;
const value = parts.slice(2).join(' ');
try { parsed = JSON.parse(value); } catch { parsed = value; }
const changeId = blackboard.propose(parts[1], parsed, 'pipe-agent');
return { changeId };
}
if (sub === 'validate') {
return { valid: blackboard.validate(parts[1], 'pipe-agent') };
}
if (sub === 'commit') {
return blackboard.commit(parts[1]);
}
if (sub === 'pending') {
return { pending: blackboard.listPendingChanges() };
}
if (sub === 'snapshot') {
return blackboard.getSnapshot();
}
throw new Error(`Unknown bb subcommand: ${sub}`);
}
case 'budget': {
const sub = args.split(/\s+/)[0];
if (!sub || sub === 'show') {
return {
ceiling: budget.getCeiling(),
spent: budget.getTotalSpent(),
remaining: budget.remaining(),
perAgent: budget.getSpendLog(),
};
}
if (sub === 'spend') {
const parts = args.split(/\s+/);
return budget.spend(parts[1], parseInt(parts[2], 10));
}
if (sub === 'reset') {
budget.reset();
return { reset: true };
}
throw new Error(`Unknown budget subcommand: ${sub}`);
}
case 'fsm': {
const parts = args.split(/\s+/);
const sub = parts[0];
if (!sub || sub === 'show') {
return {
state: fsm.state,
timeInState: fsm.timeInCurrentState,
availableEvents: fsm.availableEvents(),
};
}
if (sub === 'transition' || sub === 'go') {
return fsm.transition(parts[1], parts[2] ?? 'pipe-agent');
}
if (sub === 'events') {
return { events: fsm.availableEvents() };
}
if (sub === 'history') {
return { history: fsm.transitionHistory };
}
if (sub === 'reset') {
fsm.reset();
return { state: fsm.state };
}
throw new Error(`Unknown fsm subcommand: ${sub}`);
}
case 'agents': {
const adapterList = adapters.listAdapters();
let discovered: unknown[] = [];
try { discovered = await adapters.discoverAgents(); } catch { /* ok */ }
return { adapters: adapterList, discovered };
}
case 'spawn': {
const [agentId, ...rest] = args.split(/\s+/);
if (!agentId) throw new Error('Usage: spawn [input]');
return adapters.executeAgent(
agentId,
{ action: 'execute', params: { input: rest.join(' ') || 'execute' } },
{ agentId: 'pipe-agent', taskId: `pipe-${Date.now()}`, sessionId: 'pipe' },
);
}
case 'exec': {
if (!args) throw new Error('Usage: exec ');
// Validate against the sandbox policy allowlist before dispatch.
// This ensures pipe-mode callers cannot bypass the command allowlist.
if (!ctx.runtime.policy.isCommandAllowed(args)) {
throw new Error(`Command not in allowlist: ${args}`);
}
return runtime.exec(args, 'pipe-agent');
}
case 'health': {
return adapters.healthCheck();
}
case 'audit': {
const count = parseInt(args) || 10;
return { entries: runtime.getAuditLog().slice(-count) };
}
default:
throw new Error(`Unknown command: ${command}`);
}
}
main().catch((err) => {
console.error('Fatal:', err);
process.exit(1);
});