mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-13 19:51:24 +08:00
feat: extend session-adapter layer with codex-worktree + opencode adapters (#2145)
* feat: add codex-worktree session adapter Adds the third session adapter (after dmux-tmux and claude-history), normalizing Codex rollout sessions into the harness-neutral ecc.session.v1 snapshot. Reads ~/.codex/sessions rollout JSONL, derives objective (skipping the AGENTS.md preamble + leading message UUID), model, originator, worktree cwd, and best-effort git branch. This is step 1 of ECC-2.0-SESSION-ADAPTER-DISCOVERY (move the abstraction beyond tmux + Claude-history) and supports the wrap/adapt control-pane strategy: ECC reads sessions from any harness rather than owning one UX. - scripts/lib/session-adapters/codex-worktree.js: adapter + rollout parser - canonical-session.js: normalizeCodexWorktreeSession - registry.js: register adapter, codex/codex-worktree target types - tests/lib/session-adapters-codex.test.js: 4 tests (unit + registry routing) * feat: add opencode session adapter + allow empty intent objective Adds the fourth session adapter (after dmux-tmux, claude-history, codex-worktree), normalizing OpenCode sessions into ecc.session.v1. Reads ~/.local/share/opencode/storage: session/<project>/ses_*.json for metadata (id, directory, title, version, projectID, time) and message/<session>/msg_*.json to extract the model (modelID/providerID from the first assistant message). Derives objective from the session title, treating the auto-generated "New session - <date>" title as no objective. Recency-based active/recorded state. Schema: relax intent.objective from non-empty to allow empty string (ensureStringAllowEmpty). Sessions legitimately have no objective yet (fresh/auto-titled), and claude-history already emitted "" via metadata.title fallback. This fixes a latent over-strict validation. - scripts/lib/session-adapters/opencode.js: adapter + storage parser - canonical-session.js: normalizeOpencodeSession + ensureStringAllowEmpty - registry.js: register adapter + opencode target type - tests/lib/session-adapters-opencode.test.js: 5 tests Tests: opencode 5/0, codex 4/0, session-adapters 14/0, control-pane-state 10/0, session-inspect 8/0, control-pane 12/0. Smoke-tested on a real OpenCode session (140 messages, gpt-5.3-codex). * test: cover error/fallback branches for codex-worktree + opencode adapters Lift global branch coverage past the 80% gate (was 79.53%). Adds error and fallback path tests: missing-session/unknown-id throws, findRolloutById/ findSessionInfoById, direct file targets, objective truncation, model fallbacks, corrupt-line skip, mtime activity fallback, and the real resolveGitBranch path outside a repo. codex-worktree.js branch 52.8%->78.3%; global branch 80.04%.
This commit is contained in:
@@ -36,6 +36,12 @@ function ensureString(value, fieldPath) {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureStringAllowEmpty(value, fieldPath) {
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(`Canonical session snapshot requires ${fieldPath} to be a string`);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureOptionalString(value, fieldPath) {
|
||||
if (value !== null && value !== undefined && typeof value !== 'string') {
|
||||
throw new Error(`Canonical session snapshot requires ${fieldPath} to be a string or null`);
|
||||
@@ -210,7 +216,7 @@ function validateCanonicalSnapshot(snapshot) {
|
||||
throw new Error(`Canonical session snapshot requires workers[${index}].intent to be an object`);
|
||||
}
|
||||
|
||||
ensureString(worker.intent.objective, `workers[${index}].intent.objective`);
|
||||
ensureStringAllowEmpty(worker.intent.objective, `workers[${index}].intent.objective`);
|
||||
ensureArrayOfStrings(worker.intent.seedPaths, `workers[${index}].intent.seedPaths`);
|
||||
|
||||
if (!isObject(worker.outputs)) {
|
||||
@@ -520,12 +526,119 @@ function normalizeClaudeHistorySession(session, sourceTarget) {
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeCodexWorktreeSession(session, sourceTarget) {
|
||||
const state = session.active ? 'active' : 'recorded';
|
||||
const objective = typeof session.objective === 'string' ? session.objective : '';
|
||||
const worker = {
|
||||
id: session.sessionId,
|
||||
label: session.sessionId,
|
||||
state,
|
||||
health: 'healthy',
|
||||
branch: session.branch || null,
|
||||
worktree: session.cwd || null,
|
||||
runtime: {
|
||||
kind: 'codex-session',
|
||||
command: 'codex',
|
||||
pid: null,
|
||||
active: Boolean(session.active),
|
||||
dead: !session.active,
|
||||
},
|
||||
intent: {
|
||||
objective,
|
||||
seedPaths: []
|
||||
},
|
||||
outputs: {
|
||||
summary: [],
|
||||
validation: [],
|
||||
remainingRisks: []
|
||||
},
|
||||
artifacts: {
|
||||
sessionFile: session.sessionPath || null,
|
||||
model: session.model || null,
|
||||
originator: session.originator || null,
|
||||
cliVersion: session.cliVersion || null,
|
||||
startedAt: session.startedAt || null,
|
||||
recordCount: Number.isInteger(session.recordCount) ? session.recordCount : null
|
||||
}
|
||||
};
|
||||
|
||||
return validateCanonicalSnapshot({
|
||||
schemaVersion: SESSION_SCHEMA_VERSION,
|
||||
adapterId: 'codex-worktree',
|
||||
session: {
|
||||
id: session.sessionId,
|
||||
kind: 'codex-worktree',
|
||||
state,
|
||||
repoRoot: session.cwd || null,
|
||||
sourceTarget
|
||||
},
|
||||
workers: [worker],
|
||||
aggregates: buildAggregates([worker])
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeOpencodeSession(session, sourceTarget) {
|
||||
const state = session.active ? 'active' : 'recorded';
|
||||
const objective = typeof session.objective === 'string' ? session.objective : '';
|
||||
const worker = {
|
||||
id: session.sessionId,
|
||||
label: session.title || session.sessionId,
|
||||
state,
|
||||
health: 'healthy',
|
||||
branch: session.branch || null,
|
||||
worktree: session.cwd || null,
|
||||
runtime: {
|
||||
kind: 'opencode-session',
|
||||
command: 'opencode',
|
||||
pid: null,
|
||||
active: Boolean(session.active),
|
||||
dead: !session.active,
|
||||
},
|
||||
intent: {
|
||||
objective,
|
||||
seedPaths: []
|
||||
},
|
||||
outputs: {
|
||||
summary: [],
|
||||
validation: [],
|
||||
remainingRisks: []
|
||||
},
|
||||
artifacts: {
|
||||
sessionFile: session.sessionPath || null,
|
||||
projectId: session.projectId || null,
|
||||
version: session.version || null,
|
||||
model: session.model || null,
|
||||
provider: session.provider || null,
|
||||
title: session.title || null,
|
||||
createdAt: session.createdAt || null,
|
||||
updatedAt: session.updatedAt || null,
|
||||
messageCount: Number.isInteger(session.messageCount) ? session.messageCount : null
|
||||
}
|
||||
};
|
||||
|
||||
return validateCanonicalSnapshot({
|
||||
schemaVersion: SESSION_SCHEMA_VERSION,
|
||||
adapterId: 'opencode',
|
||||
session: {
|
||||
id: session.sessionId,
|
||||
kind: 'opencode',
|
||||
state,
|
||||
repoRoot: session.cwd || null,
|
||||
sourceTarget
|
||||
},
|
||||
workers: [worker],
|
||||
aggregates: buildAggregates([worker])
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SESSION_SCHEMA_VERSION,
|
||||
buildAggregates,
|
||||
getFallbackSessionRecordingPath,
|
||||
normalizeClaudeHistorySession,
|
||||
normalizeCodexWorktreeSession,
|
||||
normalizeDmuxSnapshot,
|
||||
normalizeOpencodeSession,
|
||||
persistCanonicalSnapshot,
|
||||
validateCanonicalSnapshot
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user