added support for todos
This commit is contained in:
@@ -3094,6 +3094,9 @@ function streamMessage(sessionId, messageId) {
|
|||||||
|
|
||||||
// Clear todos when message completes (to start fresh for next message)
|
// Clear todos when message completes (to start fresh for next message)
|
||||||
clearTodos();
|
clearTodos();
|
||||||
|
if (message.todos && Array.isArray(message.todos)) {
|
||||||
|
message.todos = [];
|
||||||
|
}
|
||||||
|
|
||||||
renderMessages(session);
|
renderMessages(session);
|
||||||
setStatus('Complete');
|
setStatus('Complete');
|
||||||
@@ -3139,6 +3142,9 @@ function streamMessage(sessionId, messageId) {
|
|||||||
|
|
||||||
// Clear todos on error
|
// Clear todos on error
|
||||||
clearTodos();
|
clearTodos();
|
||||||
|
if (message.todos && Array.isArray(message.todos)) {
|
||||||
|
message.todos = [];
|
||||||
|
}
|
||||||
|
|
||||||
renderMessages(session);
|
renderMessages(session);
|
||||||
scrollChatToBottom();
|
scrollChatToBottom();
|
||||||
|
|||||||
124
chat/server.js
124
chat/server.js
@@ -105,6 +105,10 @@ const WORKSPACES_ROOT = path.join(DATA_ROOT, 'apps');
|
|||||||
const STATIC_ROOT = path.join(__dirname, 'public');
|
const STATIC_ROOT = path.join(__dirname, 'public');
|
||||||
const UPLOADS_DIR = path.join(STATE_DIR, 'uploads');
|
const UPLOADS_DIR = path.join(STATE_DIR, 'uploads');
|
||||||
const REPO_ROOT = process.env.CHAT_REPO_ROOT || process.cwd();
|
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 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_URL = process.env.OPENROUTER_API_URL || DEFAULT_OPENROUTER_API_URL;
|
||||||
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || process.env.OPENROUTER_API_TOKEN || '';
|
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 normalized = normalizeCli(cli);
|
||||||
const binDir = process.env.OPENCODE_BIN_DIR || '/root/.opencode/bin';
|
const binDir = process.env.OPENCODE_BIN_DIR || '/root/.opencode/bin';
|
||||||
const candidates = [];
|
const candidates = [];
|
||||||
|
if (OPENCODE_REPO_CLI) candidates.push(OPENCODE_REPO_CLI);
|
||||||
if (binDir) candidates.push(path.join(binDir, normalized));
|
if (binDir) candidates.push(path.join(binDir, normalized));
|
||||||
candidates.push(`/usr/local/bin/${normalized}`);
|
candidates.push(`/usr/local/bin/${normalized}`);
|
||||||
candidates.push(normalized);
|
candidates.push(normalized);
|
||||||
@@ -5292,6 +5297,85 @@ function resolveCliCommand(cli) {
|
|||||||
return normalized;
|
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() {
|
async function ensureStateFile() {
|
||||||
try {
|
try {
|
||||||
await fs.mkdir(STATE_DIR, { recursive: true });
|
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 workspaceDir = session.workspaceDir;
|
||||||
const cliName = normalizeCli(cli || session?.cli);
|
const cliName = normalizeCli(cli || session?.cli);
|
||||||
const cliCommand = resolveCliCommand(cliName);
|
const cliCommand = resolveCliCommand(cliName);
|
||||||
|
verifyOpencodeSetup(cliCommand);
|
||||||
|
|
||||||
// Ensure model is properly resolved
|
// Ensure model is properly resolved
|
||||||
const resolvedModel = model || session.model;
|
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) {
|
if (session) {
|
||||||
session.updatedAt = message.finishedAt;
|
session.updatedAt = message.finishedAt;
|
||||||
}
|
}
|
||||||
|
if (message.todos && Array.isArray(message.todos) && message.todos.length) {
|
||||||
|
message.todos = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The reply is the final accumulated output
|
// The reply is the final accumulated output
|
||||||
@@ -10590,6 +10708,9 @@ async function processMessage(sessionId, message) {
|
|||||||
|
|
||||||
message.status = 'done';
|
message.status = 'done';
|
||||||
message.reply = reply;
|
message.reply = reply;
|
||||||
|
if (message.todos && Array.isArray(message.todos) && message.todos.length) {
|
||||||
|
message.todos = [];
|
||||||
|
}
|
||||||
message.finishedAt = new Date().toISOString();
|
message.finishedAt = new Date().toISOString();
|
||||||
session.updatedAt = message.finishedAt;
|
session.updatedAt = message.finishedAt;
|
||||||
session.cli = activeCli;
|
session.cli = activeCli;
|
||||||
@@ -10599,6 +10720,9 @@ async function processMessage(sessionId, message) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Provide helpful and parseable error details in the message
|
// Provide helpful and parseable error details in the message
|
||||||
message.status = 'error';
|
message.status = 'error';
|
||||||
|
if (message.todos && Array.isArray(message.todos) && message.todos.length) {
|
||||||
|
message.todos = [];
|
||||||
|
}
|
||||||
const details = [];
|
const details = [];
|
||||||
if (error.code) details.push(`code: ${error.code}`);
|
if (error.code) details.push(`code: ${error.code}`);
|
||||||
if (error.stderr) details.push(`stderr: ${error.stderr.trim()}`);
|
if (error.stderr) details.push(`stderr: ${error.stderr.trim()}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user