botmux 2.30.0 → 2.30.1
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/adapters/backend/tmux-backend.d.ts +3 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +4 -2
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +137 -16
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/adapters/cli/coco.js +3 -3
- package/dist/adapters/cli/coco.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +4 -0
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/cli.js +34 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts +2 -14
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +5 -25
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +19 -30
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +2 -4
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/working-dir.d.ts +16 -0
- package/dist/core/working-dir.d.ts.map +1 -0
- package/dist/core/working-dir.js +35 -0
- package/dist/core/working-dir.js.map +1 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +7 -0
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/web/groups.d.ts.map +1 -1
- package/dist/dashboard/web/groups.js +16 -1
- package/dist/dashboard/web/groups.js.map +1 -1
- package/dist/dashboard-web/app.js +86 -80
- package/dist/dashboard.js +3 -0
- package/dist/dashboard.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +11 -1
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/services/coco-paths.d.ts +10 -0
- package/dist/services/coco-paths.d.ts.map +1 -0
- package/dist/services/coco-paths.js +16 -0
- package/dist/services/coco-paths.js.map +1 -0
- package/dist/services/coco-transcript.d.ts.map +1 -1
- package/dist/services/coco-transcript.js +3 -7
- package/dist/services/coco-transcript.js.map +1 -1
- package/dist/services/group-creator.d.ts +10 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +25 -0
- package/dist/services/group-creator.js.map +1 -1
- package/dist/worker.js +23 -11
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";(()=>{var
|
|
1
|
+
"use strict";(()=>{var _=class{sessions=new Map;schedules=new Map;online=!0;listeners=new Set;upsertSessions(o){for(let c of o)this.sessions.set(c.sessionId,c);this.emit()}upsertSchedules(o){for(let c of o)this.schedules.set(c.id,c);this.emit()}applySse(o,c){if(o==="session.spawned")this.sessions.set(c.session.sessionId,c.session);else if(o==="session.update"){let I=this.sessions.get(c.sessionId);I&&this.sessions.set(c.sessionId,{...I,...c.patch})}else if(o==="session.exited"){let I=this.sessions.get(c.sessionId);I&&this.sessions.set(c.sessionId,{...I,status:"closed"})}else if(o==="schedule.created")this.schedules.set(c.schedule.id,c.schedule);else if(o==="schedule.updated"){let I=this.schedules.get(c.id);I&&this.schedules.set(c.id,{...I,...c.patch})}else if(o==="schedule.deleted")this.schedules.delete(c.id);else return;this.emit()}setOnline(o){this.online!==o&&(this.online=o,this.emit())}on(o){return this.listeners.add(o),()=>this.listeners.delete(o)}emit(){for(let o of this.listeners)o()}},A=new _;async function J(){let[n,o]=await Promise.all([fetch("/api/sessions").then($=>$.json()),fetch("/api/schedules").then($=>$.json())]);A.upsertSessions(n.sessions??[]),A.upsertSchedules(o.schedules??[]);let c=new EventSource("/events"),I=["session.spawned","session.update","session.exited","schedule.created","schedule.updated","schedule.deleted","schedule.fired","heartbeat"];for(let $ of I)c.addEventListener($,i=>{try{let M=JSON.parse(i.data);A.applySse($,M.body??M)}catch{}});c.onerror=()=>A.setOnline(!1),c.onopen=()=>A.setOnline(!0)}var ot=`
|
|
2
2
|
<form id="filters" class="filters">
|
|
3
3
|
<input type="search" name="q" placeholder="search workingDir / title / ids" />
|
|
4
4
|
<select name="cli" multiple size="4">
|
|
@@ -37,38 +37,38 @@
|
|
|
37
37
|
<tbody></tbody>
|
|
38
38
|
</table>
|
|
39
39
|
<dialog id="drawer"></dialog>
|
|
40
|
-
`;function G(
|
|
41
|
-
<td><input type="checkbox" class="row-select" ${
|
|
42
|
-
<td>${
|
|
43
|
-
<td><span class="badge cli-${
|
|
44
|
-
<td><span class="status status-${
|
|
45
|
-
<td>${
|
|
46
|
-
<td title="${
|
|
40
|
+
`;function G(n){if(!n)return"-";let o=Date.now()-n;return o<6e4?"now":o<36e5?Math.floor(o/6e4)+"m":o<864e5?Math.floor(o/36e5)+"h":Math.floor(o/864e5)+"d"}function T(n){return n.replace(/[&<>"']/g,o=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o])}var st="\u{1FA9E}",N="\u{1F4CD}",at="\u{1F5A5}\uFE0F";function W(n){n.innerHTML=ot;let o=n.querySelector("#sessions-table tbody"),c=n.querySelector("#filters"),I=n.querySelector("#drawer"),$=n.querySelector("#select-all"),i=n.querySelector("#bulk-bar"),M=n.querySelector("#bulk-count"),f=n.querySelector("#bulk-close"),E=n.querySelector("#bulk-clear"),k=n.querySelector("#sessions-table"),m=new Set,S="lastMessageAt",s="desc";function y(t){let e=t.status==="closed",a=m.has(t.sessionId)?"checked":"";return`<tr data-id="${T(t.sessionId)}">
|
|
41
|
+
<td><input type="checkbox" class="row-select" ${a} ${e?"disabled":""}></td>
|
|
42
|
+
<td>${T(t.botName??"")}</td>
|
|
43
|
+
<td><span class="badge cli-${T(t.cliId??"unknown")}">${T(t.cliId??"unknown")}</span></td>
|
|
44
|
+
<td><span class="status status-${T(t.status)}">${T(t.status)}</span></td>
|
|
45
|
+
<td>${T((t.title??"").slice(0,40))}</td>
|
|
46
|
+
<td title="${T(t.workingDir??"")}">${T((t.workingDir??"").slice(-30))}</td>
|
|
47
47
|
<td>${G(t.spawnedAt)}</td>
|
|
48
48
|
<td>${G(t.lastMessageAt)}</td>
|
|
49
|
-
<td>${t.adopt?
|
|
49
|
+
<td>${t.adopt?st:""}</td>
|
|
50
50
|
<td><button class="open">\u22EF</button></td>
|
|
51
|
-
</tr>`}function
|
|
51
|
+
</tr>`}function H(){let t=new FormData(c),e=(t.get("q")??"").toLowerCase(),a=t.getAll("cli"),b=t.get("status"),d=t.get("adopt"),x=!!t.get("active"),C=[...A.sessions.values()].filter(L=>!a.length||a.includes(L.cliId??"unknown")).filter(L=>!b||L.status===b).filter(L=>!d||d==="yes"==!!L.adopt).filter(L=>!x||L.status!=="closed").filter(L=>!e||JSON.stringify(L).toLowerCase().includes(e));return C.sort(l),C}function r(t,e){return e==="spawnedAt"||e==="lastMessageAt"?Number(t[e]??0):e==="adopt"?!!t.adopt:String(t[e]??"").toLowerCase()}function l(t,e){let a=r(t,S),b=r(e,S),d=0;return typeof a=="number"&&typeof b=="number"?d=a-b:typeof a=="boolean"&&typeof b=="boolean"?d=Number(a)-Number(b):d=String(a).localeCompare(String(b)),d===0&&(d=Number(t.lastMessageAt??0)-Number(e.lastMessageAt??0)),s==="asc"?d:-d}function p(){k.querySelectorAll("th[data-sort]").forEach(t=>{let e=t.dataset.sort===S;t.classList.toggle("sorted",e),t.setAttribute("aria-sort",e?s==="asc"?"ascending":"descending":"none");let a=t.textContent?.replace(/ [▲▼]$/,"").trim()??"";t.textContent=e?`${a} ${s==="asc"?"\u25B2":"\u25BC"}`:a})}function h(){let t=H();for(let e of[...m]){let a=A.sessions.get(e);(!a||a.status==="closed")&&m.delete(e)}o.innerHTML=t.map(y).join(""),p(),u(t)}function u(t){let e=m.size;i.hidden=e===0,M.textContent=`\u5DF2\u9009 ${e} \u4E2A\u4F1A\u8BDD`;let a=t.filter(d=>d.status!=="closed");if(a.length===0){$.checked=!1,$.indeterminate=!1,$.disabled=!0;return}$.disabled=!1;let b=a.filter(d=>m.has(d.sessionId)).length;$.checked=b===a.length,$.indeterminate=b>0&&b<a.length}function g(t){let e=t.status==="closed";I.innerHTML=`
|
|
52
52
|
<article>
|
|
53
53
|
<header>
|
|
54
|
-
<h3>${
|
|
55
|
-
<code>${
|
|
54
|
+
<h3>${T(t.title??t.sessionId)}</h3>
|
|
55
|
+
<code>${T(t.sessionId)}</code> <button data-copy="${T(t.sessionId)}">copy</button>
|
|
56
56
|
</header>
|
|
57
|
-
<p><b>bot:</b> ${
|
|
58
|
-
<p><b>chatId:</b> <code>${
|
|
59
|
-
<p><b>rootMessageId:</b> <code>${
|
|
60
|
-
${t.threadId?`<p><b>threadId:</b> <code>${
|
|
61
|
-
<p><b>workingDir:</b> ${
|
|
57
|
+
<p><b>bot:</b> ${T(t.botName??"-")} \xB7 <b>cli:</b> ${T(t.cliId??"?")} \xB7 <b>status:</b> ${T(t.status)}</p>
|
|
58
|
+
<p><b>chatId:</b> <code>${T(t.chatId)}</code> <button data-copy="${T(t.chatId)}">copy</button></p>
|
|
59
|
+
<p><b>rootMessageId:</b> <code>${T(t.rootMessageId??"")}</code> <button data-copy="${T(t.rootMessageId??"")}">copy</button></p>
|
|
60
|
+
${t.threadId?`<p><b>threadId:</b> <code>${T(t.threadId)}</code></p>`:""}
|
|
61
|
+
<p><b>workingDir:</b> ${T(t.workingDir??"-")}</p>
|
|
62
62
|
<div class="actions">
|
|
63
63
|
<button id="locate-btn" type="button">${N} \u5B9A\u4F4D\u5230\u98DE\u4E66\u8BDD\u9898</button>
|
|
64
|
-
${t.webPort?`<a class="btn-link" href="http://${
|
|
65
|
-
${
|
|
64
|
+
${t.webPort?`<a class="btn-link" href="http://${T(location.hostname)}:${t.webPort}" target="_blank">${at} \u6253\u5F00 xterm</a>`:""}
|
|
65
|
+
${e?"":'<button id="close-btn" type="button" class="contrast">\u5173\u95ED\u4F1A\u8BDD</button>'}
|
|
66
66
|
</div>
|
|
67
67
|
<form method="dialog"><button>\u5173\u95ED</button></form>
|
|
68
|
-
</article>`,
|
|
69
|
-
`),
|
|
70
|
-
... +${
|
|
71
|
-
${
|
|
68
|
+
</article>`,I.querySelectorAll("[data-copy]").forEach(d=>{d.onclick=()=>{navigator.clipboard.writeText(d.dataset.copy??""),d.textContent="copied",setTimeout(()=>{d.textContent="copy"},800)}});let a=I.querySelector("#locate-btn");a&&(a.onclick=async()=>{a.disabled=!0,a.textContent=`${N} \u53D1\u9001\u4E2D...`;try{let d=await fetch(`/api/sessions/${encodeURIComponent(t.sessionId)}/locate`,{method:"POST"}),x=await d.json();if(x.ok){let C=30;a.textContent=`${N} (\u51B7\u5374 ${C}s)`;let L=setInterval(()=>{C-=1,C<=0?(clearInterval(L),a.disabled=!1,a.textContent=`${N} \u5B9A\u4F4D\u5230\u98DE\u4E66\u8BDD\u9898`):a.textContent=`${N} (\u51B7\u5374 ${C}s)`},1e3)}else{let C=x.error??d.status;alert("Locate failed: "+C),a.disabled=!1,a.textContent=`${N} \u5B9A\u4F4D\u5230\u98DE\u4E66\u8BDD\u9898`}}catch(d){alert("Locate error: "+d),a.disabled=!1,a.textContent=`${N} \u5B9A\u4F4D\u5230\u98DE\u4E66\u8BDD\u9898`}});let b=I.querySelector("#close-btn");b&&(b.onclick=async()=>{if(confirm("\u5173\u95ED\u8FD9\u4E2A\u4F1A\u8BDD?")){b.disabled=!0;try{await fetch(`/api/sessions/${encodeURIComponent(t.sessionId)}/close`,{method:"POST"})}finally{I.close()}}}),I.showModal()}o.addEventListener("click",t=>{let e=t.target;if(e.classList.contains("row-select")){let C=e.closest("tr[data-id]");if(!C)return;let L=C.dataset.id;e.checked?m.add(L):m.delete(L),u(H());return}let a=e.closest("td");if(a&&a.querySelector(".row-select"))return;let b=e.closest("tr[data-id]");if(!b)return;let d=b.dataset.id,x=A.sessions.get(d);x&&g(x)}),$.addEventListener("change",()=>{let t=H().filter(e=>e.status!=="closed");if($.checked)for(let e of t)m.add(e.sessionId);else for(let e of t)m.delete(e.sessionId);h()}),E.addEventListener("click",()=>{m.clear(),h()}),f.addEventListener("click",async()=>{let t=[...m];if(t.length===0||!confirm(`\u5173\u95ED\u9009\u4E2D\u7684 ${t.length} \u4E2A\u4F1A\u8BDD\uFF1F`))return;f.disabled=!0,E.disabled=!0;let e=f.textContent,a=0,b=0,d=[];f.textContent=`\u5173\u95ED\u4E2D 0/${t.length}...`;let x=[...t];async function C(){for(;x.length;){let L=x.shift();try{let v=await fetch(`/api/sessions/${encodeURIComponent(L)}/close`,{method:"POST"}),j=null;try{j=await v.json()}catch{}if(!v.ok||j?.ok===!1){b+=1;let nt=j?.error??`HTTP ${v.status}`;d.push(`${L.slice(0,12)}\u2026: ${nt}`)}}catch(v){b+=1,d.push(`${L.slice(0,12)}\u2026: ${v?.message??v}`)}finally{a+=1,f.textContent=`\u5173\u95ED\u4E2D ${a}/${t.length}...`}}}if(await Promise.all(Array.from({length:Math.min(6,t.length)},()=>C())),f.textContent=e,f.disabled=!1,E.disabled=!1,m.clear(),h(),b>0){let L=d.slice(0,3).join(`
|
|
69
|
+
`),v=d.length>3?`
|
|
70
|
+
... +${d.length-3} \u4E2A`:"";alert(`\u5173\u95ED\u5B8C\u6210\uFF1A\u6210\u529F ${t.length-b} / \u5931\u8D25 ${b}
|
|
71
|
+
${L}${v}`)}}),k.querySelectorAll("th[data-sort]").forEach(t=>{t.addEventListener("click",()=>{let e=t.dataset.sort;S===e?s=s==="asc"?"desc":"asc":(S=e,s=e==="spawnedAt"||e==="lastMessageAt"?"desc":"asc"),h()})}),c.addEventListener("input",h),A.on(h),h()}var rt=`
|
|
72
72
|
<form id="sched-filters" class="filters">
|
|
73
73
|
<input type="search" name="q" placeholder="search name / prompt / workingDir" />
|
|
74
74
|
<select name="kind">
|
|
@@ -86,19 +86,19 @@ ${E}${B}`)}}),$.querySelectorAll("th[data-sort]").forEach(t=>{t.addEventListener
|
|
|
86
86
|
</tr></thead>
|
|
87
87
|
<tbody id="schedules-tbody"></tbody>
|
|
88
88
|
</table>
|
|
89
|
-
`;function
|
|
90
|
-
<td>${
|
|
91
|
-
<td>${
|
|
92
|
-
<td><code>${
|
|
93
|
-
<td>${
|
|
94
|
-
<td>${
|
|
89
|
+
`;function R(n){return n.replace(/[&<>"']/g,o=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o])}function z(n){if(!n)return"\u2014";try{return new Date(n).toLocaleString()}catch{return n}}function K(n){n.innerHTML=rt;let o=n.querySelector("#schedules-tbody"),c=n.querySelector("#sched-filters");function I(){let i=new FormData(c),M=(i.get("q")??"").toLowerCase(),f=i.get("kind"),E=!!i.get("enabled");return[...A.schedules.values()].filter(k=>!f||k.parsed?.kind===f).filter(k=>!E||k.enabled).filter(k=>!M||JSON.stringify(k).toLowerCase().includes(M)).sort((k,m)=>{if(k.enabled!==m.enabled)return k.enabled?-1:1;let S=k.nextRunAt?Date.parse(k.nextRunAt):1/0,s=m.nextRunAt?Date.parse(m.nextRunAt):1/0;return S-s})}function $(){o.innerHTML=I().map(i=>`<tr data-id="${R(i.id)}">
|
|
90
|
+
<td>${R(i.name??i.id)}</td>
|
|
91
|
+
<td>${R(i.botName??i.larkAppId??"-")}</td>
|
|
92
|
+
<td><code>${R(i.parsed?.display??"?")}</code></td>
|
|
93
|
+
<td>${z(i.nextRunAt)}</td>
|
|
94
|
+
<td>${z(i.lastRunAt)} ${i.lastStatus==="error"?"\u26A0\uFE0F":""}</td>
|
|
95
95
|
<td>${i.repeat?`${i.repeat.completed}/${i.repeat.times??"\u221E"}`:"\u2014"}</td>
|
|
96
96
|
<td>${i.enabled?"\u2713":"\u2717"}</td>
|
|
97
97
|
<td class="actions-cell">
|
|
98
98
|
<button data-op="run" type="button">Run now</button>
|
|
99
99
|
${i.enabled?'<button data-op="pause" type="button">Pause</button>':'<button data-op="resume" type="button">Resume</button>'}
|
|
100
100
|
</td>
|
|
101
|
-
</tr>`).join("")||'<tr><td colspan="8" class="empty">No schedules.</td></tr>'}
|
|
101
|
+
</tr>`).join("")||'<tr><td colspan="8" class="empty">No schedules.</td></tr>'}o.addEventListener("click",async i=>{let M=i.target.closest("button[data-op]");if(!M)return;let f=M.closest("tr[data-id]");if(!f)return;let E=f.dataset.id,k=M.dataset.op;M.disabled=!0;let m=M.textContent;M.textContent="...";try{let S=await fetch(`/api/schedules/${encodeURIComponent(E)}/${k}`,{method:"POST"}),s=await S.json().catch(()=>({}));(!S.ok||s.ok===!1)&&alert(`Failed: ${S.status} ${s?.error??""}`.trim())}catch(S){alert("Network error: "+S)}finally{M.disabled=!1,M.textContent=m}}),c.addEventListener("input",$),A.on($),$()}var q={chats:[],bots:[]},lt=`
|
|
102
102
|
<form id="g-filters" class="filters">
|
|
103
103
|
<input type="search" name="q" placeholder="search chat name / id / owner" />
|
|
104
104
|
<label><input type="checkbox" name="missing"> missing-bot only</label>
|
|
@@ -110,12 +110,12 @@ ${E}${B}`)}}),$.querySelectorAll("th[data-sort]").forEach(t=>{t.addEventListener
|
|
|
110
110
|
<tbody id="g-body"></tbody>
|
|
111
111
|
</table>
|
|
112
112
|
<dialog id="g-drawer"></dialog>
|
|
113
|
-
`;function
|
|
113
|
+
`;function w(n){return n.replace(/[&<>"']/g,o=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o])}async function D(){q=await(await fetch("/api/groups")).json()}async function it(){return(await fetch("/api/groups")).json()}function ct(n,o){if(o.size===0)return!0;let c=n?.memberBots??[];for(let I of o)if(!c.some($=>$.larkAppId===I&&$.inChat))return!1;return!0}function V(n,o){return n.filter(c=>!o||!o.has(c.larkAppId)).map(c=>`
|
|
114
114
|
<label class="checkbox-row">
|
|
115
|
-
<input type="checkbox" name="bot" value="${
|
|
116
|
-
${
|
|
115
|
+
<input type="checkbox" name="bot" value="${w(c.larkAppId)}">
|
|
116
|
+
${w(c.botName??c.larkAppId)} <small>(${w(c.larkAppId)})</small>
|
|
117
117
|
</label>
|
|
118
|
-
`).join("")}async function Q(
|
|
118
|
+
`).join("")}async function Q(n){n.innerHTML=lt;let o=n.querySelector("#g-head"),c=n.querySelector("#g-body"),I=n.querySelector("#g-filters"),$=n.querySelector("#g-refresh"),i=n.querySelector("#g-drawer");$.onclick=async()=>{$.disabled=!0;try{await D(),m()}finally{$.disabled=!1}};let M=n.querySelector("#g-create");M.onclick=()=>f(),await D();function f(){let s=q.bots;if(s.length===0){alert("No bots online. Restart the daemon first.");return}i.innerHTML=`
|
|
119
119
|
<article>
|
|
120
120
|
<header><h3>Create new group</h3></header>
|
|
121
121
|
<p>Pick bots to invite. The dashboard auto-selects an online daemon as the chat creator/owner; the rest are added as members in the same call.</p>
|
|
@@ -124,71 +124,77 @@ ${E}${B}`)}}),$.querySelectorAll("th[data-sort]").forEach(t=>{t.addEventListener
|
|
|
124
124
|
<span>Group name <small>(optional)</small></span>
|
|
125
125
|
<input type="text" name="name" placeholder="e.g. AI ChangeLog" maxlength="60">
|
|
126
126
|
</label>
|
|
127
|
+
<label class="form-row">
|
|
128
|
+
<span>Bind directory <small>(optional)</small></span>
|
|
129
|
+
<input type="text" name="bindWorkingDir" placeholder="e.g. ~/projects/botmux">
|
|
130
|
+
<small>Create the group and bind every invited bot to this directory, so new topics skip the repo picker.</small>
|
|
131
|
+
</label>
|
|
127
132
|
<fieldset>
|
|
128
133
|
<legend>Bots</legend>
|
|
129
|
-
${V(
|
|
134
|
+
${V(s)}
|
|
130
135
|
</fieldset>
|
|
131
136
|
<div class="actions">
|
|
132
137
|
<button type="submit">Create</button>
|
|
133
138
|
<button type="button" id="g-create-cancel">Cancel</button>
|
|
134
139
|
</div>
|
|
135
140
|
</form>
|
|
136
|
-
</article>`,i.showModal(),i.querySelector("#g-create-cancel").onclick=()=>i.close(),i.querySelector("#g-createform").onsubmit=async r=>{r.preventDefault();let l=new FormData(r.target),
|
|
141
|
+
</article>`,i.showModal(),i.querySelector("#g-create-cancel").onclick=()=>i.close(),i.querySelector("#g-createform").onsubmit=async r=>{r.preventDefault();let l=new FormData(r.target),p=(l.get("name")??"").trim(),h=(l.get("bindWorkingDir")??"").trim(),u=l.getAll("bot");if(u.length===0){alert("Pick at least one bot.");return}let g=r.target.querySelector("button[type=submit]");g&&(g.disabled=!0,g.textContent="Creating...");try{let t=await fetch("/api/groups/create",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:p||void 0,larkAppIds:u,bindWorkingDir:h||void 0})}),e=await t.json();if(e.ok&&e.chatId){E(e);let a=Array.isArray(e.invalidBotIds)?e.invalidBotIds:[],b=u.filter(x=>!a.includes(x)),d=new Set(b);typeof e.creator=="string"&&e.creator&&d.add(e.creator),y(e.chatId,p||e.chatId,b,e.creator),m(),H(e.chatId,d).catch(()=>{})}else alert(`Failed: ${e.error??t.status}`),i.close()}catch(t){alert("Network error: "+t),i.close()}};function y(r,l,p,h){let u=new Set(p);h&&u.add(h);let g=q.bots.map(e=>({larkAppId:e.larkAppId,botName:e.botName,inChat:u.has(e.larkAppId),oncallChat:null})),t={chatId:r,name:l,ownerId:h??null,memberBots:g};q.chats=[t,...q.chats.filter(e=>e.chatId!==r)]}async function H(r,l){let p=[600,1200,1200,1200,1200,1200];for(let h of p){await new Promise(t=>setTimeout(t,h));let u;try{u=await it()}catch{continue}let g=(u.chats??[]).find(t=>t.chatId===r);if(g&&ct(g,l)){q=u,m();return}}}}function E(s){let y=String(s.chatId),H=`https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(y)}`,r=s.invalidBotIds??[],l=s.invalidUserIds??[],p=s.autoInvitedOpenId,h=!!s.autoInviteRejected,u=s.ownerTransferredTo,g=s.transferError,t=s.notifyMessageId,e=s.notifyError,a=Array.isArray(s.oncallBindings)?s.oncallBindings:[],b=a.filter(v=>v?.ok).length,d=a.filter(v=>!v?.ok),x=a.length>0?d.length===0?`<p class="hint-ok">\u5DF2\u7ED1\u5B9A\u76EE\u5F55\uFF1A<code>${w(s.bindResolvedPath??"")}</code>\uFF08${b}/${a.length} bots\uFF09</p>`:`<p class="hint-warn">\u76EE\u5F55\u7ED1\u5B9A\u90E8\u5206\u5931\u8D25\uFF1A\u6210\u529F ${b}/${a.length}\u3002${d.map(v=>`<br><code>${w(v.larkAppId??"?")}</code>: ${w(v.error??"unknown")}`).join("")}</p>`:"",C;if(p){let v=u?"<br><small>\u7FA4\u4E3B\u5DF2\u4ECE\u673A\u5668\u4EBA\u8F6C\u8BA9\u7ED9\u4F60\u3002</small>":g?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8\u8F6C\u8BA9\u7FA4\u4E3B\u5931\u8D25\uFF08${w(g)}\uFF09\uFF0C\u4F60\u73B0\u5728\u662F\u6210\u5458\u4F46\u7FA4\u4E3B\u4ECD\u662F\u673A\u5668\u4EBA\u3002</small>`:"",j=t?`<br><small>\u673A\u5668\u4EBA\u5DF2\u5728\u7FA4\u91CC @ \u4E86\u4F60\uFF08\u6D88\u606F id <code>${w(t)}</code>\uFF09\uFF0C\u770B\u98DE\u4E66\u901A\u77E5\u5C31\u80FD\u8FDB\u7FA4\u3002</small>`:e?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8 @ \u901A\u77E5\u5931\u8D25\uFF08${w(e)}\uFF09\uFF0C\u65B0\u7FA4\u53EF\u80FD\u4E0D\u4F1A\u4E3B\u52A8\u51FA\u73B0\u5728\u4F60\u4FA7\u8FB9\u680F\uFF0C\u5EFA\u8BAE\u4ECE\u4E0B\u9762\u6309\u94AE\u8DF3\u8FDB\u53BB\u3002</small>`:"";C=`<p class="hint-ok">\u5DF2\u81EA\u52A8\u9080\u8BF7\u4F60\uFF08<code>${w(p)}</code>\uFF09\u4F5C\u4E3A\u6210\u5458\u3002${v}${j}</p>`}else h?C='<p class="hint-warn">\u98DE\u4E66\u62D2\u7EDD\u4E86\u81EA\u52A8\u9080\u8BF7\uFF08\u4F60\u7684 open_id \u5728\u521B\u5EFA\u8005 bot \u7684 scope \u4E0B\u4E0D\u53EF\u7528\uFF09\u3002<strong>\u4F60\u76EE\u524D\u4E0D\u662F\u65B0\u7FA4\u6210\u5458</strong>\uFF0C\u9700\u8981\u8BA9\u7FA4\u91CC\u7684\u67D0\u4E2A\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u6765\u3002</p>':C='<p class="hint-warn">\u6CA1\u5728 dashboard \u7F13\u5B58\u91CC\u627E\u5230 ownerOpenId\uFF0C<strong>\u6CA1\u6709\u81EA\u52A8\u9080\u8BF7\u4F60</strong>\u3002\u70B9\u5F00\u4E0B\u9762\u94FE\u63A5\u524D\uFF0C\u5148\u8BA9\u7FA4\u91CC\u4EFB\u4E00\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u53BB\u3002</p>';let L=[r.length?`<li>\u65E0\u6548 bot id: <code>${r.map(w).join(", ")}</code></li>`:"",l.length?`<li>\u65E0\u6548\u7528\u6237 open_id: <code>${l.map(w).join(", ")}</code></li>`:""].filter(Boolean).join("");i.innerHTML=`
|
|
137
142
|
<article>
|
|
138
143
|
<header><h3>\u7FA4\u521B\u5EFA\u6210\u529F</h3></header>
|
|
139
|
-
<p><b>chatId:</b> <code>${
|
|
140
|
-
<p><b>\u521B\u5EFA\u8005:</b> <code>${
|
|
141
|
-
${
|
|
142
|
-
${
|
|
144
|
+
<p><b>chatId:</b> <code>${w(y)}</code> <button type="button" data-copy="${w(y)}">copy</button></p>
|
|
145
|
+
<p><b>\u521B\u5EFA\u8005:</b> <code>${w(s.creator??"?")}</code></p>
|
|
146
|
+
${C}
|
|
147
|
+
${x}
|
|
148
|
+
${L?`<ul>${L}</ul>`:""}
|
|
143
149
|
<div class="actions">
|
|
144
|
-
<a class="btn-link primary" href="${
|
|
150
|
+
<a class="btn-link primary" href="${H}" target="_blank" rel="noopener">\u2197 \u6253\u5F00\u65B0\u7FA4</a>
|
|
145
151
|
<button type="button" id="g-create-close">\u5173\u95ED</button>
|
|
146
152
|
</div>
|
|
147
|
-
</article>`,i.querySelectorAll("[data-copy]").forEach(
|
|
153
|
+
</article>`,i.querySelectorAll("[data-copy]").forEach(v=>{v.onclick=()=>{navigator.clipboard.writeText(v.dataset.copy??""),v.textContent="copied",setTimeout(()=>{v.textContent="copy"},800)}}),i.querySelector("#g-create-close").onclick=()=>i.close()}function k(){o.innerHTML=`<tr>
|
|
148
154
|
<th>chat</th>
|
|
149
|
-
${
|
|
155
|
+
${q.bots.map(s=>`<th>${w(s.botName??s.larkAppId)}</th>`).join("")}
|
|
150
156
|
<th>actions</th>
|
|
151
|
-
</tr>`}function
|
|
157
|
+
</tr>`}function m(){k();let s=new FormData(I),y=(s.get("q")??"").toLowerCase(),H=!!s.get("missing"),r=q.chats.filter(l=>!y||(l.name??"").toLowerCase().includes(y)||l.chatId.toLowerCase().includes(y)||(l.ownerId??"").toLowerCase().includes(y)).filter(l=>!H||l.memberBots.some(p=>!p.inChat));if(r.length===0){c.innerHTML=`<tr><td colspan="${q.bots.length+2}" class="empty">No chats match the filter.</td></tr>`;return}c.innerHTML=r.map(l=>`<tr data-chat="${w(l.chatId)}">
|
|
152
158
|
<td>
|
|
153
|
-
<strong>${
|
|
154
|
-
<small><code>${
|
|
159
|
+
<strong>${w(l.name??l.chatId)}</strong><br>
|
|
160
|
+
<small><code>${w(l.chatId)}</code></small>
|
|
155
161
|
</td>
|
|
156
|
-
${
|
|
162
|
+
${q.bots.map(p=>{let h=l.memberBots.find(t=>t.larkAppId===p.larkAppId),u=h?h.error?"!":h.inChat?"\u2713":"\u2717":"?";return`<td class="${h?h.error?"cell-error":h.inChat?"cell-in":"cell-out":"cell-unknown"}" title="${w(h?.error??"")}">${u}</td>`}).join("")}
|
|
157
163
|
<td>
|
|
158
164
|
<button class="add-bots" type="button">Add bots</button>
|
|
159
165
|
<button class="manage-chat" type="button">Manage</button>
|
|
160
166
|
</td>
|
|
161
|
-
</tr>`).join("")}
|
|
167
|
+
</tr>`).join("")}m(),c.addEventListener("click",async s=>{let y=s.target.closest("button.add-bots");if(!y)return;let r=y.closest("tr[data-chat]").dataset.chat,l=q.chats.find(u=>u.chatId===r);if(!l)return;let p=new Set(l.memberBots.filter(u=>u.inChat).map(u=>u.larkAppId));if(!q.bots.filter(u=>!p.has(u.larkAppId)).length){alert("All configured bots are already in this chat.");return}i.innerHTML=`
|
|
162
168
|
<article>
|
|
163
|
-
<header><h3>Add bots to ${
|
|
169
|
+
<header><h3>Add bots to ${w(l.name??l.chatId)}</h3></header>
|
|
164
170
|
<p>Select bots to add. The dashboard will pick a bot that's already in the chat as the proxy.</p>
|
|
165
171
|
<form id="g-addform">
|
|
166
|
-
${V(
|
|
172
|
+
${V(q.bots,p)}
|
|
167
173
|
<div class="actions">
|
|
168
174
|
<button type="submit">Confirm add</button>
|
|
169
175
|
<button type="button" id="g-cancel">Cancel</button>
|
|
170
176
|
</div>
|
|
171
177
|
</form>
|
|
172
|
-
</article>`,i.showModal(),i.querySelector("#g-cancel").onclick=()=>i.close(),i.querySelector("#g-addform").onsubmit=async u=>{u.preventDefault();let t=new FormData(u.target).getAll("bot");if(t.length===0){alert("Pick at least one bot.");return}try{let
|
|
173
|
-
`);alert(
|
|
178
|
+
</article>`,i.showModal(),i.querySelector("#g-cancel").onclick=()=>i.close(),i.querySelector("#g-addform").onsubmit=async u=>{u.preventDefault();let t=new FormData(u.target).getAll("bot");if(t.length===0){alert("Pick at least one bot.");return}try{let a=await(await fetch(`/api/groups/${encodeURIComponent(r)}/add-bots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:t})})).json();if(a.error==="no_proxy_bot")alert("No bot is currently in this chat \u2014 add one manually in Feishu first, then retry.");else if(a.result){let b=a.result.map(d=>`${d.id}: ${d.ok?"OK":`failed (${d.error??"unknown"})`}`).join(`
|
|
179
|
+
`);alert(b),await D(),m()}else alert(`Unexpected response: ${JSON.stringify(a)}`)}catch(e){alert("Network error: "+e)}finally{i.close()}}}),c.addEventListener("click",async s=>{let y=s.target.closest("button.manage-chat");if(!y)return;let r=y.closest("tr[data-chat]").dataset.chat,l=q.chats.find(p=>p.chatId===r);l&&S(l)});function S(s){let y=s.memberBots.filter(r=>r.inChat),H=typeof s.ownerId=="string"?s.ownerId:"";i.innerHTML=`
|
|
174
180
|
<article>
|
|
175
|
-
<header><h3>Manage ${
|
|
176
|
-
<p><b>chatId:</b> <code>${
|
|
177
|
-
<p><b>owner:</b> <code>${
|
|
181
|
+
<header><h3>Manage ${w(s.name??s.chatId)}</h3></header>
|
|
182
|
+
<p><b>chatId:</b> <code>${w(s.chatId)}</code></p>
|
|
183
|
+
<p><b>owner:</b> <code>${w(s.ownerId??"(unknown)")}</code></p>
|
|
178
184
|
|
|
179
185
|
<fieldset>
|
|
180
186
|
<legend>Oncall \u6A21\u5F0F</legend>
|
|
181
187
|
<p><small>\u5F00\u542F\u540E\uFF1A\u7FA4\u5185\u4EFB\u4F55\u6210\u5458\u90FD\u80FD @ \u673A\u5668\u4EBA\u63D0\u95EE\uFF0C\u65B0\u8BDD\u9898\u76F4\u63A5\u7528\u7ED1\u5B9A\u76EE\u5F55\u542F\u52A8 CLI\uFF1B\u4EC5 allowedUsers \u4ECD\u53EF\u6267\u884C /cd /restart \u7B49\u547D\u4EE4\u3002</small></p>
|
|
182
|
-
${y.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':y.map(r=>{let l=!!r.oncallChat,
|
|
183
|
-
<div class="oncall-row" data-bot="${
|
|
188
|
+
${y.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':y.map(r=>{let l=!!r.oncallChat,p=r.oncallChat?.workingDir??"";return`
|
|
189
|
+
<div class="oncall-row" data-bot="${w(r.larkAppId)}">
|
|
184
190
|
<label class="checkbox-row">
|
|
185
191
|
<input type="checkbox" data-action="toggle" ${l?"checked":""}>
|
|
186
|
-
<strong>${
|
|
187
|
-
<small>(${
|
|
192
|
+
<strong>${w(r.botName??r.larkAppId)}</strong>
|
|
193
|
+
<small>(${w(r.larkAppId)})</small>
|
|
188
194
|
</label>
|
|
189
195
|
<div class="oncall-row-body">
|
|
190
196
|
<input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
|
|
191
|
-
value="${
|
|
197
|
+
value="${w(p)}" ${l?"":"disabled"}>
|
|
192
198
|
<button type="button" data-action="save">Save</button>
|
|
193
199
|
<span class="oncall-status" data-status></span>
|
|
194
200
|
</div>
|
|
@@ -200,9 +206,9 @@ ${E}${B}`)}}),$.querySelectorAll("th[data-sort]").forEach(t=>{t.addEventListener
|
|
|
200
206
|
<legend>\u9009\u62E9\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A</legend>
|
|
201
207
|
${y.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':y.map(r=>`
|
|
202
208
|
<label class="checkbox-row">
|
|
203
|
-
<input type="checkbox" name="leave-bot" value="${
|
|
204
|
-
${
|
|
205
|
-
<small>${r.larkAppId===
|
|
209
|
+
<input type="checkbox" name="leave-bot" value="${w(r.larkAppId)}">
|
|
210
|
+
${w(r.botName??r.larkAppId)}
|
|
211
|
+
<small>${r.larkAppId===H?"\xB7 \u7FA4\u4E3B":""}</small>
|
|
206
212
|
</label>
|
|
207
213
|
`).join("")}
|
|
208
214
|
</fieldset>
|
|
@@ -213,14 +219,14 @@ ${E}${B}`)}}),$.querySelectorAll("th[data-sort]").forEach(t=>{t.addEventListener
|
|
|
213
219
|
</div>
|
|
214
220
|
<p class="hint-warn"><small>\u89E3\u6563\u7FA4\u804A\u4EC5\u5F53\u67D0\u4E2A\u5728\u7FA4\u673A\u5668\u4EBA\u662F\u7FA4\u4E3B\u65F6\u624D\u4F1A\u6210\u529F\u3002\u5426\u5219\u98DE\u4E66\u4F1A\u8FD4\u56DE\u9519\u8BEF\uFF0C\u5EFA\u8BAE\u6539\u7528\u300C\u9000\u51FA\u7FA4\u804A\u300D\u3002</small></p>
|
|
215
221
|
<form method="dialog"><button>\u5173\u95ED</button></form>
|
|
216
|
-
</article>`,i.showModal(),i.querySelectorAll(".oncall-row").forEach(r=>{let l=r.dataset.bot,
|
|
217
|
-
`);alert(
|
|
218
|
-
\u5173\u95ED\u4E86 ${
|
|
219
|
-
\u5173\u95ED\u4E86 ${
|
|
222
|
+
</article>`,i.showModal(),i.querySelectorAll(".oncall-row").forEach(r=>{let l=r.dataset.bot,p=r.querySelector("input[data-action=toggle]"),h=r.querySelector("input[data-input=workingDir]"),u=r.querySelector("button[data-action=save]"),g=r.querySelector("[data-status]");p.addEventListener("change",()=>{h.disabled=!p.checked,p.checked&&h.focus()}),u.addEventListener("click",async()=>{g.textContent="",g.className="oncall-status";let t=p.checked,e=h.value.trim();if(t&&!e){g.textContent="\u8BF7\u586B\u5DE5\u4F5C\u76EE\u5F55",g.classList.add("hint-warn-inline");return}u.disabled=!0;try{let a=`/api/groups/${encodeURIComponent(s.chatId)}/oncall/${encodeURIComponent(l)}`,b=t?await fetch(a,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({workingDir:e})}):await fetch(a,{method:"DELETE"}),d=await b.json().catch(()=>({}));if(b.ok&&d.ok){g.textContent=t?`\u2713 \u5DF2\u7ED1\u5B9A \u2192 ${d.resolvedPath??e}`:"\u2713 \u5DF2\u89E3\u7ED1",g.classList.add("hint-ok");try{await D(),m()}catch{}}else g.textContent=`\u2717 ${d.error??b.status}`,g.classList.add("hint-warn-inline")}catch(a){g.textContent=`\u2717 ${a?.message??a}`,g.classList.add("hint-warn-inline")}finally{u.disabled=!1}})}),i.querySelector("#g-leave-btn").onclick=async()=>{let r=[...i.querySelectorAll("input[name=leave-bot]:checked")].map(l=>l.value);if(r.length===0){alert("\u81F3\u5C11\u9009\u4E00\u4E2A\u673A\u5668\u4EBA");return}if(confirm(`\u786E\u5B9A\u8BA9 ${r.length} \u4E2A\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A\uFF1F\u8BE5 bot \u5728\u6B64\u7FA4\u7684\u4F1A\u8BDD\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))try{let p=await(await fetch(`/api/groups/${encodeURIComponent(s.chatId)}/leave`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:r})})).json(),h=(p.result??[]).map(u=>{if(!u.ok)return`${u.larkAppId}: \u5931\u8D25 (${u.error??"unknown"})`;let g=u.closedSessions??[],t=g.filter(b=>!b.ok).length,e=g.length-t,a=g.length===0?"":t===0?`\uFF08\u5173\u95ED ${e} \u4E2A\u4F1A\u8BDD\uFF09`:`\uFF08\u5173\u95ED ${e} \u4E2A\uFF0C${t} \u4E2A\u5931\u8D25\uFF09`;return`${u.larkAppId}: OK${a}`}).join(`
|
|
223
|
+
`);alert(h||`Unexpected: ${JSON.stringify(p)}`),await D(),m()}catch(l){alert("Network error: "+l)}finally{i.close()}},i.querySelector("#g-disband-btn").onclick=async()=>{if(y.length===0||!confirm(`\u786E\u5B9A\u89E3\u6563\u7FA4\u804A\u300C${s.name??s.chatId}\u300D\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\uFF0C\u672C\u7FA4\u6240\u6709\u673A\u5668\u4EBA\u4F1A\u8BDD\u4E5F\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))return;let r=[...y].sort((p,h)=>(h.larkAppId===H?1:0)-(p.larkAppId===H?1:0)),l=[];for(let p of r)try{let h=await fetch(`/api/groups/${encodeURIComponent(s.chatId)}/disband`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppId:p.larkAppId})}),u=await h.json();if(u.ok){let g=u.closedSessions??[],t=g.filter(b=>!b.ok).length,e=g.length-t,a=g.length===0?"":t===0?`
|
|
224
|
+
\u5173\u95ED\u4E86 ${e} \u4E2A\u4F1A\u8BDD\u3002`:`
|
|
225
|
+
\u5173\u95ED\u4E86 ${e} \u4E2A\u4F1A\u8BDD\uFF0C${t} \u4E2A\u4F1A\u8BDD\u5173\u95ED\u5931\u8D25\u3002`;alert(`\u5DF2\u89E3\u6563\uFF08\u7531 ${p.botName??p.larkAppId} \u6267\u884C\uFF09${a}`),await D(),m(),i.close();return}l.push(`${p.botName??p.larkAppId}: ${u.error??h.status}`)}catch(h){l.push(`${p.botName??p.larkAppId}: ${h}`)}alert(`\u6240\u6709\u5728\u7FA4\u673A\u5668\u4EBA\u5747\u65E0\u6CD5\u89E3\u6563\uFF1A
|
|
220
226
|
${l.join(`
|
|
221
227
|
`)}
|
|
222
228
|
|
|
223
|
-
\u5EFA\u8BAE\u6539\u7528\u300C\u9000\u51FA\u7FA4\u804A\u300D\u3002`)}}
|
|
229
|
+
\u5EFA\u8BAE\u6539\u7528\u300C\u9000\u51FA\u7FA4\u804A\u300D\u3002`)}}I.addEventListener("input",m)}var P={bots:[]},O=null,dt=`
|
|
224
230
|
<form id="bd-filters" class="filters">
|
|
225
231
|
<input type="search" name="q" placeholder="search bot name / app id" />
|
|
226
232
|
<button type="button" id="bd-refresh">Refresh</button>
|
|
@@ -230,18 +236,18 @@ ${l.join(`
|
|
|
230
236
|
Groups & Bots \u91CC\u5DF2\u7ECF\u624B\u52A8\u7ED1\u8FC7\u7684\u7FA4\u4E0D\u52A8\uFF1B\u901A\u8FC7 <code>/oncall unbind</code> \u89E3\u8FC7\u7ED1\u7684\u7FA4\u6C38\u8FDC\u4E0D\u518D\u88AB\u81EA\u52A8\u8986\u76D6\u3002
|
|
231
237
|
</p>
|
|
232
238
|
<div id="bd-list"></div>
|
|
233
|
-
`;function
|
|
234
|
-
<header><strong>${
|
|
235
|
-
<small>${
|
|
236
|
-
<p class="hint-warn-inline">\u67E5\u8BE2\u5931\u8D25\uFF1A${
|
|
237
|
-
</article>`;let
|
|
239
|
+
`;function B(n){return n.replace(/[&<>"']/g,o=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o])}async function X(){try{let n=await fetch("/api/bots"),o=await n.json().catch(()=>({}));if(!n.ok){O=o?.error?`HTTP ${n.status}: ${o.error}${o.path?` (${o.path})`:""}`:`HTTP ${n.status}`,P={bots:[]};return}if(!o||!Array.isArray(o.bots)){O="unexpected response shape (no `bots` array)",P={bots:[]};return}O=null,P=o}catch(n){O=n?.message??String(n),P={bots:[]}}}function Y(n){if(!n)return"\u2014";let o=new Date(n);return Number.isNaN(o.getTime())?"\u2014":o.toLocaleString()}async function Z(n){n.innerHTML=dt;let o=n.querySelector("#bd-list"),c=n.querySelector("#bd-filters"),I=n.querySelector("#bd-refresh");I.onclick=async()=>{I.disabled=!0;try{await X(),$()}finally{I.disabled=!1}},await X();function $(){let E=(new FormData(c).get("q")??"").toLowerCase(),k=P.bots.filter(m=>!E||(m.botName??"").toLowerCase().includes(E)||(m.larkAppId??"").toLowerCase().includes(E));if(O){o.innerHTML=`<p class="hint-warn">\u65E0\u6CD5\u52A0\u8F7D bot \u5217\u8868\uFF1A${B(O)}<br>\u5E38\u89C1\u539F\u56E0\uFF1Adashboard / daemon \u8FDB\u7A0B\u8FD8\u5728\u8DD1\u65E7\u4EE3\u7801\uFF0C\u6267\u884C <code>botmux restart</code> \u540E\u5237\u65B0\u3002</p>`;return}if(k.length===0){o.innerHTML='<p class="empty">\u6CA1\u6709\u5728\u7EBF\u7684 bot\u3002\u5148 `botmux restart` \u8BA9 daemon \u4E0A\u7EBF\u3002</p>';return}o.innerHTML=k.map(i).join(""),M()}function i(f){if(f.error)return`<article class="bd-card" data-appid="${B(f.larkAppId)}">
|
|
240
|
+
<header><strong>${B(f.botName??f.larkAppId)}</strong>
|
|
241
|
+
<small>${B(f.larkAppId)}</small></header>
|
|
242
|
+
<p class="hint-warn-inline">\u67E5\u8BE2\u5931\u8D25\uFF1A${B(f.error)}</p>
|
|
243
|
+
</article>`;let E=f.defaultOncall??{enabled:!1,workingDir:"",since:0},k=!!E.enabled;return`<article class="bd-card" data-appid="${B(f.larkAppId)}">
|
|
238
244
|
<header>
|
|
239
|
-
<strong>${
|
|
240
|
-
<small>${
|
|
245
|
+
<strong>${B(f.botName??f.larkAppId)}</strong>
|
|
246
|
+
<small>${B(f.larkAppId)}</small>
|
|
241
247
|
</header>
|
|
242
248
|
<div class="bd-body">
|
|
243
249
|
<label class="checkbox-row">
|
|
244
|
-
<input type="checkbox" data-action="toggle" ${
|
|
250
|
+
<input type="checkbox" data-action="toggle" ${k?"checked":""}>
|
|
245
251
|
<strong>\u9ED8\u8BA4\u8FDB oncall \u6A21\u5F0F</strong>
|
|
246
252
|
<small>\uFF08\u6240\u6709\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8\u7ED1\uFF09</small>
|
|
247
253
|
</label>
|
|
@@ -249,16 +255,16 @@ ${l.join(`
|
|
|
249
255
|
<label>
|
|
250
256
|
<span>\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55</span>
|
|
251
257
|
<input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
|
|
252
|
-
value="${
|
|
258
|
+
value="${B(E.workingDir??"")}" ${k?"":"disabled"}>
|
|
253
259
|
</label>
|
|
254
260
|
</div>
|
|
255
261
|
<div class="bd-meta">
|
|
256
|
-
<small>\u4E0A\u6B21\u542F\u7528\u65F6\u95F4\uFF1A${
|
|
257
|
-
<small>\u5DF2\u81EA\u52A8\u7ED1\u5B9A ${
|
|
262
|
+
<small>\u4E0A\u6B21\u542F\u7528\u65F6\u95F4\uFF1A${B(Y(E.since??0))}</small>
|
|
263
|
+
<small>\u5DF2\u81EA\u52A8\u7ED1\u5B9A ${f.autoboundChatCount??0} \u4E2A\u7FA4</small>
|
|
258
264
|
</div>
|
|
259
265
|
<div class="actions">
|
|
260
266
|
<button type="button" data-action="save">Save</button>
|
|
261
267
|
<span class="oncall-status" data-status></span>
|
|
262
268
|
</div>
|
|
263
269
|
</div>
|
|
264
|
-
</article>`}function
|
|
270
|
+
</article>`}function M(){o.querySelectorAll(".bd-card").forEach(f=>{let E=f.dataset.appid,k=f.querySelector("input[data-action=toggle]"),m=f.querySelector("input[data-input=workingDir]"),S=f.querySelector("button[data-action=save]"),s=f.querySelector("[data-status]");!k||!m||!S||!s||(k.addEventListener("change",()=>{m.disabled=!k.checked,k.checked&&m.focus()}),S.addEventListener("click",async()=>{s.textContent="",s.className="oncall-status";let y=k.checked,H=m.value.trim();if(y&&!H){s.textContent="\u5F00\u542F\u65F6\u5FC5\u987B\u586B\u5DE5\u4F5C\u76EE\u5F55",s.classList.add("hint-warn-inline");return}S.disabled=!0;try{let r=await fetch(`/api/bots/${encodeURIComponent(E)}/default-oncall`,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({enabled:y,workingDir:H})}),l=await r.json().catch(()=>({}));if(r.ok&&l.ok){let p=l.resolvedPath?` \u2192 ${l.resolvedPath}`:"";s.textContent=y?`\u2713 \u5DF2\u5F00\u542F${p}\uFF08\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8 oncall\uFF09`:"\u2713 \u5DF2\u5173\u95ED\uFF08\u5DF2\u7ED1\u5B9A\u7684\u7FA4\u4E0D\u52A8\uFF09",s.classList.add("hint-ok");let h=P.bots.find(g=>g.larkAppId===E);h&&l.defaultOncall&&(h.defaultOncall=l.defaultOncall);let u=f.querySelector(".bd-meta small:first-child");u&&l.defaultOncall?.since!=null&&(u.textContent=`\u4E0A\u6B21\u542F\u7528\u65F6\u95F4\uFF1A${Y(l.defaultOncall.since)}`)}else s.textContent=`\u2717 ${l.error??r.status}`,s.classList.add("hint-warn-inline")}catch(r){s.textContent=`\u2717 ${r?.message??r}`,s.classList.add("hint-warn-inline")}finally{S.disabled=!1}}))})}$(),c.addEventListener("input",$)}var F=document.getElementById("root");function tt(){let n=location.hash||"#/";n.startsWith("#/groups")?Q(F):n.startsWith("#/bot-defaults")?Z(F):n.startsWith("#/schedules")?K(F):W(F);for(let o of document.querySelectorAll("header nav a"))o.classList.toggle("active",o.getAttribute("href")===(n||"#/")||n==="#/"&&o.dataset.route==="sessions")}var U=document.getElementById("status");function et(){U&&(U.textContent=A.online?"\u25CF live":"\u25CF disconnected",U.className="status "+(A.online?"online":"offline"))}A.on(et);et();(async()=>{try{await J()}catch(n){console.error("botmux dashboard bootstrap failed",n),A.setOnline(!1)}window.addEventListener("hashchange",tt),tt()})();})();
|
package/dist/dashboard.js
CHANGED
|
@@ -566,6 +566,9 @@ const server = createServer(async (req, res) => {
|
|
|
566
566
|
// Feishu push notification — being a chat member alone doesn't always
|
|
567
567
|
// surface the chat in their sidebar (esp. mobile).
|
|
568
568
|
notifyOwnerOpenId: autoInvited ?? undefined,
|
|
569
|
+
bindWorkingDir: typeof parsed.bindWorkingDir === 'string' && parsed.bindWorkingDir.trim()
|
|
570
|
+
? parsed.bindWorkingDir.trim()
|
|
571
|
+
: undefined,
|
|
569
572
|
};
|
|
570
573
|
const upstream = await fetch(`http://127.0.0.1:${creator.ipcPort}/api/groups/create`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(forwardBody) });
|
|
571
574
|
const upstreamText = await upstream.text();
|