mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-14 04:01:30 +08:00
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).
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)) {
|
||||
@@ -571,6 +577,60 @@ function normalizeCodexWorktreeSession(session, sourceTarget) {
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -578,6 +638,7 @@ module.exports = {
|
||||
normalizeClaudeHistorySession,
|
||||
normalizeCodexWorktreeSession,
|
||||
normalizeDmuxSnapshot,
|
||||
normalizeOpencodeSession,
|
||||
persistCanonicalSnapshot,
|
||||
validateCanonicalSnapshot
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user