added support for todos

This commit is contained in:
southseact-3d
2026-02-10 11:53:31 +00:00
parent cc40085441
commit 8e9f2dec8e
2 changed files with 130 additions and 0 deletions

View File

@@ -3094,6 +3094,9 @@ function streamMessage(sessionId, messageId) {
// Clear todos when message completes (to start fresh for next message)
clearTodos();
if (message.todos && Array.isArray(message.todos)) {
message.todos = [];
}
renderMessages(session);
setStatus('Complete');
@@ -3139,6 +3142,9 @@ function streamMessage(sessionId, messageId) {
// Clear todos on error
clearTodos();
if (message.todos && Array.isArray(message.todos)) {
message.todos = [];
}
renderMessages(session);
scrollChatToBottom();

View File

@@ -105,6 +105,10 @@ const WORKSPACES_ROOT = path.join(DATA_ROOT, 'apps');
const STATIC_ROOT = path.join(__dirname, 'public');
const UPLOADS_DIR = path.join(STATE_DIR, 'uploads');
const REPO_ROOT = process.env.CHAT_REPO_ROOT || process.cwd();
const OPENCODE_REPO_ROOT = path.join(REPO_ROOT, 'opencode');
const OPENCODE_REPO_CLI = path.join(OPENCODE_REPO_ROOT, 'packages', 'opencode', 'bin', 'opencode');
const OPENCODE_PROMPT_DIR = path.join(OPENCODE_REPO_ROOT, 'packages', 'opencode', 'src', 'session', 'prompt');
const OPENCODE_REQUIRE_REPO = process.env.OPENCODE_REQUIRE_REPO !== 'false';
const DEFAULT_OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';
const OPENROUTER_API_URL = process.env.OPENROUTER_API_URL || DEFAULT_OPENROUTER_API_URL;
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || process.env.OPENROUTER_API_TOKEN || '';
@@ -5279,6 +5283,7 @@ function resolveCliCommand(cli) {
const normalized = normalizeCli(cli);
const binDir = process.env.OPENCODE_BIN_DIR || '/root/.opencode/bin';
const candidates = [];
if (OPENCODE_REPO_CLI) candidates.push(OPENCODE_REPO_CLI);
if (binDir) candidates.push(path.join(binDir, normalized));
candidates.push(`/usr/local/bin/${normalized}`);
candidates.push(normalized);
@@ -5292,6 +5297,85 @@ function resolveCliCommand(cli) {
return normalized;
}
const opencodeVerificationCache = {
checked: false,
ok: false,
error: null,
cliPath: null,
};
function verifyOpencodePrompts() {
const promptFiles = [
'codex_header.txt',
'beast.txt',
'gemini.txt',
'anthropic.txt',
'qwen.txt',
'trinity.txt',
'wordpress-plugin.txt',
'wordpress-plugin-subsequent.txt'
];
const missing = [];
const nonWordPress = [];
for (const file of promptFiles) {
const fullPath = path.join(OPENCODE_PROMPT_DIR, file);
if (!fsSync.existsSync(fullPath)) {
missing.push(fullPath);
continue;
}
const content = fsSync.readFileSync(fullPath, 'utf8');
if (!/wordpress/i.test(content)) {
nonWordPress.push(fullPath);
}
}
if (missing.length) {
throw new Error(`OpenCode prompt verification failed: missing prompt files: ${missing.join(', ')}`);
}
if (nonWordPress.length) {
throw new Error(`OpenCode prompt verification failed: prompts are not WordPress-specific: ${nonWordPress.join(', ')}`);
}
}
function verifyOpencodeCli(cliCommand) {
if (!OPENCODE_REQUIRE_REPO) return;
const repoRootResolved = fsSync.existsSync(OPENCODE_REPO_ROOT)
? fsSync.realpathSync(OPENCODE_REPO_ROOT)
: null;
if (!repoRootResolved) {
throw new Error('OpenCode repo root not found; cannot verify CLI build source.');
}
let cliResolved = null;
try {
cliResolved = fsSync.realpathSync(cliCommand);
} catch (_) {
cliResolved = null;
}
if (!cliResolved || !cliResolved.startsWith(repoRootResolved)) {
throw new Error(`OpenCode CLI is not using the repo build. Expected CLI under ${repoRootResolved} but got ${cliCommand}.`);
}
}
function verifyOpencodeSetup(cliCommand) {
if (opencodeVerificationCache.checked) {
if (opencodeVerificationCache.error) throw opencodeVerificationCache.error;
return;
}
try {
verifyOpencodeCli(cliCommand);
verifyOpencodePrompts();
opencodeVerificationCache.checked = true;
opencodeVerificationCache.ok = true;
opencodeVerificationCache.cliPath = cliCommand;
} catch (err) {
opencodeVerificationCache.checked = true;
opencodeVerificationCache.ok = false;
opencodeVerificationCache.error = err;
throw err;
}
}
async function ensureStateFile() {
try {
await fs.mkdir(STATE_DIR, { recursive: true });
@@ -8898,6 +8982,7 @@ async function sendToOpencode({ session, model, content, message, cli, streamCal
const workspaceDir = session.workspaceDir;
const cliName = normalizeCli(cli || session?.cli);
const cliCommand = resolveCliCommand(cliName);
verifyOpencodeSetup(cliCommand);
// Ensure model is properly resolved
const resolvedModel = model || session.model;
@@ -9117,6 +9202,36 @@ async function sendToOpencode({ session, model, content, message, cli, streamCal
}
});
}
persistState().catch(() => { });
}
}
if (event.type === 'todo.updated' && event.properties?.todos) {
const todos = event.properties.todos;
if (message) {
message.todos = todos;
log('Captured todos from todo.updated event', {
messageId: messageKey,
todoCount: todos.length,
todos: todos.map(t => ({ id: t.id, content: t.content?.substring(0, 50), status: t.status }))
});
if (messageKey && activeStreams.has(messageKey)) {
const streams = activeStreams.get(messageKey);
const data = JSON.stringify({
type: 'todos',
todos: todos,
timestamp: new Date().toISOString()
});
streams.forEach(res => {
try {
res.write(`data: ${data}\n\n`);
} catch (err) {
log('SSE todos write error', { err: String(err) });
}
});
}
persistState().catch(() => { });
}
}
@@ -9213,6 +9328,9 @@ async function sendToOpencode({ session, model, content, message, cli, streamCal
if (session) {
session.updatedAt = message.finishedAt;
}
if (message.todos && Array.isArray(message.todos) && message.todos.length) {
message.todos = [];
}
}
// The reply is the final accumulated output
@@ -10590,6 +10708,9 @@ async function processMessage(sessionId, message) {
message.status = 'done';
message.reply = reply;
if (message.todos && Array.isArray(message.todos) && message.todos.length) {
message.todos = [];
}
message.finishedAt = new Date().toISOString();
session.updatedAt = message.finishedAt;
session.cli = activeCli;
@@ -10599,6 +10720,9 @@ async function processMessage(sessionId, message) {
} catch (error) {
// Provide helpful and parseable error details in the message
message.status = 'error';
if (message.todos && Array.isArray(message.todos) && message.todos.length) {
message.todos = [];
}
const details = [];
if (error.code) details.push(`code: ${error.code}`);
if (error.stderr) details.push(`stderr: ${error.stderr.trim()}`);