mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
feat(session): add worker health alongside state in ecc.session.v1 (#751)
This commit is contained in:
@@ -60,6 +60,35 @@ function ensureInteger(value, fieldPath) {
|
||||
}
|
||||
}
|
||||
|
||||
const STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
||||
|
||||
function parseUpdatedMs(updated) {
|
||||
if (typeof updated !== 'string' || updated.length === 0) return null;
|
||||
const ms = Date.parse(updated);
|
||||
return Number.isNaN(ms) ? null : ms;
|
||||
}
|
||||
|
||||
function deriveWorkerHealth(rawWorker) {
|
||||
const state = (rawWorker.status && rawWorker.status.state) || 'unknown';
|
||||
const completedStates = ['completed', 'succeeded', 'success', 'done'];
|
||||
const failedStates = ['failed', 'error'];
|
||||
|
||||
if (failedStates.includes(state)) return 'degraded';
|
||||
if (completedStates.includes(state)) return 'healthy';
|
||||
|
||||
if (state === 'running' || state === 'active') {
|
||||
const pane = rawWorker.pane;
|
||||
if (pane && pane.dead) return 'degraded';
|
||||
|
||||
const updatedMs = parseUpdatedMs(rawWorker.status && rawWorker.status.updated);
|
||||
if (updatedMs === null) return 'stale';
|
||||
if (Date.now() - updatedMs > STALE_THRESHOLD_MS) return 'stale';
|
||||
return 'healthy';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function buildAggregates(workers) {
|
||||
const states = workers.reduce((accumulator, worker) => {
|
||||
const state = worker.state || 'unknown';
|
||||
@@ -67,9 +96,16 @@ function buildAggregates(workers) {
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
const healths = workers.reduce((accumulator, worker) => {
|
||||
const health = worker.health || 'unknown';
|
||||
accumulator[health] = (accumulator[health] || 0) + 1;
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
workerCount: workers.length,
|
||||
states
|
||||
states,
|
||||
healths
|
||||
};
|
||||
}
|
||||
|
||||
@@ -157,6 +193,7 @@ function validateCanonicalSnapshot(snapshot) {
|
||||
ensureString(worker.id, `workers[${index}].id`);
|
||||
ensureString(worker.label, `workers[${index}].label`);
|
||||
ensureString(worker.state, `workers[${index}].state`);
|
||||
ensureString(worker.health, `workers[${index}].health`);
|
||||
ensureOptionalString(worker.branch, `workers[${index}].branch`);
|
||||
ensureOptionalString(worker.worktree, `workers[${index}].worktree`);
|
||||
|
||||
@@ -202,11 +239,20 @@ function validateCanonicalSnapshot(snapshot) {
|
||||
throw new Error('Canonical session snapshot requires aggregates.states to be an object');
|
||||
}
|
||||
|
||||
if (!isObject(snapshot.aggregates.healths)) {
|
||||
throw new Error('Canonical session snapshot requires aggregates.healths to be an object');
|
||||
}
|
||||
|
||||
for (const [state, count] of Object.entries(snapshot.aggregates.states)) {
|
||||
ensureString(state, 'aggregates.states key');
|
||||
ensureInteger(count, `aggregates.states.${state}`);
|
||||
}
|
||||
|
||||
for (const [health, count] of Object.entries(snapshot.aggregates.healths)) {
|
||||
ensureString(health, 'aggregates.healths key');
|
||||
ensureInteger(count, `aggregates.healths.${health}`);
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -376,6 +422,7 @@ function normalizeDmuxSnapshot(snapshot, sourceTarget) {
|
||||
id: worker.workerSlug,
|
||||
label: worker.workerSlug,
|
||||
state: worker.status.state || 'unknown',
|
||||
health: deriveWorkerHealth(worker),
|
||||
branch: worker.status.branch || null,
|
||||
worktree: worker.status.worktree || null,
|
||||
runtime: {
|
||||
@@ -431,6 +478,7 @@ function normalizeClaudeHistorySession(session, sourceTarget) {
|
||||
id: workerId,
|
||||
label: metadata.title || session.filename || workerId,
|
||||
state: 'recorded',
|
||||
health: 'healthy',
|
||||
branch: metadata.branch || null,
|
||||
worktree: metadata.worktree || null,
|
||||
runtime: {
|
||||
|
||||
Reference in New Issue
Block a user