aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
1,109 lines (882 loc) • 32.3 kB
Markdown
# collaborative-edit
## Execution Modes
**Choose your execution mode:**
### 1. YOLO Mode - Fast, Autonomous (0-1 prompts)
- Autonomous decision making with logging
- Minimal user interaction
- **Best for:** Simple, deterministic tasks
### 2. Interactive Mode - Balanced, Educational (5-10 prompts) **[DEFAULT]**
- Explicit decision checkpoints
- Educational explanations
- **Best for:** Learning, complex decisions
### 3. Pre-Flight Planning - Comprehensive Upfront Planning
- Task analysis phase (identify all ambiguities)
- Zero ambiguity execution
- **Best for:** Ambiguous requirements, critical work
**Parameter:** `mode` (optional, default: `interactive`)
## Task Definition (AIOS Task Format V1.0)
```yaml
task: collaborativeEdit()
responsável: River (Facilitator)
responsavel_type: Agente
atomic_layer: Molecule
**Entrada:**
- campo: task
tipo: string
origem: User Input
obrigatório: true
validação: Must be registered task
- campo: parameters
tipo: object
origem: User Input
obrigatório: false
validação: Valid task parameters
- campo: mode
tipo: string
origem: User Input
obrigatório: false
validação: yolo|interactive|pre-flight
**Saída:**
- campo: execution_result
tipo: object
destino: Memory
persistido: false
- campo: logs
tipo: array
destino: File (.ai/logs/*)
persistido: true
- campo: state
tipo: object
destino: State management
persistido: true
```
## Pre-Conditions
**Purpose:** Validate prerequisites BEFORE task execution (blocking)
**Checklist:**
```yaml
pre-conditions:
- [ ] Task is registered; required parameters provided; dependencies met
tipo: pre-condition
blocker: true
validação: |
Check task is registered; required parameters provided; dependencies met
error_message: "Pre-condition failed: Task is registered; required parameters provided; dependencies met"
```
## Post-Conditions
**Purpose:** Validate execution success AFTER task completes
**Checklist:**
```yaml
post-conditions:
- [ ] Task completed; exit code 0; expected outputs created
tipo: post-condition
blocker: true
validação: |
Verify task completed; exit code 0; expected outputs created
error_message: "Post-condition failed: Task completed; exit code 0; expected outputs created"
```
## Acceptance Criteria
**Purpose:** Definitive pass/fail criteria for task completion
**Checklist:**
```yaml
acceptance-criteria:
- [ ] Task completed as expected; side effects documented
tipo: acceptance-criterion
blocker: true
validação: |
Assert task completed as expected; side effects documented
error_message: "Acceptance criterion not met: Task completed as expected; side effects documented"
```
## Tools
**External/shared resources used by this task:**
- **Tool:** task-runner
- **Purpose:** Task execution and orchestration
- **Source:** .aios-core/core/task-runner.js
- **Tool:** logger
- **Purpose:** Execution logging and error tracking
- **Source:** .aios-core/utils/logger.js
## Scripts
**Agent-specific code for this task:**
- **Script:** execute-task.js
- **Purpose:** Generic task execution wrapper
- **Language:** JavaScript
- **Location:** .aios-core/scripts/execute-task.js
## Error Handling
**Strategy:** retry
**Common Errors:**
1. **Error:** Task Not Found
- **Cause:** Specified task not registered in system
- **Resolution:** Verify task name and registration
- **Recovery:** List available tasks, suggest similar
2. **Error:** Invalid Parameters
- **Cause:** Task parameters do not match expected schema
- **Resolution:** Validate parameters against task definition
- **Recovery:** Provide parameter template, reject execution
3. **Error:** Execution Timeout
- **Cause:** Task exceeds maximum execution time
- **Resolution:** Optimize task or increase timeout
- **Recovery:** Kill task, cleanup resources, log state
## Performance
**Expected Metrics:**
```yaml
duration_expected: 2-5 min (estimated)
cost_estimated: $0.001-0.003
token_usage: ~1,000-3,000 tokens
```
**Optimization Notes:**
- Parallelize independent operations; reuse atom results; implement early exits
## Metadata
```yaml
story: N/A
version: 1.0.0
dependencies:
- N/A
tags:
- automation
- workflow
updated_at: 2025-11-17
```
# No checklists needed - this task manages real-time collaborative editing sessions, no document validation required
tools:
- github-cli
# Collaborative Edit - AIOS Developer Task
## Purpose
Create and manage collaborative editing sessions for real-time component modification with multiple participants.
## Command Pattern
```
*collaborative-edit <action> [options]
```
## Actions
- `start`: Start a new collaborative editing session
- `join`: Join an existing session
- `leave`: Leave current session
- `end`: End a collaborative session
- `status`: Check session status
## Parameters
### Start Session
- `--component <path>`: Component to edit collaboratively
- `--participants <users>`: Initial participants (comma-separated)
- `--mode <mode>`: Editing mode (live, turn-based, review)
- `--timeout <minutes>`: Session timeout
### Join Session
- `--session-id <id>`: Session ID to join
- `--role <role>`: Participant role (editor, reviewer, observer)
## Examples
```bash
# Start collaborative editing session
*collaborative-edit start --component aios-core/agents/data-agent.md --participants alice,bob --mode live
# Join existing session
*collaborative-edit join --session-id session-1234567890 --role editor
# Check session status
*collaborative-edit status --session-id session-1234567890
# End session and merge changes
*collaborative-edit end --session-id session-1234567890 --merge-strategy collaborative
```
## Implementation
```javascript
const fs = require('fs').promises;
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const EventEmitter = require('events');
class CollaborativeEditTask extends EventEmitter {
constructor() {
super();
this.taskName = 'collaborative-edit';
this.description = 'Manage collaborative editing sessions';
this.rootPath = process.cwd();
this.sessionDir = path.join(this.rootPath, '.aios', 'sessions');
this.synchronizer = null;
this.conflictManager = null;
this.currentSession = null;
this.editHistory = [];
}
async execute(params) {
try {
console.log(chalk.blue('👥 AIOS Collaborative Editing'));
console.log(chalk.gray('Real-time collaborative modification system\n'));
// Parse action and parameters
const { action, config } = await this.parseParameters(params);
// Initialize dependencies
await this.initializeDependencies();
// Execute action
let result;
switch (action) {
case 'start':
result = await this.startSession(config);
break;
case 'join':
result = await this.joinSession(config);
break;
case 'leave':
result = await this.leaveSession(config);
break;
case 'end':
result = await this.endSession(config);
break;
case 'status':
result = await this.getSessionStatus(config);
break;
default:
throw new Error(`Unknown action: ${action}`);
}
return result;
} catch (error) {
console.error(chalk.red(`\n❌ Collaborative edit failed: ${error.message}`));
throw error;
}
}
async parseParameters(params) {
if (params.length < 1) {
throw new Error('Usage: *collaborative-edit <action> [options]');
}
const action = params[0];
const config = {
sessionId: null,
componentPath: null,
participants: [],
mode: 'live',
timeout: 60,
role: 'editor',
mergeStrategy: 'collaborative'
};
// Parse options
for (let i = 1; i < params.length; i++) {
const param = params[i];
if (param.startsWith('--session-id') && params[i + 1]) {
config.sessionId = params[++i];
} else if (param.startsWith('--component') && params[i + 1]) {
config.componentPath = params[++i];
} else if (param.startsWith('--participants') && params[i + 1]) {
config.participants = params[++i].split(',').map(p => p.trim());
} else if (param.startsWith('--mode') && params[i + 1]) {
config.mode = params[++i];
} else if (param.startsWith('--timeout') && params[i + 1]) {
config.timeout = parseInt(params[++i]);
} else if (param.startsWith('--role') && params[i + 1]) {
config.role = params[++i];
} else if (param.startsWith('--merge-strategy') && params[i + 1]) {
config.mergeStrategy = params[++i];
}
}
// Validate action
const validActions = ['start', 'join', 'leave', 'end', 'status'];
if (!validActions.includes(action)) {
throw new Error(`Invalid action: ${action}. Must be one of: ${validActions.join(', ')}`);
}
return { action, config };
}
async initializeDependencies() {
// const ModificationSynchronizer = require('../scripts/modification-synchronizer'); // Archived in archived-utilities/ (Story 3.1.3)
// this.synchronizer = new ModificationSynchronizer({ rootPath: this.rootPath }); // Archived in archived-utilities/ (Story 3.1.3)
// await this.synchronizer.initialize(); // Archived in archived-utilities/ (Story 3.1.3)
// const ConflictManager = require('../scripts/conflict-manager'); // Archived in archived-utilities/ (Story 3.1.2)
// this.conflictManager = new ConflictManager({ rootPath: this.rootPath }); // Archived in archived-utilities/ (Story 3.1.2)
await fs.mkdir(this.sessionDir, { recursive: true });
}
async startSession(config) {
console.log(chalk.blue('🚀 Starting collaborative session...'));
// Validate component exists
const component = await this.validateComponent(config.componentPath);
// Generate session ID
const sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`;
// Create session
const session = {
id: sessionId,
componentPath: config.componentPath,
componentType: component.type,
participants: [
{
id: process.env.USER || 'initiator',
role: 'owner',
joinedAt: new Date().toISOString(),
status: 'active'
}
],
mode: config.mode,
status: 'active',
createdAt: new Date().toISOString(),
timeout: config.timeout,
editHistory: [],
locks: new Map(),
metadata: {
originalContent: component.content,
currentContent: component.content,
version: 0
}
};
// Add initial participants
for (const participant of config.participants) {
if (participant !== session.participants[0].id) {
session.participants.push({
id: participant,
role: 'editor',
joinedAt: new Date().toISOString(),
status: 'invited'
});
}
}
// Save session
await this.saveSession(session);
// Start synchronization
await this.synchronizer.startSync();
// Create collaborative workspace
await this.createCollaborativeWorkspace(session);
// Set as current session
this.currentSession = session;
// Start real-time monitoring
this.startRealtimeMonitoring(session);
console.log(chalk.green('\n✅ Collaborative session created'));
console.log(chalk.gray(` Session ID: ${sessionId}`));
console.log(chalk.gray(` Component: ${config.componentPath}`));
console.log(chalk.gray(` Mode: ${config.mode}`));
console.log(chalk.gray(` Participants: ${session.participants.length}`));
if (config.mode === 'live') {
console.log(chalk.blue('\n📡 Live editing enabled - changes sync in real-time'));
}
return {
success: true,
sessionId: sessionId,
componentPath: config.componentPath,
participants: session.participants,
mode: config.mode,
workspace: await this.getWorkspaceInfo(session)
};
}
async joinSession(config) {
if (!config.sessionId) {
throw new Error('Session ID required to join');
}
console.log(chalk.blue(`🔗 Joining session ${config.sessionId}...`));
// Load session
const session = await this.loadSession(config.sessionId);
if (session.status !== 'active') {
throw new Error('Session is not active');
}
// Check if already participant
const participantId = process.env.USER || 'user';
let participant = session.participants.find(p => p.id === participantId);
if (!participant) {
// Add new participant
participant = {
id: participantId,
role: config.role,
joinedAt: new Date().toISOString(),
status: 'active'
};
session.participants.push(participant);
} else {
// Update existing participant
participant.status = 'active';
participant.role = config.role;
}
// Update session
await this.saveSession(session);
// Set as current session
this.currentSession = session;
// Subscribe to updates
await this.subscribeToSessionUpdates(session);
// Load current state
const workspace = await this.loadWorkspace(session);
console.log(chalk.green('\n✅ Joined collaborative session'));
console.log(chalk.gray(` Role: ${config.role}`));
console.log(chalk.gray(` Active participants: ${session.participants.filter(p => p.status === 'active').length}`));
console.log(chalk.gray(` Current version: ${session.metadata.version}`));
// Show current editing status
if (session.mode === 'turn-based') {
const currentEditor = this.getCurrentEditor(session);
console.log(chalk.yellow(`\n⏳ Current editor: ${currentEditor || 'None'}`));
}
return {
success: true,
sessionId: session.id,
role: config.role,
workspace: workspace,
participants: session.participants.filter(p => p.status === 'active')
};
}
async leaveSession(config) {
const sessionId = config.sessionId || (this.currentSession && this.currentSession.id);
if (!sessionId) {
throw new Error('No active session to leave');
}
console.log(chalk.blue(`👋 Leaving session ${sessionId}...`));
// Load session
const session = await this.loadSession(sessionId);
// Update participant status
const participantId = process.env.USER || 'user';
const participant = session.participants.find(p => p.id === participantId);
if (participant) {
participant.status = 'left';
participant.leftAt = new Date().toISOString();
}
// Save any pending changes
if (this.currentSession && this.editHistory.length > 0) {
await this.saveEditHistory(session);
}
// Update session
await this.saveSession(session);
// Clear current session
this.currentSession = null;
console.log(chalk.green('✅ Left collaborative session'));
return {
success: true,
sessionId: sessionId
};
}
async endSession(config) {
if (!config.sessionId) {
throw new Error('Session ID required to end session');
}
console.log(chalk.blue(`🏁 Ending session ${config.sessionId}...`));
// Load session
const session = await this.loadSession(config.sessionId);
// Check permissions
const participantId = process.env.USER || 'user';
const participant = session.participants.find(p => p.id === participantId);
if (!participant || participant.role !== 'owner') {
throw new Error('Only session owner can end the session');
}
// Finalize all pending edits
await this.finalizeEdits(session);
// Merge changes
console.log(chalk.gray('Merging collaborative changes...'));
const mergeResult = await this.mergeCollaborativeChanges(session, config.mergeStrategy);
// Update session status
session.status = 'completed';
session.endedAt = new Date().toISOString();
session.mergeResult = mergeResult;
// Save final session state
await this.saveSession(session);
// Stop synchronization
await this.synchronizer.stopSync();
// Archive session
await this.archiveSession(session);
console.log(chalk.green('\n✅ Collaborative session ended'));
console.log(chalk.gray(` Total edits: ${session.editHistory.length}`));
console.log(chalk.gray(` Participants: ${session.participants.length}`));
console.log(chalk.gray(` Duration: ${this.calculateDuration(session)}`));
if (mergeResult.success) {
console.log(chalk.green(` Changes merged successfully`));
}
return {
success: true,
sessionId: session.id,
mergeResult: mergeResult,
statistics: this.getSessionStatistics(session)
};
}
async getSessionStatus(config) {
const sessionId = config.sessionId || (this.currentSession && this.currentSession.id);
if (!sessionId) {
// Show all active sessions
return await this.listActiveSessions();
}
// Load specific session
const session = await this.loadSession(sessionId);
console.log(chalk.blue('\n📊 Session Status'));
console.log(chalk.gray('━'.repeat(50)));
console.log(`ID: ${chalk.white(session.id)}`);
console.log(`Component: ${chalk.white(session.componentPath)}`);
console.log(`Status: ${this.formatStatus(session.status)}`);
console.log(`Mode: ${chalk.white(session.mode)}`);
console.log(`Created: ${chalk.white(new Date(session.createdAt).toLocaleString())}`);
console.log(chalk.blue('\n👥 Participants:'));
session.participants.forEach(p => {
const status = p.status === 'active' ? chalk.green('●') : chalk.gray('○');
console.log(` ${status} ${p.id} (${p.role})`);
});
console.log(chalk.blue('\n📝 Edit History:'));
console.log(` Total edits: ${session.editHistory.length}`);
console.log(` Current version: ${session.metadata.version}`);
if (session.editHistory.length > 0) {
const recentEdits = session.editHistory.slice(-5);
console.log(' Recent edits:');
recentEdits.forEach(edit => {
console.log(` - ${edit.author} at ${new Date(edit.timestamp).toLocaleTimeString()}`);
});
}
if (session.mode === 'turn-based') {
const currentEditor = this.getCurrentEditor(session);
console.log(chalk.yellow(`\n⏳ Current turn: ${currentEditor || 'None'}`));
}
return {
success: true,
session: session,
activeParticipants: session.participants.filter(p => p.status === 'active').length,
isCurrentSession: sessionId === (this.currentSession && this.currentSession.id)
};
}
// Helper methods
async validateComponent(componentPath) {
const fullPath = path.resolve(this.rootPath, componentPath);
try {
const stats = await fs.stat(fullPath);
if (!stats.isFile()) {
throw new Error('Component path must be a file');
}
const content = await fs.readFile(fullPath, 'utf-8');
const type = this.detectComponentType(fullPath);
return {
path: componentPath,
fullPath: fullPath,
type: type,
content: content,
size: stats.size
};
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`Component not found: ${componentPath}`);
}
throw error;
}
}
detectComponentType(filePath) {
if (filePath.includes('/agents/')) return 'agent';
if (filePath.includes('/tasks/')) return 'task';
if (filePath.includes('/workflows/')) return 'workflow';
if (filePath.includes('/utils/')) return 'util';
return 'unknown';
}
async saveSession(session) {
const sessionFile = path.join(this.sessionDir, `${session.id}.json`);
await fs.writeFile(sessionFile, JSON.stringify(session, null, 2));
}
async loadSession(sessionId) {
const sessionFile = path.join(this.sessionDir, `${sessionId}.json`);
try {
const content = await fs.readFile(sessionFile, 'utf-8');
return JSON.parse(content);
} catch (error) {
if (error.code === 'ENOENT') {
throw new Error(`Session not found: ${sessionId}`);
}
throw error;
}
}
async createCollaborativeWorkspace(session) {
const workspaceDir = path.join(this.sessionDir, session.id, 'workspace');
await fs.mkdir(workspaceDir, { recursive: true });
// Create working copy
const workingFile = path.join(workspaceDir, 'working.txt');
await fs.writeFile(workingFile, session.metadata.originalContent);
// Create version history
const versionDir = path.join(workspaceDir, 'versions');
await fs.mkdir(versionDir, { recursive: true });
const v0File = path.join(versionDir, 'v0.txt');
await fs.writeFile(v0File, session.metadata.originalContent);
// Create edit log
const editLog = path.join(workspaceDir, 'edits.jsonl');
await fs.writeFile(editLog, '');
return workspaceDir;
}
async loadWorkspace(session) {
const workspaceDir = path.join(this.sessionDir, session.id, 'workspace');
const workingFile = path.join(workspaceDir, 'working.txt');
const currentContent = await fs.readFile(workingFile, 'utf-8');
return {
workspaceDir: workspaceDir,
currentContent: currentContent,
version: session.metadata.version,
lastEdit: session.editHistory.length > 0 ?
session.editHistory[session.editHistory.length - 1] : null
};
}
startRealtimeMonitoring(session) {
if (session.mode !== 'live') return;
// Set up file watcher for workspace
const workspaceDir = path.join(this.sessionDir, session.id, 'workspace');
const workingFile = path.join(workspaceDir, 'working.txt');
// Monitor for changes
let lastContent = session.metadata.currentContent;
this.monitorInterval = setInterval(async () => {
try {
const currentContent = await fs.readFile(workingFile, 'utf-8');
if (currentContent !== lastContent) {
// Detect and broadcast changes
const edit = {
id: `edit-${Date.now()}`,
author: process.env.USER || 'unknown',
timestamp: new Date().toISOString(),
type: 'content_change',
diff: this.generateDiff(lastContent, currentContent)
};
await this.broadcastEdit(session, edit);
lastContent = currentContent;
}
} catch (error) {
// Workspace may have been cleaned up
}
}, 1000); // Check every second
}
async subscribeToSessionUpdates(session) {
// Subscribe to edit broadcasts
const editLog = path.join(this.sessionDir, session.id, 'workspace', 'edits.jsonl');
// Watch for new edits
let lastSize = 0;
this.watchInterval = setInterval(async () => {
try {
const stats = await fs.stat(editLog);
if (stats.size > lastSize) {
// Read new edits
const content = await fs.readFile(editLog, 'utf-8');
const lines = content.trim().split('\n');
const newEdits = lines.slice(this.editHistory.length);
for (const line of newEdits) {
if (line) {
const edit = JSON.parse(line);
await this.applyRemoteEdit(session, edit);
this.editHistory.push(edit);
}
}
lastSize = stats.size;
}
} catch (error) {
// Edit log may not exist yet
}
}, 500); // Check every 500ms
}
async broadcastEdit(session, edit) {
// Add to session history
session.editHistory.push(edit);
session.metadata.version++;
// Write to edit log
const editLog = path.join(this.sessionDir, session.id, 'workspace', 'edits.jsonl');
await fs.appendFile(editLog, JSON.stringify(edit) + '\n');
// Save session state
await this.saveSession(session);
// Emit event
this.emit('edit_broadcast', { sessionId: session.id, edit });
}
async applyRemoteEdit(session, edit) {
if (edit.author === (process.env.USER || 'unknown')) {
return; // Skip own edits
}
console.log(chalk.gray(`📝 ${edit.author} made changes`));
// Apply edit to workspace
const workingFile = path.join(this.sessionDir, session.id, 'workspace', 'working.txt');
if (edit.type === 'content_change' && edit.content) {
await fs.writeFile(workingFile, edit.content);
}
}
generateDiff(oldContent, newContent) {
// Simple diff generation
const oldLines = oldContent.split('\n');
const newLines = newContent.split('\n');
const diff = {
additions: 0,
deletions: 0,
changes: []
};
// Compare lines
const maxLines = Math.max(oldLines.length, newLines.length);
for (let i = 0; i < maxLines; i++) {
if (oldLines[i] !== newLines[i]) {
if (i >= oldLines.length) {
diff.additions++;
diff.changes.push({ type: 'add', line: i, content: newLines[i] });
} else if (i >= newLines.length) {
diff.deletions++;
diff.changes.push({ type: 'delete', line: i, content: oldLines[i] });
} else {
diff.changes.push({ type: 'modify', line: i, old: oldLines[i], new: newLines[i] });
}
}
}
return diff;
}
getCurrentEditor(session) {
if (session.mode !== 'turn-based') return null;
// Find current turn holder
const activeTurn = session.editHistory.find(edit =>
edit.type === 'turn_taken' && !edit.completed
);
return activeTurn ? activeTurn.author : null;
}
async saveEditHistory(session) {
// Save current edit state
const workspaceDir = path.join(this.sessionDir, session.id, 'workspace');
const versionDir = path.join(workspaceDir, 'versions');
const versionFile = path.join(versionDir, `v${session.metadata.version}.txt`);
const workingFile = path.join(workspaceDir, 'working.txt');
const content = await fs.readFile(workingFile, 'utf-8');
await fs.writeFile(versionFile, content);
}
async finalizeEdits(session) {
// Ensure all edits are saved
await this.saveEditHistory(session);
// Clear any locks
session.locks = new Map();
// Mark all participants as inactive
session.participants.forEach(p => {
if (p.status === 'active') {
p.status = 'completed';
}
});
}
async mergeCollaborativeChanges(session, strategy) {
const result = {
success: false,
finalContent: null,
mergeDetails: []
};
try {
const workingFile = path.join(this.sessionDir, session.id, 'workspace', 'working.txt');
const finalContent = await fs.readFile(workingFile, 'utf-8');
// Apply to actual component
const componentPath = path.resolve(this.rootPath, session.componentPath);
await fs.writeFile(componentPath, finalContent);
result.success = true;
result.finalContent = finalContent;
result.mergeDetails.push(`Applied final collaborative edits using ${strategy} strategy`);
} catch (error) {
result.mergeDetails.push(`Merge failed: ${error.message}`);
}
return result;
}
calculateDuration(session) {
const start = new Date(session.createdAt);
const end = session.endedAt ? new Date(session.endedAt) : new Date();
const duration = end - start;
const minutes = Math.floor(duration / 60000);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
}
return `${minutes}m`;
}
async archiveSession(session) {
const archiveDir = path.join(this.sessionDir, 'archive');
await fs.mkdir(archiveDir, { recursive: true });
const archiveFile = path.join(archiveDir, `${session.id}.json`);
await fs.writeFile(archiveFile, JSON.stringify(session, null, 2));
// Clean up active session files
const sessionFile = path.join(this.sessionDir, `${session.id}.json`);
await fs.unlink(sessionFile);
}
async listActiveSessions() {
console.log(chalk.blue('\n📋 Active Collaborative Sessions'));
console.log(chalk.gray('━'.repeat(50)));
const files = await fs.readdir(this.sessionDir);
const sessions = [];
for (const file of files) {
if (file.endsWith('.json') && !file.startsWith('archive')) {
try {
const session = await this.loadSession(file.replace('.json', ''));
if (session.status === 'active') {
sessions.push(session);
}
} catch (error) {
// Skip invalid files
}
}
}
if (sessions.length === 0) {
console.log(chalk.gray('No active sessions'));
} else {
sessions.forEach(session => {
console.log(`\n${chalk.white(session.id)}`);
console.log(` Component: ${session.componentPath}`);
console.log(` Mode: ${session.mode}`);
console.log(` Participants: ${session.participants.filter(p => p.status === 'active').length}`);
console.log(` Started: ${new Date(session.createdAt).toLocaleString()}`);
});
}
return {
success: true,
activeSessions: sessions.length,
sessions: sessions.map(s => ({
id: s.id,
component: s.componentPath,
participants: s.participants.length,
mode: s.mode
}))
};
}
getSessionStatistics(session) {
const stats = {
totalEdits: session.editHistory.length,
participants: session.participants.length,
activeParticipants: session.participants.filter(p => p.status === 'active').length,
duration: this.calculateDuration(session),
editsByAuthor: {}
};
// Count edits by author
session.editHistory.forEach(edit => {
if (!stats.editsByAuthor[edit.author]) {
stats.editsByAuthor[edit.author] = 0;
}
stats.editsByAuthor[edit.author]++;
});
return stats;
}
formatStatus(status) {
const statusMap = {
active: chalk.green('ACTIVE'),
completed: chalk.blue('COMPLETED'),
cancelled: chalk.red('CANCELLED'),
error: chalk.red('ERROR')
};
return statusMap[status] || status;
}
// Cleanup
cleanup() {
if (this.monitorInterval) {
clearInterval(this.monitorInterval);
}
if (this.watchInterval) {
clearInterval(this.watchInterval);
}
}
}
module.exports = CollaborativeEditTask;
```
## Validation Rules
### Session Management
- Only one active session per component allowed
- Session owner has administrative privileges
- Participants must be invited or have appropriate permissions
- Sessions timeout after specified duration
- All edits must be tracked and versioned
### Editing Modes
- **Live**: Real-time collaborative editing with instant sync
- **Turn-based**: Sequential editing with explicit turn management
- **Review**: Changes require approval before applying
### Conflict Prevention
- Automatic locking for turn-based editing
- Real-time conflict detection for live editing
- Version tracking for all changes
- Rollback capability for problematic edits
## Integration Points
### Modification Synchronizer
- Handles real-time synchronization of edits
- Manages edit broadcasting and receiving
- Tracks version consistency
- Handles network interruptions
### Conflict Manager
- Detects and resolves editing conflicts
- Manages locks and turn-based access
- Handles merge strategies
- Provides conflict visualization
### Notification Service
- Notifies participants of session events
- Alerts on turn changes
- Broadcasts edit notifications
- Handles session invitations
## Security Considerations
- Validate participant permissions
- Encrypt sensitive session data
- Audit all collaborative actions
- Prevent unauthorized access to sessions
- Secure communication channels