// opencode shell dispatch v2: tolerates lost POST response (B-101), polls /api/shell for new id, auto-kills leftovers const BASE = process.env.BASE; const CMD = process.env.CMD; const AUTH = 'Basic ' + Buffer.from('opencode:' + (process.env.OPENCODE_SERVER_PASSWORD || 'IwinTrue@123')).toString('base64'); const capMs = parseInt(process.env.CAP_MS || '300000', 10); const H = { 'Authorization': AUTH, 'Content-Type': 'application/json' }; async function j(url, opt) { try { const r = await fetch(url, Object.assign({ headers: H }, opt)); const t = await r.text(); try { return { status: r.status, json: JSON.parse(t) }; } catch { return { status: r.status, text: t }; } } catch (e) { return { status: 0, err: e.message }; } } const sleep = ms => new Promise(r => setTimeout(r, ms)); async function listShells() { const r = await j(BASE + '/api/shell'); return ((r.json && r.json.data) || []).map(x => x.id); } async function main() { const before = new Set(await listShells()); console.log('PRE', before.size); const cr = await j(BASE + '/api/session', { method: 'POST', body: JSON.stringify({ title: "r112-" + Date.now(), model: { id: "deepseek3.2", providerID: "axon" }, projectID: process.env.PROJECT_ID }) }); const sid = (cr.json && cr.json.data && cr.json.data.id) || ''; console.log('SID', sid || JSON.stringify(cr).slice(0, 150)); if (!sid) throw new Error('no session'); j(BASE + '/api/session/' + sid + '/shell', { method: 'POST', body: JSON.stringify({ command: CMD }) }) .then(r => console.log('CREATE_STATUS', r.status)).catch(e => console.log('CREATE_ERR', e.message)); let id = null; for (let i = 0; i < 20 && !id; i++) { await sleep(2000); const now = await listShells(); const fresh = now.filter(x => !before.has(x)); if (fresh.length) id = fresh[fresh.length - 1]; } console.log('SHELL_ID', id || 'NONE'); if (!id) throw new Error('shell not created'); const t0 = Date.now(); let last = ''; while (Date.now() - t0 < capMs) { await sleep(5000); const st = await j(BASE + '/api/shell/' + id); const d = (st.json && st.json.data) || {}; const s = d.status || '?'; if (s !== last) { console.log('STATUS', s, Math.round((Date.now() - t0) / 1000) + 's'); last = s; } if (s && s !== 'running') { const out = await j(BASE + '/api/shell/' + id + '/output'); console.log('OUT_BEGIN'); console.log(((out.json && out.json.data && out.json.data.output) || out.text || '').slice(-6000)); console.log('OUT_END'); return; } } console.log('TIMEOUT', capMs); } main().catch(e => { console.error('ERR', e.message); process.exit(1); });