feat: add URL-driven workspace state and streamed Codex research
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
import { spawn, spawnSync } from 'node:child_process'
|
||||
import { mkdir } from 'node:fs/promises'
|
||||
import { homedir } from 'node:os'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
// This process is independent of Vite / the web backend. Keep it running while
|
||||
// restarting the app. Codex owns its own login and refreshes that login normally.
|
||||
const reportRoot = resolve(
|
||||
process.env.TWITTER_LITE_REPORT_ROOT || '.data/research',
|
||||
)
|
||||
const endpoint = new URL(
|
||||
process.env.TWITTER_LITE_CODEX_URL || 'ws://127.0.0.1:4500',
|
||||
)
|
||||
if (
|
||||
endpoint.protocol !== 'ws:' ||
|
||||
endpoint.hostname !== '127.0.0.1' ||
|
||||
endpoint.username ||
|
||||
endpoint.password
|
||||
) {
|
||||
throw new Error('The prototype Codex endpoint must be ws://127.0.0.1:<port>.')
|
||||
}
|
||||
await mkdir(reportRoot, { recursive: true, mode: 0o700 })
|
||||
const env = {
|
||||
HOME: homedir(),
|
||||
PATH: process.env.PATH,
|
||||
CODEX_HOME: process.env.CODEX_HOME || resolve(homedir(), '.codex'),
|
||||
}
|
||||
for (const name of [
|
||||
'LANG',
|
||||
'LC_ALL',
|
||||
'SSL_CERT_FILE',
|
||||
'SSL_CERT_DIR',
|
||||
'NIX_SSL_CERT_FILE',
|
||||
'TMPDIR',
|
||||
]) {
|
||||
if (process.env[name]) env[name] = process.env[name]
|
||||
}
|
||||
// Enumerate the effective config, including CLI-wrapper and project settings.
|
||||
// Keep this output private: MCP transport settings can contain credentials.
|
||||
const configured = spawnSync('codex', ['mcp', 'list', '--json'], {
|
||||
cwd: reportRoot,
|
||||
env,
|
||||
encoding: 'utf8',
|
||||
timeout: 15_000,
|
||||
maxBuffer: 4 * 1024 * 1024,
|
||||
})
|
||||
let mcpOverrides
|
||||
try {
|
||||
if (configured.error || configured.status !== 0) throw new Error()
|
||||
const servers = JSON.parse(configured.stdout)
|
||||
if (!Array.isArray(servers)) throw new Error()
|
||||
mcpOverrides = servers.flatMap((server) => {
|
||||
if (
|
||||
typeof server?.name !== 'string' ||
|
||||
!/^[A-Za-z0-9_-]+$/.test(server.name)
|
||||
)
|
||||
throw new Error()
|
||||
return ['-c', `mcp_servers.${server.name}.enabled=false`]
|
||||
})
|
||||
} catch {
|
||||
throw new Error(
|
||||
'Could not inspect Codex MCP configuration safely. Check codex mcp list locally.',
|
||||
)
|
||||
}
|
||||
const child = spawn(
|
||||
'codex',
|
||||
[
|
||||
// Keep overrides before the subcommand so they merge with wrapper settings.
|
||||
'-c',
|
||||
'shell_environment_policy.inherit="none"',
|
||||
'-c',
|
||||
'features.multi_agent=false',
|
||||
'-c',
|
||||
'features.hooks=false',
|
||||
'-c',
|
||||
'web_search="disabled"',
|
||||
...mcpOverrides,
|
||||
'app-server',
|
||||
'--listen',
|
||||
endpoint.origin,
|
||||
],
|
||||
{ cwd: reportRoot, env, stdio: 'inherit' },
|
||||
)
|
||||
child.on('error', () => {
|
||||
console.error(
|
||||
'Could not start Codex. Install codex and run codex login first.',
|
||||
)
|
||||
process.exitCode = 1
|
||||
})
|
||||
child.on('exit', (code) => {
|
||||
process.exitCode = code ?? 1
|
||||
})
|
||||
for (const signal of ['SIGINT', 'SIGTERM'])
|
||||
process.on(signal, () => child.kill(signal))
|
||||
Reference in New Issue
Block a user