@wendongfly/myhi 1.0.121 → 1.0.123

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/attach.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{createRequire as e}from"module";if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=new URL(".",import.meta.url).pathname.slice(import.meta.url.match(/^file:\/\/\/\w:/)?1:0,-1)+"/";var t={};const s=e(import.meta.url)("module");const r=e(import.meta.url)("fs");const o=e(import.meta.url)("path");const n=e(import.meta.url)("os");const i=e(import.meta.url)("readline");const c=(0,s.createRequire)(import.meta.url);const{io:p}=c("socket.io-client");const a=process.env.MYHI_SERVER||"http://localhost:12300";let m=null;const u=process.argv.slice(2);for(let e=0;e<u.length;e++){if((u[e]==="--password"||u[e]==="-pw")&&u[e+1]){m=u[e+1];u.splice(e,2);e--}}let d;if(m){d={password:m}}else{try{d={token:(0,r.readFileSync)((0,o.join)((0,n.homedir)(),".myhi","token"),"utf8").trim()}}catch{console.error("[myhi] 未找到 token,请使用 --password <密码> 或先启动服务器");process.exit(1)}}function cleanup(e){try{process.stdin.setRawMode(false)}catch{}process.stdin.pause();e.disconnect()}function attach(e,t){e.emit("join",t);e.on("joined",(s=>{const r=s.mode==="agent";process.stderr.write(`\r\n[myhi] 已附加到 "${s.title}" (${t}) 模式=${r?"agent":"pty"}\r\n`);process.stderr.write("[myhi] 按 Ctrl+] 分离\r\n\r\n");e.emit("take-control",{sessionId:t});if(r){let t="";e.on("agent:message",(e=>{if(!e)return;switch(e.type){case"system":if(e.subtype==="init")process.stderr.write("[会话已连接]\r\n");else if(e.subtype==="interrupted")process.stderr.write("\r\n[已中断]\r\n");break;case"assistant":if(e.message?.content){for(const t of e.message.content){if(t.type==="text")process.stdout.write(t.text+"\r\n");else if(t.type==="tool_use"){process.stdout.write(`\r\n[工具] ${t.name}\r\n`);if(t.input){const e=typeof t.input==="string"?t.input:JSON.stringify(t.input,null,2);process.stdout.write(""+e.slice(0,500)+"\r\n")}}}}break;case"content_block_delta":if(e.delta?.text){process.stdout.write(e.delta.text);t+=e.delta.text}break;case"content_block_stop":if(t){process.stdout.write("\r\n");t=""}break;case"tool_use":process.stdout.write(`\r\n[工具] ${e.tool_name||e.name||"?"}\r\n`);break;case"tool_result":if(e.content){const t=typeof e.content==="string"?e.content:Array.isArray(e.content)?e.content.map((e=>e.text||"")).join(""):"";if(t)process.stdout.write(""+t.slice(0,1e3)+"\r\n")}break;case"result":process.stdout.write(`\r\n[完成] ${e.duration_ms?(e.duration_ms/1e3).toFixed(1)+"s":""} ${e.total_cost_usd?"$"+e.total_cost_usd.toFixed(4):""}\r\n\r\n`);break;case"control_request":process.stdout.write(`\r\n[权限请求] ${e.request?.tool_name||"?"}\r\n`);break}}));e.on("agent:history",(e=>{for(const t of e){if(t.type==="user")process.stdout.write(`> ${t.content}\r\n`);else if(t.type==="result")process.stdout.write(`[完成]\r\n`)}process.stdout.write("\r\n")}));e.on("agent:busy",(e=>{if(e)process.stderr.write("[思考中...]\r\n")}));e.on("agent:error",(e=>{process.stderr.write(`[错误] ${e.message}\r\n`)}));const s=(0,i.createInterface)({input:process.stdin,output:process.stdout,prompt:"> "});let r=false;e.on("agent:busy",(e=>{r=e;if(!e)s.prompt()}));s.prompt();s.on("line",(t=>{const o=t.trim();if(!o){if(!r)s.prompt();return}if(o==="/quit"||o==="/exit"||o==="/q"){process.stderr.write("[myhi] 已分离\r\n");cleanup(e);process.exit(0)}if(r){process.stderr.write("[正在处理中,请等待]\r\n");return}e.emit("agent:query",{prompt:o})}));s.on("close",(()=>{cleanup(e);process.exit(0)}))}else{try{process.stdin.setRawMode(true)}catch{}process.stdin.resume();process.stdin.setEncoding("binary");process.stdin.on("data",(t=>{if(t===""){process.stderr.write("\r\n[myhi] 已分离\r\n");cleanup(e);process.exit(0)}e.emit("input",t)}));e.on("output",(e=>{process.stdout.write(e,"binary")}));process.stdout.on("resize",(()=>{e.emit("resize",{cols:process.stdout.columns||80,rows:process.stdout.rows||24})}))}}));e.on("control-denied",(({reason:e})=>{process.stderr.write(`\r\n[myhi] 获取控制权失败: ${e}\r\n`)}));e.on("control-changed",(({holder:t,holderName:s})=>{if(t&&t!==e.id){process.stderr.write(`\r\n[myhi] ${s||"其他用户"} 已获取控制权,当前为只读\r\n`)}else if(!t){process.stderr.write("\r\n[myhi] 控制权已释放\r\n")}}));e.on("kicked",(({reason:t})=>{process.stderr.write(`\r\n[myhi] ${t||"你已被管理员踢出"}\r\n`);cleanup(e);process.exit(1)}));e.on("session-exit",(({code:t})=>{process.stderr.write(`\r\n[myhi] 会话已退出 (code ${t})\r\n`);cleanup(e);process.exit(0)}));e.on("error",(({message:t})=>{process.stderr.write(`\r\n[myhi] 错误: ${t}\r\n`);cleanup(e);process.exit(1)}))}async function pickSession(e){return new Promise(((t,s)=>{e.emit("list");e.once("sessions",(s=>{const r=s.filter((e=>e.alive));if(!r.length){process.stderr.write("[myhi] 没有活跃的会话。\n");cleanup(e);process.exit(0)}process.stdout.write("\n活跃会话:\n");r.forEach(((e,t)=>{const s=e.viewers>0?` (${e.viewers} 人在线)`:"";const r=e.mode==="agent"?" [Agent]":" [PTY]";process.stdout.write(` [${t+1}] ${e.title}${r}${s} — ${e.id}\n`)}));process.stdout.write("\n");const o=(0,i.createInterface)({input:process.stdin,output:process.stdout});o.question("选择会话编号: ",(s=>{o.close();const n=parseInt(s,10)-1;if(n<0||n>=r.length){process.stderr.write("[myhi] 无效的选择。\n");cleanup(e);process.exit(1)}t(r[n].id)}))}))}))}async function createAndAttach(e,t){return new Promise(((s,r)=>{e.emit("create",t,(t=>{if(!t?.ok){process.stderr.write(`[myhi] 创建失败: ${t?.error||"未知错误"}\n`);cleanup(e);process.exit(1)}process.stdout.write(`[myhi] 已创建会话 "${t.session.title}" — ${t.session.id}\n`);s(t.session.id)}))}))}async function promptNew(e){const t=(0,i.createInterface)({input:process.stdin,output:process.stdout});const ask=e=>new Promise((s=>t.question(e,s)));const s=(await ask("会话名称 [shell]: ")).trim()||"shell";const r=(await ask("启动命令(可选): ")).trim()||undefined;t.close();return createAndAttach(e,{title:s,initCmd:r})}const l=p(a,{transports:["websocket","polling"],auth:d});l.on("connect_error",(e=>{process.stderr.write(`[myhi] 连接失败: ${e.message}\n`);process.exit(1)}));l.on("connect",(async()=>{const e=u[0];if(e==="--new"){const e=u[1];const t=u[2]||undefined;const s=e?await createAndAttach(l,{title:e,initCmd:t}):await promptNew(l);attach(l,s)}else{const t=e||await pickSession(l);attach(l,t)}}));for(const e of["SIGINT","SIGTERM"]){process.on(e,(()=>{cleanup(l);process.exit(0)}))}process.on("exit",(()=>{try{process.stdin.setRawMode(false)}catch{}}));
2
+ import{createRequire as e}from"module";if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=new URL(".",import.meta.url).pathname.slice(import.meta.url.match(/^file:\/\/\/\w:/)?1:0,-1)+"/";var t={};const s=e(import.meta.url)("module");const r=e(import.meta.url)("fs");const o=e(import.meta.url)("path");const n=e(import.meta.url)("os");const i=e(import.meta.url)("readline");const c=(0,s.createRequire)(import.meta.url);const{io:p}=c("socket.io-client");const a=process.env.MYHI_SERVER||"http://localhost:12300";let m=null;const d=process.argv.slice(2);for(let e=0;e<d.length;e++){if((d[e]==="--password"||d[e]==="-pw")&&d[e+1]){m=d[e+1];d.splice(e,2);e--}}let u;if(m){u={password:m}}else{try{u={token:(0,r.readFileSync)((0,o.join)((0,n.homedir)(),".myhi","token"),"utf8").trim()}}catch{console.error("[myhi] 未找到 token,请使用 --password <密码> 或先启动服务器");process.exit(1)}}function cleanup(e){try{process.stdin.setRawMode(false)}catch{}process.stdin.pause();e.disconnect()}function attach(e,t){e.emit("join",t);e.on("joined",(s=>{const r=s.mode==="agent";process.stderr.write(`\r\n[myhi] 已附加到 "${s.title}" (${t}) 模式=${r?"agent":"pty"}\r\n`);process.stderr.write("[myhi] 按 Ctrl+] 分离\r\n\r\n");e.emit("take-control",{sessionId:t});if(r){let t="";e.on("agent:message",(e=>{if(!e)return;switch(e.type){case"system":if(e.subtype==="init")process.stderr.write("[会话已连接]\r\n");else if(e.subtype==="interrupted")process.stderr.write("\r\n[已中断]\r\n");break;case"assistant":if(e.message?.content){for(const t of e.message.content){if(t.type==="text")process.stdout.write(t.text+"\r\n");else if(t.type==="tool_use"){process.stdout.write(`\r\n[工具] ${t.name}\r\n`);if(t.input){const e=typeof t.input==="string"?t.input:JSON.stringify(t.input,null,2);process.stdout.write(""+e.slice(0,500)+"\r\n")}}}}break;case"content_block_delta":if(e.delta?.text){process.stdout.write(e.delta.text);t+=e.delta.text}break;case"content_block_stop":if(t){process.stdout.write("\r\n");t=""}break;case"tool_use":process.stdout.write(`\r\n[工具] ${e.tool_name||e.name||"?"}\r\n`);break;case"tool_result":if(e.content){const t=typeof e.content==="string"?e.content:Array.isArray(e.content)?e.content.map((e=>e.text||"")).join(""):"";if(t)process.stdout.write(""+t.slice(0,1e3)+"\r\n")}break;case"result":process.stdout.write(`\r\n[完成] ${e.duration_ms?(e.duration_ms/1e3).toFixed(1)+"s":""} ${e.total_cost_usd?"$"+e.total_cost_usd.toFixed(4):""}\r\n\r\n`);break}}));e.on("agent:history",(e=>{for(const t of e){if(t.type==="user")process.stdout.write(`> ${t.content}\r\n`);else if(t.type==="result")process.stdout.write(`[完成]\r\n`)}process.stdout.write("\r\n")}));e.on("agent:busy",(e=>{if(e)process.stderr.write("[思考中...]\r\n")}));e.on("agent:error",(e=>{process.stderr.write(`[错误] ${e.message}\r\n`)}));const s=(0,i.createInterface)({input:process.stdin,output:process.stdout,prompt:"> "});let r=false;e.on("agent:busy",(e=>{r=e;if(!e)s.prompt()}));s.prompt();s.on("line",(t=>{const o=t.trim();if(!o){if(!r)s.prompt();return}if(o==="/quit"||o==="/exit"||o==="/q"){process.stderr.write("[myhi] 已分离\r\n");cleanup(e);process.exit(0)}if(r){process.stderr.write("[正在处理中,请等待]\r\n");return}e.emit("agent:query",{prompt:o})}));s.on("close",(()=>{cleanup(e);process.exit(0)}))}else{try{process.stdin.setRawMode(true)}catch{}process.stdin.resume();process.stdin.setEncoding("binary");process.stdin.on("data",(t=>{if(t===""){process.stderr.write("\r\n[myhi] 已分离\r\n");cleanup(e);process.exit(0)}e.emit("input",t)}));e.on("output",(e=>{process.stdout.write(e,"binary")}));process.stdout.on("resize",(()=>{e.emit("resize",{cols:process.stdout.columns||80,rows:process.stdout.rows||24})}))}}));e.on("control-denied",(({reason:e})=>{process.stderr.write(`\r\n[myhi] 获取控制权失败: ${e}\r\n`)}));e.on("control-changed",(({holder:t,holderName:s})=>{if(t&&t!==e.id){process.stderr.write(`\r\n[myhi] ${s||"其他用户"} 已获取控制权,当前为只读\r\n`)}else if(!t){process.stderr.write("\r\n[myhi] 控制权已释放\r\n")}}));e.on("kicked",(({reason:t})=>{process.stderr.write(`\r\n[myhi] ${t||"你已被管理员踢出"}\r\n`);cleanup(e);process.exit(1)}));e.on("session-exit",(({code:t})=>{process.stderr.write(`\r\n[myhi] 会话已退出 (code ${t})\r\n`);cleanup(e);process.exit(0)}));e.on("error",(({message:t})=>{process.stderr.write(`\r\n[myhi] 错误: ${t}\r\n`);cleanup(e);process.exit(1)}))}async function pickSession(e){return new Promise(((t,s)=>{e.emit("list");e.once("sessions",(s=>{const r=s.filter((e=>e.alive));if(!r.length){process.stderr.write("[myhi] 没有活跃的会话。\n");cleanup(e);process.exit(0)}process.stdout.write("\n活跃会话:\n");r.forEach(((e,t)=>{const s=e.viewers>0?` (${e.viewers} 人在线)`:"";const r=e.mode==="agent"?" [Agent]":" [PTY]";process.stdout.write(` [${t+1}] ${e.title}${r}${s} — ${e.id}\n`)}));process.stdout.write("\n");const o=(0,i.createInterface)({input:process.stdin,output:process.stdout});o.question("选择会话编号: ",(s=>{o.close();const n=parseInt(s,10)-1;if(n<0||n>=r.length){process.stderr.write("[myhi] 无效的选择。\n");cleanup(e);process.exit(1)}t(r[n].id)}))}))}))}async function createAndAttach(e,t){return new Promise(((s,r)=>{e.emit("create",t,(t=>{if(!t?.ok){process.stderr.write(`[myhi] 创建失败: ${t?.error||"未知错误"}\n`);cleanup(e);process.exit(1)}process.stdout.write(`[myhi] 已创建会话 "${t.session.title}" — ${t.session.id}\n`);s(t.session.id)}))}))}async function promptNew(e){const t=(0,i.createInterface)({input:process.stdin,output:process.stdout});const ask=e=>new Promise((s=>t.question(e,s)));const s=(await ask("会话名称 [shell]: ")).trim()||"shell";const r=(await ask("启动命令(可选): ")).trim()||undefined;t.close();return createAndAttach(e,{title:s,initCmd:r})}const l=p(a,{transports:["websocket","polling"],auth:u});l.on("connect_error",(e=>{process.stderr.write(`[myhi] 连接失败: ${e.message}\n`);process.exit(1)}));l.on("connect",(async()=>{const e=d[0];if(e==="--new"){const e=d[1];const t=d[2]||undefined;const s=e?await createAndAttach(l,{title:e,initCmd:t}):await promptNew(l);attach(l,s)}else{const t=e||await pickSession(l);attach(l,t)}}));for(const e of["SIGINT","SIGTERM"]){process.on(e,(()=>{cleanup(l);process.exit(0)}))}process.on("exit",(()=>{try{process.stdin.setRawMode(false)}catch{}}));
package/dist/chat.html CHANGED
@@ -34,8 +34,6 @@
34
34
  .sb-dot.working { background: #58a6ff; animation: pulse 1s infinite; }
35
35
  .sb-dot.waiting { background: #f0883e; animation: pulse 0.6s infinite; }
36
36
  @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
37
- #mode-badge { padding: 0.15rem 0.5rem; border-radius: 10px; background: #21262d; border: 1px solid #30363d; cursor: pointer; user-select: none; }
38
- #mode-badge:hover { background: #30363d; }
39
37
  #control-badge { padding: 0.15rem 0.5rem; border-radius: 10px; cursor: pointer; user-select: none; border: 1px solid; }
40
38
  #control-badge.controlling { background: #0d3321; color: #3fb950; border-color: #238636; }
41
39
  #control-badge.readonly { background: #21262d; color: #8b949e; border-color: #30363d; }
@@ -84,34 +82,6 @@
84
82
  .tool-body { display: none; background: #161b22; border: 1px solid #21262d; border-radius: 6px; padding: 0.5rem; margin-top: 0.2rem; font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.75rem; line-height: 1.3; overflow-x: auto; white-space: pre-wrap; word-break: break-all; max-height: 300px; overflow-y: auto; }
85
83
  .tool-body.open { display: block; }
86
84
 
87
- /* 权限请求卡片 */
88
- .msg-permission { background: #1c1500; border: 1px solid #d29922; border-radius: 8px; padding: 0.65rem 0.75rem; font-size: 0.82rem; }
89
- .msg-permission .perm-title { color: #d29922; font-weight: 600; margin-bottom: 0.3rem; }
90
- .msg-permission .perm-detail { color: #e6edf3; font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.78rem; margin-bottom: 0.5rem; white-space: pre-wrap; }
91
- .perm-actions { display: flex; gap: 0.4rem; flex-wrap: wrap; }
92
- .perm-actions button { padding: 0.35rem 0.8rem; border-radius: 6px; font-size: 0.78rem; cursor: pointer; border: none; }
93
- .btn-allow { background: #238636; color: #fff; }
94
- .btn-allow:hover { background: #2ea043; }
95
- .btn-deny { background: #21262d; color: #f85149; border: 1px solid #30363d !important; }
96
-
97
- /* 权限弹窗 modal */
98
- #perm-modal { display: none; position: fixed; inset: 0; z-index: 90; align-items: flex-end; justify-content: center; }
99
- #perm-modal.open { display: flex; }
100
- #perm-modal-backdrop { position: absolute; inset: 0; background: rgba(0,0,0,0.55); }
101
- #perm-modal-box { position: relative; background: #161b22; border-radius: 16px 16px 0 0; width: 100%; max-width: 480px; padding: 1rem 1rem; padding-bottom: max(1rem, env(safe-area-inset-bottom)); z-index: 1; animation: slideUp 0.25s ease; border-top: 3px solid #d29922; }
102
- @keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } }
103
- #perm-modal-box .pm-icon { text-align: center; font-size: 1.6rem; margin-bottom: 0.4rem; }
104
- #perm-modal-box .pm-title { text-align: center; font-size: 0.95rem; font-weight: 600; color: #d29922; margin-bottom: 0.6rem; }
105
- #perm-modal-box .pm-tool { text-align: center; font-size: 0.78rem; color: #8b949e; margin-bottom: 0.3rem; }
106
- #perm-modal-box .pm-detail { background: #0d1117; border: 1px solid #21262d; border-radius: 8px; padding: 0.6rem 0.75rem; font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.78rem; line-height: 1.4; color: #e6edf3; white-space: pre-wrap; word-break: break-all; max-height: 200px; overflow-y: auto; margin-bottom: 0.75rem; }
107
- #perm-modal-box .pm-actions { display: flex; gap: 0.5rem; }
108
- #perm-modal-box .pm-actions button { flex: 1; padding: 0.6rem; border-radius: 10px; font-size: 0.88rem; font-weight: 600; cursor: pointer; border: none; transition: all 0.15s; }
109
- #perm-modal-box .pm-actions button:active { transform: scale(0.96); }
110
- #perm-modal-box .pm-btn-allow { background: #238636; color: #fff; }
111
- #perm-modal-box .pm-btn-allow:hover { background: #2ea043; }
112
- #perm-modal-box .pm-btn-deny { background: #21262d; color: #f85149; border: 1px solid #30363d; }
113
- #perm-modal-box .pm-btn-deny:hover { background: #30363d; }
114
- #perm-modal-box .pm-queue { text-align: center; font-size: 0.7rem; color: #8b949e; margin-top: 0.5rem; }
115
85
 
116
86
  /* Diff 视图 */
117
87
  .msg-diff { font-family: 'SF Mono', 'Consolas', monospace; font-size: 0.78rem; background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 0.5rem 0.6rem; overflow-x: auto; line-height: 1.35; }
@@ -253,7 +223,6 @@
253
223
 
254
224
  <div id="status-bar">
255
225
  <span class="sb-item" id="work-status"><span class="sb-dot idle"></span> 空闲</span>
256
- <span id="mode-badge" onclick="switchMode()">默认</span>
257
226
  <span id="control-badge" class="readonly" style="display:none">只读</span>
258
227
  <span id="viewer-count" class="sb-item"></span>
259
228
  <span id="update-badge" style="display:none;color:#d29922;cursor:pointer" onclick="location.href='/'">⬆ 有更新</span>
@@ -409,22 +378,6 @@
409
378
  </div>
410
379
  </div>
411
380
 
412
- <!-- 权限授权弹窗 -->
413
- <div id="perm-modal">
414
- <div id="perm-modal-backdrop"></div>
415
- <div id="perm-modal-box">
416
- <div class="pm-icon">&#x1F6E1;&#xFE0F;</div>
417
- <div class="pm-title" id="pm-title">请求权限</div>
418
- <div class="pm-tool" id="pm-tool"></div>
419
- <div class="pm-detail" id="pm-detail"></div>
420
- <div class="pm-actions">
421
- <button class="pm-btn-deny" id="pm-btn-deny">拒绝</button>
422
- <button class="pm-btn-allow" id="pm-btn-allow">允许</button>
423
- </div>
424
- <div class="pm-queue" id="pm-queue" style="display:none"></div>
425
- </div>
426
- </div>
427
-
428
381
  <div id="status-overlay">连接中...</div>
429
382
 
430
383
  <script type="module">
@@ -509,7 +462,6 @@
509
462
  let isController = false;
510
463
  let myRole = 'viewer';
511
464
  let claudeSession = false; // 是否为 Claude Code 会话
512
- let currentMode = 'default'; // 当前权限模式
513
465
  let workState = 'idle';
514
466
 
515
467
  // 设置
@@ -603,8 +555,6 @@
603
555
  }
604
556
 
605
557
  // 权限请求
606
- if (/allow|deny|approve|reject|permission|Do you want to|允许|拒绝/i.test(clean) && clean.length < 500) return { type: 'permission', detail: clean };
607
-
608
558
  // Diff
609
559
  const lines = clean.split('\n');
610
560
  const hasDiffMarkers = lines.some(l => /^[+-](?![-+]{2})/.test(l)) && lines.some(l => /^@@/.test(l) || /^(---|[\+]{3})\s/.test(l));
@@ -809,21 +759,6 @@
809
759
  // 非工具消息时结束工具组
810
760
  function endToolGroup() { _toolGroupEl = null; _toolGroupCount = 0; }
811
761
 
812
- function addPermissionMessage(raw, detail) {
813
- removeThinking();
814
- const msg = document.createElement('div');
815
- msg.className = 'msg msg-permission';
816
- msg.innerHTML = `<div class="perm-title">需要权限确认</div><div class="perm-detail">${escHtml(detail)}</div>
817
- <div class="perm-actions">
818
- <button class="btn-allow" onclick="respondPermission('y', this)">允许</button>
819
- <button class="btn-deny" onclick="respondPermission('n', this)">拒绝</button>
820
- </div>`;
821
- chatArea.appendChild(msg);
822
- trimMessages();
823
- scrollToBottom();
824
- setWorkState('waiting');
825
- }
826
-
827
762
  function addDiffMessage(raw) {
828
763
  removeThinking();
829
764
  const lines = stripAnsi(raw).split('\n');
@@ -959,93 +894,6 @@
959
894
  chatArea.appendChild(msg);
960
895
  }
961
896
 
962
- // 权限响应
963
- window.respondPermission = function(answer, btn) {
964
- if (!isController) { addStatusMessage('你没有控制权,无法操作'); return; }
965
- socket.emit('input', answer + '\r');
966
- const actions = btn.closest('.perm-actions');
967
- if (actions) actions.innerHTML = answer === 'y' ? '<span style="color:#3fb950">已允许</span>' : '<span style="color:#f85149">已拒绝</span>';
968
- };
969
-
970
- // ── 权限弹窗队列管理 ──────────────────────────────
971
- const permQueue = []; // { reqId, toolName, detail }
972
- let permModalOpen = false;
973
-
974
- function showPermModal(item) {
975
- const modal = document.getElementById('perm-modal');
976
- document.getElementById('pm-title').textContent = `${item.toolName} 请求权限`;
977
- document.getElementById('pm-tool').textContent = item.toolLabel || '';
978
- document.getElementById('pm-detail').textContent = item.detail;
979
- const queueEl = document.getElementById('pm-queue');
980
- if (permQueue.length > 0) {
981
- queueEl.textContent = `还有 ${permQueue.length} 个权限请求等待处理`;
982
- queueEl.style.display = '';
983
- } else {
984
- queueEl.style.display = 'none';
985
- }
986
- // 绑定按钮
987
- document.getElementById('pm-btn-allow').onclick = () => respondPermModal(item.reqId, true);
988
- document.getElementById('pm-btn-deny').onclick = () => respondPermModal(item.reqId, false);
989
- modal.classList.add('open');
990
- permModalOpen = true;
991
- // 振动提醒(移动端)
992
- if (navigator.vibrate) navigator.vibrate(100);
993
- }
994
-
995
- function closePermModal() {
996
- document.getElementById('perm-modal').classList.remove('open');
997
- permModalOpen = false;
998
- }
999
-
1000
- function respondPermModal(requestId, allow) {
1001
- if (!isController) { addStatusMessage('你没有控制权,无法操作'); return; }
1002
- socket.emit('agent:permission', { requestId, allow });
1003
- // 更新内联卡片
1004
- const inlineActions = document.querySelector(`.perm-actions[data-reqid="${requestId}"]`);
1005
- if (inlineActions) inlineActions.innerHTML = allow ? '<span style="color:#3fb950">✓ 已允许</span>' : '<span style="color:#f85149">✗ 已拒绝</span>';
1006
- closePermModal();
1007
- setWorkState('working');
1008
- // 处理队列中的下一个
1009
- if (permQueue.length > 0) {
1010
- const next = permQueue.shift();
1011
- setTimeout(() => showPermModal(next), 200);
1012
- }
1013
- }
1014
-
1015
- function enqueuePermission(reqId, toolName, detail, toolLabel) {
1016
- const item = { reqId, toolName, detail, toolLabel };
1017
- if (!permModalOpen) {
1018
- showPermModal(item);
1019
- } else {
1020
- permQueue.push(item);
1021
- // 更新队列提示
1022
- const queueEl = document.getElementById('pm-queue');
1023
- queueEl.textContent = `还有 ${permQueue.length} 个权限请求等待处理`;
1024
- queueEl.style.display = '';
1025
- }
1026
- }
1027
-
1028
- // backdrop 点击不关闭弹窗(防止误触),但可以拒绝
1029
- document.getElementById('perm-modal-backdrop').addEventListener('click', () => {
1030
- // 不自动关闭,必须明确选择
1031
- });
1032
-
1033
- window.respondAgentPermission = function(requestId, allow, btn) {
1034
- if (!isController) { addStatusMessage('你没有控制权,无法操作'); return; }
1035
- socket.emit('agent:permission', { requestId, allow });
1036
- const actions = btn.closest('.perm-actions');
1037
- if (actions) actions.innerHTML = allow ? '<span style="color:#3fb950">✓ 已允许</span>' : '<span style="color:#f85149">✗ 已拒绝</span>';
1038
- setWorkState('working');
1039
- // 如果弹窗正在显示同一个请求,也关闭它
1040
- if (permModalOpen) {
1041
- closePermModal();
1042
- if (permQueue.length > 0) {
1043
- const next = permQueue.shift();
1044
- setTimeout(() => showPermModal(next), 200);
1045
- }
1046
- }
1047
- };
1048
-
1049
897
  // ── 输出处理 ──────────────────────────────────
1050
898
  function flushOutput() {
1051
899
  if (!outputBuffer) return;
@@ -1069,7 +917,6 @@
1069
917
  switch (result.type) {
1070
918
  case 'thinking': showThinking(result.content); break;
1071
919
  case 'tool': endStream(); addToolMessage(raw, result.name); break;
1072
- case 'permission': endStream(); addPermissionMessage(raw, result.detail); break;
1073
920
  case 'diff': endStream(); addDiffMessage(raw); break;
1074
921
  case 'error': endStream(); addErrorMessage(raw); break;
1075
922
  case 'prompt': endStream(); removeThinking(); setWorkState('idle'); break;
@@ -1105,19 +952,6 @@
1105
952
  }
1106
953
  }
1107
954
 
1108
- // ── 权限模式 ──────────────────────────────────
1109
- const MODES = [
1110
- { id: 'default', label: '默认' },
1111
- { id: 'acceptEdits', label: '自动编辑' },
1112
- { id: 'plan', label: '计划模式' },
1113
- { id: 'bypass', label: '跳过权限' },
1114
- ];
1115
-
1116
- function updateModeUI() {
1117
- const m = MODES.find(x => x.id === currentMode) || MODES[0];
1118
- document.getElementById('mode-badge').textContent = m.label;
1119
- }
1120
-
1121
955
  function updateShortcutBar() {
1122
956
  if (!currentSession) { shortcutBar.style.display = 'none'; return; }
1123
957
  const isAgent = currentSession.mode === 'agent';
@@ -1130,25 +964,6 @@
1130
964
  document.getElementById('sk-claude-agent').style.display = isAgent ? 'flex' : 'none';
1131
965
  }
1132
966
 
1133
- window.switchMode = function() {
1134
- if (!isController) return;
1135
- if (currentSession?.mode === 'agent') {
1136
- // Agent 模式:仅更新模式设置(下次查询生效)
1137
- const idx = MODES.findIndex(x => x.id === currentMode);
1138
- currentMode = MODES[(idx + 1) % MODES.length].id;
1139
- updateModeUI();
1140
- socket.emit('set-mode', { sessionId: SESSION_ID, mode: currentMode });
1141
- addStatusMessage(`权限模式已切换为:${MODES.find(x => x.id === currentMode)?.label}`);
1142
- } else {
1143
- // PTY 模式:发送 Shift+Tab 到 Claude Code CLI
1144
- socket.emit('input', '\x1b[Z');
1145
- const idx = MODES.findIndex(x => x.id === currentMode);
1146
- currentMode = MODES[(idx + 1) % MODES.length].id;
1147
- updateModeUI();
1148
- socket.emit('set-mode', { sessionId: SESSION_ID, mode: currentMode });
1149
- }
1150
- };
1151
-
1152
967
  // ── Socket.IO ──────────────────────────────────
1153
968
  const socket = io({
1154
969
  transports: ['websocket', 'polling'],
@@ -1180,6 +995,38 @@
1180
995
  socket.on('connect_error', () => { _reconnectFails++; showOverlay(_reconnectFails >= 5 ? '连接失败,请检查网络' : `正在重连... (${_reconnectFails})`, _reconnectFails >= 10); });
1181
996
  socket.on('disconnect', () => { showOverlay('已断开连接,正在重连...'); });
1182
997
 
998
+ // ── 移动端连接保活 ──────────────────────────────
999
+ // 页面切换到前台时立即检查连接
1000
+ document.addEventListener('visibilitychange', () => {
1001
+ if (!document.hidden) {
1002
+ if (!socket.connected) {
1003
+ showOverlay('正在重新连接...');
1004
+ socket.connect();
1005
+ } else {
1006
+ // 连接还在,重新 join 确保状态同步
1007
+ socket.emit('join', SESSION_ID);
1008
+ }
1009
+ }
1010
+ });
1011
+
1012
+ // 网络恢复时立即重连
1013
+ window.addEventListener('online', () => {
1014
+ if (!socket.connected) {
1015
+ showOverlay('网络已恢复,正在重连...');
1016
+ socket.connect();
1017
+ }
1018
+ });
1019
+ window.addEventListener('offline', () => {
1020
+ showOverlay('网络已断开,等待恢复...');
1021
+ });
1022
+
1023
+ // 应用层心跳:每 15 秒检查连接状态
1024
+ setInterval(() => {
1025
+ if (!document.hidden && !socket.connected) {
1026
+ socket.connect();
1027
+ }
1028
+ }, 15000);
1029
+
1183
1030
  socket.on('joined', (session) => {
1184
1031
  currentSession = session;
1185
1032
  overlay.classList.add('hidden');
@@ -1191,8 +1038,6 @@
1191
1038
  updateControlUI(session.controlHolder === socket.id, session.controlHolderName);
1192
1039
 
1193
1040
  claudeSession = isClaudeSession(session) || session.mode === 'agent';
1194
- currentMode = session.permissionMode || 'default';
1195
- updateModeUI();
1196
1041
  updateShortcutBar();
1197
1042
 
1198
1043
  // 更新输入框占位符
@@ -1230,7 +1075,6 @@
1230
1075
  });
1231
1076
  socket.on('control-denied', ({ reason }) => addStatusMessage('无法获取控制: ' + reason));
1232
1077
  socket.on('kicked', ({ reason }) => { addStatusMessage(reason || '你已被管理员踢出'); setTimeout(() => location.href = '/', 2000); });
1233
- socket.on('mode-changed', ({ sessionId, mode }) => { if (sessionId === SESSION_ID) { currentMode = mode; updateModeUI(); } });
1234
1078
 
1235
1079
  // ── Agent 模式事件 ──────────────────────────────
1236
1080
  socket.on('agent:message', (msg) => {
@@ -1263,36 +1107,6 @@
1263
1107
  }
1264
1108
  setWorkState('working');
1265
1109
  break;
1266
- case 'control_request':
1267
- removeThinking();
1268
- if (msg.request) {
1269
- const toolName = msg.request.tool_name || '未知工具';
1270
- const input = msg.request.input || {};
1271
- const detail = toolName === 'Bash' ? (input.command || JSON.stringify(input))
1272
- : toolName === 'Edit' ? `${input.file_path || ''}\n${input.old_string ? '替换: ' + input.old_string.slice(0, 120) : ''}`
1273
- : toolName === 'Write' ? `${input.file_path || ''}`
1274
- : toolName === 'Read' ? `${input.file_path || ''}`
1275
- : JSON.stringify(input, null, 2);
1276
- const reqId = msg.request_id || msg.request?.request_id || msg.uuid;
1277
- // 工具类型标签
1278
- const toolLabels = { Bash: '执行命令', Edit: '编辑文件', Write: '写入文件', Read: '读取文件', Glob: '搜索文件', Grep: '搜索内容' };
1279
- const toolLabel = toolLabels[toolName] || toolName;
1280
- // 内联卡片
1281
- const el = document.createElement('div');
1282
- el.className = 'msg msg-permission';
1283
- el.innerHTML = `<div class="perm-title">${escHtml(toolName)} 请求权限</div><div class="perm-detail">${escHtml(detail)}</div>
1284
- <div class="perm-actions" data-reqid="${escHtml(reqId)}">
1285
- <button class="btn-allow" onclick="respondAgentPermission('${escHtml(reqId)}', true, this)">允许</button>
1286
- <button class="btn-deny" onclick="respondAgentPermission('${escHtml(reqId)}', false, this)">拒绝</button>
1287
- </div>`;
1288
- chatArea.appendChild(el);
1289
- trimMessages();
1290
- scrollToBottom();
1291
- setWorkState('waiting');
1292
- // 弹窗授权
1293
- enqueuePermission(reqId, toolName, detail, toolLabel);
1294
- }
1295
- break;
1296
1110
  case 'result':
1297
1111
  removeThinking();
1298
1112
  setWorkState('idle');
package/dist/index.html CHANGED
@@ -615,6 +615,16 @@
615
615
  // Refresh periodically
616
616
  setInterval(() => { if (socket.connected) socket.emit('list'); }, 5000);
617
617
 
618
+ // ── 移动端连接保活 ──────────────────────────────
619
+ document.addEventListener('visibilitychange', () => {
620
+ if (!document.hidden) {
621
+ if (!socket.connected) socket.connect();
622
+ else socket.emit('list');
623
+ }
624
+ });
625
+ window.addEventListener('online', () => { if (!socket.connected) socket.connect(); });
626
+ setInterval(() => { if (!document.hidden && !socket.connected) socket.connect(); }, 15000);
627
+
618
628
  // ── Render ──────────────────────────────────────────────────
619
629
  const PALETTE = ['#7c3aed','#2563eb','#059669','#b45309','#db2777','#0891b2'];
620
630
 
@@ -684,11 +694,8 @@
684
694
 
685
695
  // ── Presets ───────────────────────────────────────────────────
686
696
  const PRESETS = [
687
- // Agent 模式(使用 Claude SDK)
688
- { icon: '🛡️', label: '交互审批', title: 'claude', type: 'agent', cmd: 'claude', permissionMode: 'default' },
689
- { icon: '✏️', label: '自动编辑', title: 'claude', type: 'agent', cmd: 'claude --permission-mode acceptEdits', permissionMode: 'acceptEdits' },
690
- { icon: '📋', label: '计划模式', title: 'claude', type: 'agent', cmd: 'claude --permission-mode plan', permissionMode: 'plan' },
691
- { icon: '⚡', label: '跳过权限', title: 'claude', type: 'agent', cmd: 'claude --dangerously-skip-permissions', permissionMode: 'bypassPermissions' },
697
+ // Agent 模式
698
+ { icon: '🤖', label: 'Claude', title: 'claude', type: 'agent', cmd: 'claude' },
692
699
  // PTY 模式(终端)
693
700
  { icon: '✨', label: 'Gemini', title: 'gemini', type: 'pty', cmd: 'gemini' },
694
701
  { icon: '🐚', label: 'Shell', title: 'shell', type: 'pty', cmd: '' },
@@ -750,7 +757,6 @@
750
757
  socket.emit('create-agent', {
751
758
  title,
752
759
  cwd,
753
- permissionMode: preset.permissionMode,
754
760
  }, (res) => {
755
761
  if (res?.ok) openSession(res.session.id);
756
762
  else alert('创建失败: ' + (res?.error || '未知错误'));
@@ -938,7 +944,6 @@
938
944
  title,
939
945
  cwd,
940
946
  resumeSessionId: session.sessionId,
941
- permissionMode: 'default',
942
947
  }, (res) => {
943
948
  if (res?.ok) openSession(res.session.id);
944
949
  else alert('导入失败: ' + (res?.error || '未知错误'));