_dispatch_251.cjs 4.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. // 189 派发客户端(修正版)—— 修复 B-56 客户端侧缺陷,使 189 通道在凭证就绪后即可用
  2. //
  3. // 根因之一(客户端):旧脚本(_dispatch_r91.cjs 等)在 POST /api/session 时把 prompt 嵌进 body
  4. // 并假设 opencode 建会话后自动运行 agent。实测 189 的 opencode 不会自动运行(session 创建后
  5. // outcome 停在初始态,无 assistant 产出)。正确触发流程是:
  6. // ① POST /api/session 建会话(返回 data.id)
  7. // ② POST /api/session/{id}/prompt 经 {text:"..."} 触发 agent 运行(注意字段是 text,不是 prompt)
  8. // ③ 轮询 GET /api/session/{id} 取 outcome(success / failed)
  9. //
  10. // 用法:
  11. // OPENCODE_SERVER_PASSWORD='IwinTrue@123' node _dispatch_189.cjs "在此写要执行的命令/任务"
  12. //
  13. // 注意:189 当前(R93 实测)opencode 所有模型调用均 outcome=failed,根因是 189 节点
  14. // C:\Users\Administrator\.config\opencode\opencode.json 中 agnes/sensenova/axon 均无 apiKey,
  15. // 且 opencode-free 模型需账号授权 —— 即无任一可用 LLM 后端。该问题需运维在 189 本机
  16. // 配置 API Key 并重启 opencode 后方可解决(SSH 22 当前 refused,无法远程处置)。
  17. // 本客户端只负责把「触发流程」修对;凭证就绪后本文件即可直接派发 admin 浏览器套件。
  18. const BASE = process.env.OPENCODE_251_BASE || 'http://192.168.16.251:4096';
  19. const AUTH = 'Basic ' + Buffer.from('opencode:' + (process.env.OPENCODE_SERVER_PASSWORD || '')).toString('base64');
  20. const MODEL = {
  21. id: process.env.OPENCODE_MODEL || 'deepseek3.2',
  22. providerID: process.env.OPENCODE_PROVIDER || 'axon',
  23. };
  24. const PROJECT_ID = '527b7036bd8d16188784528603a9b79a1d95c9fb';
  25. async function dispatch(promptText, timeoutMs = 20 * 60 * 1000) {
  26. // ① 建会话
  27. const cr = await fetch(BASE + '/api/session', {
  28. method: 'POST',
  29. headers: { 'Content-Type': 'application/json', 'Authorization': AUTH },
  30. body: JSON.stringify({ title: 'dispatch-' + Date.now(), model: MODEL, projectID: PROJECT_ID }),
  31. });
  32. const cj = await cr.json().catch(() => ({}));
  33. const sid = (cj.data && cj.data.id) || cj.id;
  34. if (!sid) throw new Error('create session failed: ' + JSON.stringify(cj).slice(0, 200));
  35. console.log('SESSION_ID', sid);
  36. // ② 正确触发 agent:POST /prompt,body 字段是 text
  37. const pr = await fetch(BASE + '/api/session/' + sid + '/prompt', {
  38. method: 'POST',
  39. headers: { 'Content-Type': 'application/json', 'Authorization': AUTH },
  40. body: JSON.stringify({ text: promptText }),
  41. });
  42. console.log('PROMPT', pr.status, (await pr.text()).slice(0, 200));
  43. // ③ 轮询 outcome
  44. const start = Date.now();
  45. let last = '?';
  46. while (Date.now() - start < timeoutMs) {
  47. await new Promise(r => setTimeout(r, 5000));
  48. const q = await fetch(BASE + '/api/session/' + sid, { headers: { 'Authorization': AUTH } });
  49. const j = await q.json().catch(() => null);
  50. const outcome = (j && (j.data && j.data.outcome || j.outcome)) || '?';
  51. if (outcome !== last) { console.log('OUTCOME', outcome); last = outcome; }
  52. if (outcome === 'success' || outcome === 'succeeded') {
  53. // 拉取会话消息,取最后一条 assistant 文本作为结果
  54. let resultText = '';
  55. try {
  56. const m = await fetch(BASE + '/api/session/' + sid + '/message', { headers: { 'Authorization': AUTH } });
  57. const mj = await m.json().catch(() => null);
  58. const list = (mj && (mj.data || mj)) || [];
  59. const asst = list.filter(x => x.type === 'assistant');
  60. const lastAsst = asst[asst.length - 1];
  61. if (lastAsst) {
  62. const parts = lastAsst.content || [];
  63. resultText = parts.map(p => p.text || p.content || '').filter(Boolean).join('\n');
  64. }
  65. } catch (e) { resultText = 'msg-fetch-err:' + e.message; }
  66. return { sid, outcome: 'succeeded', resultText };
  67. }
  68. if (outcome === 'failed') return { sid, outcome: 'failed' };
  69. }
  70. return { sid, outcome: 'timeout' };
  71. }
  72. (async () => {
  73. const prompt = process.argv[2] || '请只执行命令 `node -v` 并把 stdout 原样返回。';
  74. if (!process.env.OPENCODE_SERVER_PASSWORD) {
  75. console.error('ERROR: 需设置环境变量 OPENCODE_SERVER_PASSWORD');
  76. process.exit(2);
  77. }
  78. try {
  79. const r = await dispatch(prompt);
  80. console.log('RESULT', JSON.stringify(r));
  81. if (r && r.resultText) console.log('RESULT_TEXT_START\n' + r.resultText + '\nRESULT_TEXT_END');
  82. } catch (e) {
  83. console.error('DISPATCH_ERROR', e.message);
  84. process.exit(1);
  85. }
  86. })();