Files
ccs/bin/delegation/delegation-handler.js
T
Kai (Tam Nhu) TranandGitHub 4df5a7d357 feat!: delegation system overhaul and .claude/ shipping (v4.1.0) (#8)
## 🚀 Release v4.1.0 - Major Update

**Breaking changes from v3.5.0**. This release includes the complete v4.0.0 delegation overhaul plus v4.1.0 architecture improvements.

---

## 🎯 v4.0.0: Delegation System Overhaul

**Complete rewrite of the delegation infrastructure with enhanced decision-making and streaming support.**

### New Delegation Features

**Stream-JSON Communication Protocol:**
- Real-time token streaming with `{type: "content", data: "..."}` format
- Progress indicators during delegation execution
- Clean separation: stdout for data, stderr for errors
- Handles tool calls, thinking blocks, and text content

**Enhanced Decision Framework:**
- `/ccs:glm` and `/ccs:kimi` slash commands with auto-enhancement
- `[AUTO ENHANCE]` prompts for better model understanding
- Context-aware task delegation with clear boundaries
- Continuation support: `/ccs:glm:continue` and `/ccs:kimi:continue`

**Robust Error Handling:**
- Graceful degradation when profiles unconfigured
- Clear error messages with actionable fixes
- Signal handling (SIGINT/SIGTERM) for clean child process termination
- Session state recovery on interruption

**Performance & Reliability:**
- Headless mode (`-p` flag) for background execution
- Slash command detection and auto-routing
- Validation system with `DelegationValidator`
- Profile readiness checks in `ccs --version`

### Delegation Components

**New Files:**
- `bin/delegation/delegation-handler.js` - Core delegation orchestrator
- `bin/delegation/stream-processor.js` - Real-time output handling
- `bin/utils/delegation-validator.js` - Profile validation
- `.claude/commands/ccs/*.md` - Slash command definitions
- `.claude/skills/ccs-delegation/` - AI decision framework
- `.claude/agents/ccs-delegator.md` - Proactive delegation agent

**Documentation:**
- Complete delegation workflows with mermaid diagrams
- Troubleshooting guides for common issues
- Headless execution patterns

---

##  v4.1.0: Selective Symlinking Architecture

**Single source of truth for CCS items with automatic propagation.**

### New Architecture

**Ship .claude/ Directory:**
- CCS items now ship with npm/sh/ps1 packages
- Selective item-level symlinks: `~/.ccs/.claude/` → `~/.claude/`
- Auto-propagation on `npm update` - zero manual sync
- Backward compatible with existing `~/.ccs/shared/` mechanism

**Symlink Chain:**
```
~/.ccs/.claude/ (source) 
    ↓ selective symlinks
~/.claude/ (CCS items installed here)
    ↑ symlinked by
~/.ccs/shared/
    ↑ symlinked by  
profiles (work, personal, team)
```

### New Commands

**Maintenance Tools:**
- `ccs update` - Re-install CCS symlinks to ~/.claude/
- `ccs doctor` - Added Check 9: CCS symlinks health verification

**Safe Installation:**
- Automatic conflict backup before symlinking
- Idempotent operations (safe to run multiple times)
- Health monitoring and recovery

### New Components

- `bin/utils/claude-symlink-manager.js` - Manages selective symlinks
- Updated all 3 installers (npm postinstall, install.sh, install.ps1)
- Enhanced `ccs doctor` with symlink health checks

---

## 💥 Breaking Changes

**From v3.5.0 → v4.x:**

1. **Delegation commands moved**: 
   - Old: User manually created in `~/.claude/commands/`
   - New: Auto-shipped in `~/.ccs/.claude/`, symlinked to `~/.claude/commands/ccs/`

2. **Slash command format**:
   - New: `/ccs:glm`, `/ccs:kimi`, `/ccs:glm:continue`
   - Old custom commands may need migration

3. **Profile validation**:
   - Placeholders (`YOUR_API_KEY_HERE`) now detected and marked invalid
   - Must configure real API keys for delegation to work

4. **Stream output format**:
   - Headless mode (`-p`) now uses stream-JSON protocol
   - Old text output replaced with structured `{type, data}` format
2025-11-16 06:31:55 -05:00

213 lines
5.9 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const { HeadlessExecutor } = require('./headless-executor');
const { SessionManager } = require('./session-manager');
const { ResultFormatter } = require('./result-formatter');
const { DelegationValidator } = require('../utils/delegation-validator');
const { SettingsParser } = require('./settings-parser');
/**
* Delegation command handler
* Routes -p flag commands to HeadlessExecutor with enhanced features
*/
class DelegationHandler {
/**
* Route delegation command
* @param {Array<string>} args - Full args array from ccs.js
*/
async route(args) {
try {
// 1. Parse args into { profile, prompt, options }
const parsed = this._parseArgs(args);
// 2. Detect special profiles (glm:continue, kimi:continue)
if (parsed.profile.includes(':continue')) {
return await this._handleContinue(parsed);
}
// 3. Validate profile
this._validateProfile(parsed.profile);
// 4. Execute via HeadlessExecutor
const result = await HeadlessExecutor.execute(
parsed.profile,
parsed.prompt,
parsed.options
);
// 5. Format and display results
const formatted = ResultFormatter.format(result);
console.log(formatted);
// 6. Exit with proper code
process.exit(result.exitCode || 0);
} catch (error) {
console.error(`[X] Delegation error: ${error.message}`);
if (process.env.CCS_DEBUG) {
console.error(error.stack);
}
process.exit(1);
}
}
/**
* Handle continue command (resume last session)
* @param {Object} parsed - Parsed args
*/
async _handleContinue(parsed) {
const baseProfile = parsed.profile.replace(':continue', '');
// Get last session from SessionManager
const sessionMgr = new SessionManager();
const lastSession = sessionMgr.getLastSession(baseProfile);
if (!lastSession) {
console.error(`[X] No previous session found for ${baseProfile}`);
console.error(` Start a new session first with: ccs ${baseProfile} -p "task"`);
process.exit(1);
}
// Execute with resume flag
const result = await HeadlessExecutor.execute(
baseProfile,
parsed.prompt,
{
...parsed.options,
resumeSession: true,
sessionId: lastSession.sessionId
}
);
const formatted = ResultFormatter.format(result);
console.log(formatted);
process.exit(result.exitCode || 0);
}
/**
* Parse args into structured format
* @param {Array<string>} args - Raw args
* @returns {Object} { profile, prompt, options }
*/
_parseArgs(args) {
// Extract profile (first non-flag arg or 'default')
const profile = this._extractProfile(args);
// Extract prompt from -p or --prompt
const prompt = this._extractPrompt(args);
// Extract options (--timeout, --permission-mode, etc.)
const options = this._extractOptions(args);
return { profile, prompt, options };
}
/**
* Extract profile from args (first non-flag arg)
* @param {Array<string>} args - Args array
* @returns {string} Profile name
*/
_extractProfile(args) {
// Find first arg that doesn't start with '-' and isn't -p value
let skipNext = false;
for (let i = 0; i < args.length; i++) {
if (skipNext) {
skipNext = false;
continue;
}
if (args[i] === '-p' || args[i] === '--prompt') {
skipNext = true;
continue;
}
if (!args[i].startsWith('-')) {
return args[i];
}
}
// No profile specified, return null (will error in validation)
return null;
}
/**
* Extract prompt from -p flag
* @param {Array<string>} args - Args array
* @returns {string} Prompt text
*/
_extractPrompt(args) {
const pIndex = args.indexOf('-p');
const promptIndex = args.indexOf('--prompt');
const index = pIndex !== -1 ? pIndex : promptIndex;
if (index === -1 || index === args.length - 1) {
console.error('[X] Missing prompt after -p flag');
console.error(' Usage: ccs glm -p "task description"');
process.exit(1);
}
return args[index + 1];
}
/**
* Extract options from remaining args
* @param {Array<string>} args - Args array
* @returns {Object} Options for HeadlessExecutor
*/
_extractOptions(args) {
const cwd = process.cwd();
// Read default permission mode from .claude/settings.local.json
// Falls back to 'acceptEdits' if file doesn't exist
const defaultPermissionMode = SettingsParser.parseDefaultPermissionMode(cwd);
const options = {
cwd,
outputFormat: 'stream-json',
permissionMode: defaultPermissionMode
};
// Parse permission-mode (CLI flag overrides settings file)
const permModeIndex = args.indexOf('--permission-mode');
if (permModeIndex !== -1 && permModeIndex < args.length - 1) {
options.permissionMode = args[permModeIndex + 1];
}
// Parse timeout
const timeoutIndex = args.indexOf('--timeout');
if (timeoutIndex !== -1 && timeoutIndex < args.length - 1) {
options.timeout = parseInt(args[timeoutIndex + 1], 10);
}
return options;
}
/**
* Validate profile exists and is configured
* @param {string} profile - Profile name
*/
_validateProfile(profile) {
if (!profile) {
console.error('[X] No profile specified');
console.error(' Usage: ccs <profile> -p "task"');
console.error(' Examples: ccs glm -p "task", ccs kimi -p "task"');
process.exit(1);
}
// Use DelegationValidator to check profile
const validation = DelegationValidator.validate(profile);
if (!validation.valid) {
console.error(`[X] Profile '${profile}' is not configured for delegation`);
console.error(` ${validation.error}`);
console.error('');
console.error(' Run: ccs doctor');
console.error(' Or configure: ~/.ccs/${profile}.settings.json');
process.exit(1);
}
}
}
module.exports = DelegationHandler;