mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
feat: orchestration harness, selective install, observer improvements
This commit is contained in:
138
scripts/lib/session-adapters/canonical-session.js
Normal file
138
scripts/lib/session-adapters/canonical-session.js
Normal file
@@ -0,0 +1,138 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const SESSION_SCHEMA_VERSION = 'ecc.session.v1';
|
||||
|
||||
function buildAggregates(workers) {
|
||||
const states = workers.reduce((accumulator, worker) => {
|
||||
const state = worker.state || 'unknown';
|
||||
accumulator[state] = (accumulator[state] || 0) + 1;
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
workerCount: workers.length,
|
||||
states
|
||||
};
|
||||
}
|
||||
|
||||
function deriveDmuxSessionState(snapshot) {
|
||||
if (snapshot.sessionActive) {
|
||||
return 'active';
|
||||
}
|
||||
|
||||
if (snapshot.workerCount > 0) {
|
||||
return 'idle';
|
||||
}
|
||||
|
||||
return 'missing';
|
||||
}
|
||||
|
||||
function normalizeDmuxSnapshot(snapshot, sourceTarget) {
|
||||
const workers = (snapshot.workers || []).map(worker => ({
|
||||
id: worker.workerSlug,
|
||||
label: worker.workerSlug,
|
||||
state: worker.status.state || 'unknown',
|
||||
branch: worker.status.branch || null,
|
||||
worktree: worker.status.worktree || null,
|
||||
runtime: {
|
||||
kind: 'tmux-pane',
|
||||
command: worker.pane ? worker.pane.currentCommand || null : null,
|
||||
pid: worker.pane ? worker.pane.pid || null : null,
|
||||
active: worker.pane ? Boolean(worker.pane.active) : false,
|
||||
dead: worker.pane ? Boolean(worker.pane.dead) : false,
|
||||
},
|
||||
intent: {
|
||||
objective: worker.task.objective || '',
|
||||
seedPaths: Array.isArray(worker.task.seedPaths) ? worker.task.seedPaths : []
|
||||
},
|
||||
outputs: {
|
||||
summary: Array.isArray(worker.handoff.summary) ? worker.handoff.summary : [],
|
||||
validation: Array.isArray(worker.handoff.validation) ? worker.handoff.validation : [],
|
||||
remainingRisks: Array.isArray(worker.handoff.remainingRisks) ? worker.handoff.remainingRisks : []
|
||||
},
|
||||
artifacts: {
|
||||
statusFile: worker.files.status,
|
||||
taskFile: worker.files.task,
|
||||
handoffFile: worker.files.handoff
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
schemaVersion: SESSION_SCHEMA_VERSION,
|
||||
adapterId: 'dmux-tmux',
|
||||
session: {
|
||||
id: snapshot.sessionName,
|
||||
kind: 'orchestrated',
|
||||
state: deriveDmuxSessionState(snapshot),
|
||||
repoRoot: snapshot.repoRoot || null,
|
||||
sourceTarget
|
||||
},
|
||||
workers,
|
||||
aggregates: buildAggregates(workers)
|
||||
};
|
||||
}
|
||||
|
||||
function deriveClaudeWorkerId(session) {
|
||||
if (session.shortId && session.shortId !== 'no-id') {
|
||||
return session.shortId;
|
||||
}
|
||||
|
||||
return path.basename(session.filename || session.sessionPath || 'session', '.tmp');
|
||||
}
|
||||
|
||||
function normalizeClaudeHistorySession(session, sourceTarget) {
|
||||
const metadata = session.metadata || {};
|
||||
const workerId = deriveClaudeWorkerId(session);
|
||||
const worker = {
|
||||
id: workerId,
|
||||
label: metadata.title || session.filename || workerId,
|
||||
state: 'recorded',
|
||||
branch: metadata.branch || null,
|
||||
worktree: metadata.worktree || null,
|
||||
runtime: {
|
||||
kind: 'claude-session',
|
||||
command: 'claude',
|
||||
pid: null,
|
||||
active: false,
|
||||
dead: true,
|
||||
},
|
||||
intent: {
|
||||
objective: metadata.inProgress && metadata.inProgress.length > 0
|
||||
? metadata.inProgress[0]
|
||||
: (metadata.title || ''),
|
||||
seedPaths: []
|
||||
},
|
||||
outputs: {
|
||||
summary: Array.isArray(metadata.completed) ? metadata.completed : [],
|
||||
validation: [],
|
||||
remainingRisks: metadata.notes ? [metadata.notes] : []
|
||||
},
|
||||
artifacts: {
|
||||
sessionFile: session.sessionPath,
|
||||
context: metadata.context || null
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
schemaVersion: SESSION_SCHEMA_VERSION,
|
||||
adapterId: 'claude-history',
|
||||
session: {
|
||||
id: workerId,
|
||||
kind: 'history',
|
||||
state: 'recorded',
|
||||
repoRoot: metadata.worktree || null,
|
||||
sourceTarget
|
||||
},
|
||||
workers: [worker],
|
||||
aggregates: buildAggregates([worker])
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SESSION_SCHEMA_VERSION,
|
||||
buildAggregates,
|
||||
normalizeClaudeHistorySession,
|
||||
normalizeDmuxSnapshot
|
||||
};
|
||||
147
scripts/lib/session-adapters/claude-history.js
Normal file
147
scripts/lib/session-adapters/claude-history.js
Normal file
@@ -0,0 +1,147 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const sessionManager = require('../session-manager');
|
||||
const sessionAliases = require('../session-aliases');
|
||||
const { normalizeClaudeHistorySession } = require('./canonical-session');
|
||||
|
||||
function parseClaudeTarget(target) {
|
||||
if (typeof target !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const prefix of ['claude-history:', 'claude:', 'history:']) {
|
||||
if (target.startsWith(prefix)) {
|
||||
return target.slice(prefix.length).trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isSessionFileTarget(target, cwd) {
|
||||
if (typeof target !== 'string' || target.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const absoluteTarget = path.resolve(cwd, target);
|
||||
return fs.existsSync(absoluteTarget)
|
||||
&& fs.statSync(absoluteTarget).isFile()
|
||||
&& absoluteTarget.endsWith('.tmp');
|
||||
}
|
||||
|
||||
function hydrateSessionFromPath(sessionPath) {
|
||||
const filename = path.basename(sessionPath);
|
||||
const parsed = sessionManager.parseSessionFilename(filename);
|
||||
if (!parsed) {
|
||||
throw new Error(`Unsupported session file: ${sessionPath}`);
|
||||
}
|
||||
|
||||
const content = sessionManager.getSessionContent(sessionPath);
|
||||
const stats = fs.statSync(sessionPath);
|
||||
|
||||
return {
|
||||
...parsed,
|
||||
sessionPath,
|
||||
content,
|
||||
metadata: sessionManager.parseSessionMetadata(content),
|
||||
stats: sessionManager.getSessionStats(content || ''),
|
||||
size: stats.size,
|
||||
modifiedTime: stats.mtime,
|
||||
createdTime: stats.birthtime || stats.ctime
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSessionRecord(target, cwd) {
|
||||
const explicitTarget = parseClaudeTarget(target);
|
||||
|
||||
if (explicitTarget) {
|
||||
if (explicitTarget === 'latest') {
|
||||
const [latest] = sessionManager.getAllSessions({ limit: 1 }).sessions;
|
||||
if (!latest) {
|
||||
throw new Error('No Claude session history found');
|
||||
}
|
||||
|
||||
return {
|
||||
session: sessionManager.getSessionById(latest.filename, true),
|
||||
sourceTarget: {
|
||||
type: 'claude-history',
|
||||
value: 'latest'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const alias = sessionAliases.resolveAlias(explicitTarget);
|
||||
if (alias) {
|
||||
return {
|
||||
session: hydrateSessionFromPath(alias.sessionPath),
|
||||
sourceTarget: {
|
||||
type: 'claude-alias',
|
||||
value: explicitTarget
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const session = sessionManager.getSessionById(explicitTarget, true);
|
||||
if (!session) {
|
||||
throw new Error(`Claude session not found: ${explicitTarget}`);
|
||||
}
|
||||
|
||||
return {
|
||||
session,
|
||||
sourceTarget: {
|
||||
type: 'claude-history',
|
||||
value: explicitTarget
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (isSessionFileTarget(target, cwd)) {
|
||||
return {
|
||||
session: hydrateSessionFromPath(path.resolve(cwd, target)),
|
||||
sourceTarget: {
|
||||
type: 'session-file',
|
||||
value: path.resolve(cwd, target)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported Claude session target: ${target}`);
|
||||
}
|
||||
|
||||
function createClaudeHistoryAdapter() {
|
||||
return {
|
||||
id: 'claude-history',
|
||||
canOpen(target, context = {}) {
|
||||
if (context.adapterId && context.adapterId !== 'claude-history') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.adapterId === 'claude-history') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const cwd = context.cwd || process.cwd();
|
||||
return parseClaudeTarget(target) !== null || isSessionFileTarget(target, cwd);
|
||||
},
|
||||
open(target, context = {}) {
|
||||
const cwd = context.cwd || process.cwd();
|
||||
|
||||
return {
|
||||
adapterId: 'claude-history',
|
||||
getSnapshot() {
|
||||
const { session, sourceTarget } = resolveSessionRecord(target, cwd);
|
||||
return normalizeClaudeHistorySession(session, sourceTarget);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createClaudeHistoryAdapter,
|
||||
isSessionFileTarget,
|
||||
parseClaudeTarget
|
||||
};
|
||||
78
scripts/lib/session-adapters/dmux-tmux.js
Normal file
78
scripts/lib/session-adapters/dmux-tmux.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const { collectSessionSnapshot } = require('../orchestration-session');
|
||||
const { normalizeDmuxSnapshot } = require('./canonical-session');
|
||||
|
||||
function isPlanFileTarget(target, cwd) {
|
||||
if (typeof target !== 'string' || target.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const absoluteTarget = path.resolve(cwd, target);
|
||||
return fs.existsSync(absoluteTarget)
|
||||
&& fs.statSync(absoluteTarget).isFile()
|
||||
&& path.extname(absoluteTarget) === '.json';
|
||||
}
|
||||
|
||||
function isSessionNameTarget(target, cwd) {
|
||||
if (typeof target !== 'string' || target.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const coordinationDir = path.resolve(cwd, '.claude', 'orchestration', target);
|
||||
return fs.existsSync(coordinationDir) && fs.statSync(coordinationDir).isDirectory();
|
||||
}
|
||||
|
||||
function buildSourceTarget(target, cwd) {
|
||||
if (isPlanFileTarget(target, cwd)) {
|
||||
return {
|
||||
type: 'plan',
|
||||
value: path.resolve(cwd, target)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'session',
|
||||
value: target
|
||||
};
|
||||
}
|
||||
|
||||
function createDmuxTmuxAdapter(options = {}) {
|
||||
const collectSessionSnapshotImpl = options.collectSessionSnapshotImpl || collectSessionSnapshot;
|
||||
|
||||
return {
|
||||
id: 'dmux-tmux',
|
||||
canOpen(target, context = {}) {
|
||||
if (context.adapterId && context.adapterId !== 'dmux-tmux') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.adapterId === 'dmux-tmux') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const cwd = context.cwd || process.cwd();
|
||||
return isPlanFileTarget(target, cwd) || isSessionNameTarget(target, cwd);
|
||||
},
|
||||
open(target, context = {}) {
|
||||
const cwd = context.cwd || process.cwd();
|
||||
|
||||
return {
|
||||
adapterId: 'dmux-tmux',
|
||||
getSnapshot() {
|
||||
const snapshot = collectSessionSnapshotImpl(target, cwd);
|
||||
return normalizeDmuxSnapshot(snapshot, buildSourceTarget(target, cwd));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createDmuxTmuxAdapter,
|
||||
isPlanFileTarget,
|
||||
isSessionNameTarget
|
||||
};
|
||||
42
scripts/lib/session-adapters/registry.js
Normal file
42
scripts/lib/session-adapters/registry.js
Normal file
@@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
const { createClaudeHistoryAdapter } = require('./claude-history');
|
||||
const { createDmuxTmuxAdapter } = require('./dmux-tmux');
|
||||
|
||||
function createDefaultAdapters() {
|
||||
return [
|
||||
createClaudeHistoryAdapter(),
|
||||
createDmuxTmuxAdapter()
|
||||
];
|
||||
}
|
||||
|
||||
function createAdapterRegistry(options = {}) {
|
||||
const adapters = options.adapters || createDefaultAdapters();
|
||||
|
||||
return {
|
||||
adapters,
|
||||
select(target, context = {}) {
|
||||
const adapter = adapters.find(candidate => candidate.canOpen(target, context));
|
||||
if (!adapter) {
|
||||
throw new Error(`No session adapter matched target: ${target}`);
|
||||
}
|
||||
|
||||
return adapter;
|
||||
},
|
||||
open(target, context = {}) {
|
||||
const adapter = this.select(target, context);
|
||||
return adapter.open(target, context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function inspectSessionTarget(target, options = {}) {
|
||||
const registry = createAdapterRegistry(options);
|
||||
return registry.open(target, options).getSnapshot();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createAdapterRegistry,
|
||||
createDefaultAdapters,
|
||||
inspectSessionTarget
|
||||
};
|
||||
Reference in New Issue
Block a user