agentic-qe
Version:
Agentic Quality Engineering Fleet System - AI-driven quality management platform
250 lines • 9.54 kB
JavaScript
;
/**
* Agent Detach Command
*
* Detaches from an active agent console session, cleaning up resources
* and saving session data. Supports graceful detachment with statistics.
*
* @module cli/commands/agent/detach
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentDetachCommand = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const attach_1 = require("./attach");
const Logger_1 = require("../../../utils/Logger");
const logger = Logger_1.Logger.getInstance();
/**
* Agent Detach Command Implementation
*/
class AgentDetachCommand {
/**
* Execute agent detach
*
* @param options - Detach options
* @returns Detach result
*/
static async execute(options) {
const { agentId, saveSession = true, showStats = true, force = false } = options;
logger.info(`Detaching from agent: ${agentId}`, { saveSession, force });
try {
// Get active session
const session = attach_1.AgentAttachCommand.getActiveSession(agentId);
if (!session) {
if (force) {
logger.warn(`No active session found for agent: ${agentId}, force detaching`);
return this.createForceDetachResult(agentId);
}
throw new Error(`Not attached to agent: ${agentId}`);
}
// Calculate session duration
const duration = Date.now() - session.attachedAt.getTime();
// Stop monitoring
await this.stopMonitoring(session);
// Update session status
session.status = 'detached';
// Display stats if requested
if (showStats) {
this.displaySessionStats(session, duration);
}
// Save session data
let sessionSaved = false;
if (saveSession) {
await this.archiveSession(session, duration);
sessionSaved = true;
}
// Clean up session metadata
await this.cleanupSession(session);
const result = {
agentId,
sessionId: session.sessionId,
detachedAt: new Date(),
duration,
stats: { ...session.stats },
sessionSaved
};
logger.info(`Detached from agent: ${agentId}`, {
sessionId: session.sessionId,
duration: `${(duration / 1000).toFixed(2)}s`
});
return result;
}
catch (error) {
logger.error(`Failed to detach from agent ${agentId}:`, error);
if (force) {
logger.warn('Force detaching despite error');
return this.createForceDetachResult(agentId);
}
throw new Error(`Agent detach failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Stop monitoring activities
*/
static async stopMonitoring(session) {
// Clear all intervals
const intervals = ['logInterval', 'metricsInterval', 'eventsInterval'];
for (const intervalName of intervals) {
const interval = session[intervalName];
if (interval) {
clearInterval(interval);
delete session[intervalName];
}
}
// Remove all event listeners
session.events.removeAllListeners();
logger.debug(`Stopped monitoring for session: ${session.sessionId}`);
}
/**
* Display session statistics
*/
static displaySessionStats(session, duration) {
console.log('\n' + '='.repeat(60));
console.log('📊 Session Statistics');
console.log('='.repeat(60));
console.log(`Agent ID: ${session.agentId}`);
console.log(`Session ID: ${session.sessionId}`);
console.log(`Duration: ${(duration / 1000).toFixed(2)}s`);
console.log(`Logs Received: ${session.stats.logsReceived}`);
console.log(`Events Received: ${session.stats.eventsReceived}`);
console.log(`Metrics Received: ${session.stats.metricsReceived}`);
console.log('='.repeat(60) + '\n');
}
/**
* Archive session data
*/
static async archiveSession(session, duration) {
await fs.ensureDir(this.ARCHIVE_DIR);
const archivePath = path.join(this.ARCHIVE_DIR, `${session.sessionId}.json`);
const archiveData = {
sessionId: session.sessionId,
agentId: session.agentId,
attachedAt: session.attachedAt.toISOString(),
detachedAt: new Date().toISOString(),
duration,
stats: session.stats,
status: 'completed'
};
await fs.writeJson(archivePath, archiveData, { spaces: 2 });
logger.debug(`Session archived: ${archivePath}`);
}
/**
* Cleanup session metadata
*/
static async cleanupSession(session) {
const sessionPath = path.join(this.SESSIONS_DIR, `${session.sessionId}.json`);
if (await fs.pathExists(sessionPath)) {
await fs.remove(sessionPath);
logger.debug(`Session metadata cleaned: ${sessionPath}`);
}
// Remove from active sessions
// This assumes access to the private activeSessions map
// In production, would need proper API access
}
/**
* Create force detach result
*/
static createForceDetachResult(agentId) {
return {
agentId,
sessionId: 'unknown',
detachedAt: new Date(),
duration: 0,
stats: {
logsReceived: 0,
eventsReceived: 0,
metricsReceived: 0
},
sessionSaved: false
};
}
/**
* Detach all active sessions
*/
static async detachAll(options) {
const activeSessions = attach_1.AgentAttachCommand.getAllActiveSessions();
logger.info(`Detaching from all active sessions (${activeSessions.length})`);
const results = [];
for (const session of activeSessions) {
try {
const result = await this.execute({
agentId: session.agentId,
saveSession: options.saveSession,
showStats: options.showStats,
force: false
});
results.push(result);
}
catch (error) {
logger.error(`Failed to detach from agent ${session.agentId}:`, error);
}
}
return results;
}
/**
* Get archived sessions
*/
static async getArchivedSessions(limit) {
await fs.ensureDir(this.ARCHIVE_DIR);
const files = await fs.readdir(this.ARCHIVE_DIR);
const jsonFiles = files.filter(f => f.endsWith('.json'));
const sessions = await Promise.all(jsonFiles.map(async (file) => {
const filePath = path.join(this.ARCHIVE_DIR, file);
return await fs.readJson(filePath);
}));
// Sort by detached time descending
sessions.sort((a, b) => new Date(b.detachedAt).getTime() - new Date(a.detachedAt).getTime());
return limit ? sessions.slice(0, limit) : sessions;
}
/**
* Clean up old archived sessions
*/
static async cleanupOldSessions(olderThanDays) {
await fs.ensureDir(this.ARCHIVE_DIR);
const files = await fs.readdir(this.ARCHIVE_DIR);
const cutoffTime = Date.now() - (olderThanDays * 24 * 60 * 60 * 1000);
let cleaned = 0;
for (const file of files) {
if (!file.endsWith('.json'))
continue;
const filePath = path.join(this.ARCHIVE_DIR, file);
const session = await fs.readJson(filePath);
const detachedAt = new Date(session.detachedAt).getTime();
if (detachedAt < cutoffTime) {
await fs.remove(filePath);
cleaned++;
}
}
logger.info(`Cleaned up ${cleaned} old archived sessions`);
return cleaned;
}
}
exports.AgentDetachCommand = AgentDetachCommand;
_a = AgentDetachCommand;
AgentDetachCommand.SESSIONS_DIR = path.join(process.cwd(), '.aqe', 'sessions');
AgentDetachCommand.ARCHIVE_DIR = path.join(_a.SESSIONS_DIR, 'archive');
//# sourceMappingURL=detach.js.map