import { spawn } from 'node:child_process'; const child = spawn('dsh', ['--profile', 'acp'], { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, PATH: `/home/yangxuan/.nvm/versions/node/v26.8.2/bin:${process.env.PATH}` }, cwd: '/home/yangxuan', }); let buf = ''; const pending = new Map(); let nextId = 0; const notifications = []; child.stdout.on('data', (d) => { buf += d.toString(); let i; while ((i = buf.indexOf('\n')) >= 0) { const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1); if (!line) continue; let msg; try { msg = JSON.parse(line); } catch { console.log('[non-json stdout]', line.slice(0, 200)); continue; } handle(msg); } }); child.stderr.on('data', (d) => process.stderr.write('[stderr] ' + d.toString())); function send(msg) { child.stdin.write(JSON.stringify(msg) + '\n'); } function call(method, params, timeoutMs = 180000) { const id = ++nextId; return new Promise((resolve, reject) => { const t = setTimeout(() => { pending.delete(id); reject(new Error(`timeout: ${method}`)); }, timeoutMs); pending.set(id, { resolve: (v) => { clearTimeout(t); resolve(v); }, reject: (e) => { clearTimeout(t); reject(e); } }); send({ jsonrpc: '2.0', id, method, params }); }); } function handle(msg) { if (msg.id !== undefined && msg.method === 'session/request_permission') { const opt = msg.params?.options?.[0]; console.log('[permission request]', JSON.stringify(msg.params?.toolCall ?? {}).slice(0, 200)); send({ jsonrpc: '2.0', id: msg.id, result: { outcome: { outcome: 'selected', optionId: opt?.optionId } } }); return; } if (msg.id !== undefined && (msg.result !== undefined || msg.error !== undefined)) { const p = pending.get(msg.id); if (!p) return; pending.delete(msg.id); msg.error ? p.reject(new Error(JSON.stringify(msg.error))) : p.resolve(msg.result); return; } if (msg.method) { notifications.push(msg); const u = msg.params?.update; const kind = u?.sessionUpdate ?? ''; const text = u?.content?.text ?? u?.text ?? u?.title ?? ''; console.log(`[notify] ${msg.method} ${kind} ${String(text).slice(0, 120)}`); } } try { const init = await call('initialize', { protocolVersion: 1, clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } }, clientInfo: { name: 'dsh-acp-smoke', version: '0.0.1' }, }); console.log('== initialize =='); console.log(JSON.stringify(init).slice(0, 800)); const sess = await call('session/new', { cwd: '/home/yangxuan', mcpServers: [] }); console.log('== session/new =='); console.log(JSON.stringify(sess).slice(0, 800)); const sid = sess.sessionId; const res = await call('session/prompt', { sessionId: sid, prompt: [{ type: 'text', text: '只回答一句话:1+1 等于几?不要使用任何工具。' }], }); console.log('== session/prompt =='); console.log(JSON.stringify(res).slice(0, 500)); const assistant = notifications .map((n) => n.params?.update) .filter((u) => u?.sessionUpdate === 'agent_message_chunk') .map((u) => u?.content?.text ?? '') .join(''); console.log('== 助手全文 =='); console.log(assistant.slice(0, 500)); console.log('== 通知统计 ==', notifications.length, '条'); await call('session/close', { sessionId: sid }, 60000).then((r) => console.log('== session/close ==', JSON.stringify(r))).catch((e) => console.log('close 失败:', e.message)); } catch (err) { console.log('!! 失败:', err.message); } finally { child.kill('SIGTERM'); setTimeout(() => process.exit(0), 1500); }