@lelouchhe/webagent 0.1.9 → 0.2.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/README.md +45 -268
- package/dist/index.html +2 -3
- package/dist/js/app.C4WRSLDF.js +10 -0
- package/dist/{styles.01a9ju9l.css → styles.01a6wdjv.css} +30 -5
- package/package.json +5 -4
- package/dist/js/app.IXP5KGP6.js +0 -8
- package/lib/bridge.js +0 -284
- package/lib/config.js +0 -62
- package/lib/daemon.js +0 -278
- package/lib/event-handler.js +0 -104
- package/lib/push-service.js +0 -168
- package/lib/routes.js +0 -929
- package/lib/server.js +0 -72
- package/lib/session-manager.js +0 -276
- package/lib/shared/constants.js +0 -16
- package/lib/sse-manager.js +0 -80
- package/lib/store.js +0 -174
- package/lib/title-service.js +0 -71
- package/lib/types.js +0 -13
package/dist/js/app.IXP5KGP6.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
var ce=class extends Error{name="ApiError";status;constructor(t,s){super(s),this.status=t}};async function U(e,t){let s=await fetch(e,t);if(!s.ok){let r=`HTTP ${s.status}`;try{let a=await s.json();a.error&&(r=String(a.error))}catch{}throw new ce(s.status,r)}let i=await s.text();if(i)return JSON.parse(i)}function q(e,t){return U(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}function He(e){let t={};return e?.cwd&&(t.cwd=e.cwd),e?.inheritFromSessionId&&(t.inheritFromSessionId=e.inheritFromSessionId),q("/api/v1/sessions",t)}function de(e){return U("/api/v1/sessions/"+e,{method:"DELETE"})}function te(){return U("/api/v1/sessions")}function P(e){return U("/api/v1/sessions/"+e)}function ue(e,t,s){let i={text:t};return s?.length&&(i.images=s),q("/api/v1/sessions/"+e+"/prompt",i)}function _e(e){return q("/api/v1/sessions/"+e+"/cancel",{})}function ne(e,t,s){return q("/api/v1/sessions/"+e+"/permissions/"+t,{optionId:s})}function se(e,t){return q("/api/v1/sessions/"+e+"/permissions/"+t,{denied:!0})}function K(e,t,s){let i=t.replace(/_/g,"-");return U("/api/v1/sessions/"+e+"/"+i,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:s})})}function Ne(e,t){return U("/api/v1/sessions/"+e+"/title",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})}function qe(e,t){return q("/api/v1/sessions/"+e+"/bash",{command:t})}function V(e,t,s){let i={visible:t};return s&&(i.sessionId=s),q("/api/v1/clients/"+e+"/visibility",i)}var T=e=>document.querySelector(e),o={messages:T("#messages"),input:T("#input"),sendBtn:T("#send-btn"),prompt:T("#input-prompt"),status:T("#status"),sessionInfo:T("#session-info"),newBtn:T("#new-btn"),attachBtn:T("#attach-btn"),fileInput:T("#file-input"),attachPreview:T("#attach-preview"),themeBtn:T("#theme-btn"),slashMenu:T("#slash-menu"),inputArea:T("#input-area"),statusBar:T("#status-bar")},n={eventSource:null,clientId:null,sessionId:null,sessionSwitchGen:0,sessionCwd:null,sessionTitle:null,awaitingNewSession:!1,configOptions:[],currentAssistantEl:null,currentAssistantText:"",currentThinkingEl:null,currentThinkingText:"",busy:!1,pendingImages:[],currentBashEl:null,followMessages:!0,pendingToolCallIds:new Set,pendingPermissionRequestIds:new Set,pendingPromptDone:!1,turnEnded:!1,newTurnStarted:!1,sentMessageForSession:null,sentBashForSession:null,cancelTimeout:1e4,_cancelTimerId:null,_onCancelTimeout:null,lastEventSeq:0,oldestLoadedSeq:0,hasMoreHistory:!1,loadingOlderEvents:!1,replayInProgress:!1,replayTarget:null,replayQueue:[],unconfirmedPermissions:new Map},at={disconnected:"is-disconnected",connecting:"is-connecting",connected:"is-connected"};function B(e,t=e){o.status.textContent="",o.status.className=`status-dot ${at[e]}`,o.status.dataset.state=e,o.status.setAttribute("aria-label",t),o.status.setAttribute("title",t)}function x(e){return n.configOptions.find(t=>t.id===e)}function ie(e){return x(e)?.currentValue??null}function Be(e,t){let s=x(e);s&&(s.currentValue=t)}function pe(e){n.configOptions=e,_(),O()}function _(){o.inputArea.classList.remove("plan-mode","autopilot-mode");let e=ie("mode")||"";e.includes("#plan")?o.inputArea.classList.add("plan-mode"):e.includes("#autopilot")&&o.inputArea.classList.add("autopilot-mode")}function O(){if(!o.statusBar)return;let e=ie("model"),t=n.sessionCwd||"";if(o.statusBar.textContent="",t){e&&o.statusBar.appendChild(document.createTextNode(e+" \xB7 "));let s=document.createElement("span");s.className="status-cwd",s.textContent=t,o.statusBar.appendChild(s)}else e&&(o.statusBar.textContent=e)}function I(e){n.busy=e,e?(o.sendBtn.textContent="^X",o.sendBtn.title="Cancel (Ctrl+X)",o.sendBtn.classList.add("cancel"),o.prompt.classList.add("busy")):(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel"),o.prompt.classList.remove("busy"))}function M({cwd:e,inheritFromSessionId:t=n.sessionId}={}){n.awaitingNewSession=!0,He({cwd:e,inheritFromSessionId:t}).catch(()=>{})}var Oe=[];function Ae(e){Oe.push(e)}function y(){for(let e of Oe)e();o.messages.innerHTML="",n.currentAssistantEl=null,n.currentAssistantText="",n.currentThinkingEl=null,n.currentThinkingText="",n.pendingImages.length=0,n.followMessages=!0,n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.unconfirmedPermissions.clear(),n.pendingPromptDone=!1,n.turnEnded=!1,n.newTurnStarted=!1,n._cancelTimerId=null,n.lastEventSeq=0,n.oldestLoadedSeq=0,n.hasMoreHistory=!1,n.loadingOlderEvents=!1,n.replayInProgress=!1,n.replayQueue=[],o.attachPreview.innerHTML="",o.attachPreview.classList.remove("active"),o.input.disabled=!1,o.sendBtn.disabled=!1,o.input.placeholder="",I(!1),n.sessionTitle=null,n.sessionCwd=null,n.configOptions=[],oe(null,null),o.statusBar&&(o.statusBar.textContent="")}function L(){o.newBtn.classList.toggle("hidden",o.input.value.length>0)}function G(){return!n.busy||!n.sessionId?!1:(_e(n.sessionId).catch(()=>{}),J(),n.cancelTimeout>0&&(n._cancelTimerId=setTimeout(()=>{n._cancelTimerId=null,n.busy&&(n.turnEnded=!0,I(!1),n._onCancelTimeout?.())},n.cancelTimeout)),!0)}function J(){n._cancelTimerId!=null&&(clearTimeout(n._cancelTimerId),n._cancelTimerId=null)}function Re(){return location.hash.slice(1)||null}function De(e){history.replaceState(null,"",`#${e}`)}function oe(e,t){o.sessionInfo.textContent=t||(e?e.slice(0,8)+"\u2026":""),document.title=t||">_"}B("disconnected");marked.setOptions({breaks:!0,gfm:!0});function me(e){return DOMPurify.sanitize(marked.parse(e))}function R(e,t){let s=document.createElement("div");return s.className=`msg ${e}`,s.innerHTML=e==="user"?u(t).replace(/\n/g,"<br>"):me(t),E(s),s}function c(e){let t=document.createElement("div");return t.className="system-msg",t.textContent=e,E(t),t}function H(){n.currentAssistantEl=null,n.currentAssistantText=""}function k(){if(n.currentThinkingEl){let e=n.currentThinkingEl.querySelector("summary");e.textContent="\u283F thought",e.classList.remove("active"),e.style.animation="none",n.currentThinkingEl=null,n.currentThinkingText=""}}var A=null,lt=80;function ge(e){return e.scrollHeight-e.scrollTop-e.clientHeight<lt}function ct(){n.followMessages=ge(o.messages)}o.messages.addEventListener("scroll",ct);function dt(){return n.followMessages||ge(o.messages)}function E(e,t=!1){if(n.replayTarget)return n.replayTarget.appendChild(e),e;let s=t||dt();return o.messages.appendChild(e),S(s),e}function Ue(){D(),A=document.createElement("div"),A.id="waiting",A.innerHTML='<span class="cursor">\u258C</span>',E(A,!0)}function D(){A&&(A.remove(),A=null)}var fe=!1;function S(e){let t=o.messages;if(e||n.followMessages){typeof requestAnimationFrame=="function"?fe||(fe=!0,requestAnimationFrame(()=>{fe=!1,t.scrollTop=t.scrollHeight})):t.scrollTop=t.scrollHeight,n.followMessages=!0;return}n.followMessages=ge(t)}function u(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function je(e){if(!e)return"";let t=new Date(e.endsWith("Z")?e:e+"Z"),s=i=>String(i).padStart(2,"0");return`${t.getFullYear()}-${s(t.getMonth()+1)}-${s(t.getDate())} ${s(t.getHours())}:${s(t.getMinutes())}`}function he(e){if(typeof e=="string"&&e.includes("*** Begin Patch")){let t=e.split(`
|
|
2
|
-
`),s=[];for(let i of t)i.startsWith("*** Begin Patch")||i.startsWith("*** End Patch")||(i.startsWith("*** Update File:")||i.startsWith("*** Add File:")||i.startsWith("*** Delete File:")?s.push(`<span class="diff-file">${u(i)}</span>`):i.startsWith("@@")?s.push(`<span class="diff-hunk">${u(i)}</span>`):i.startsWith("-")?s.push(`<span class="diff-del">${u(i)}</span>`):i.startsWith("+")?s.push(`<span class="diff-add">${u(i)}</span>`):s.push(u(i)));return s.join(`
|
|
3
|
-
`)}if(e&&typeof e=="object"){let t=[];if(e.path&&t.push(`<span class="diff-file">*** ${u(e.path)}</span>`),e.old_str!=null)for(let s of String(e.old_str).split(`
|
|
4
|
-
`))t.push(`<span class="diff-del">- ${u(s)}</span>`);if(e.new_str!=null)for(let s of String(e.new_str).split(`
|
|
5
|
-
`))t.push(`<span class="diff-add">+ ${u(s)}</span>`);return e.file_text!=null&&t.push(`<span class="diff-add">+ (new file, ${e.file_text.split(`
|
|
6
|
-
`).length} lines)</span>`),t.length>(e.path?1:0)?t.join(`
|
|
7
|
-
`):null}return null}function Q(e,t=!1){let s=document.createElement("div");return s.className="bash-block",s.innerHTML=`<span class="bash-cmd${t?" running":""}">${u(e)}</span><div class="bash-output"></div>`,s.querySelector(".bash-cmd").addEventListener("click",()=>{let i=s.querySelector(".bash-output");i.style.display==="none"?i.style.display="block":i.classList.contains("has-content")&&(i.style.display="none")}),E(s),t&&(n.currentBashEl=s),s}function Y(e,t,s){if(!e)return;let i=e.querySelector(".bash-cmd");i.classList.remove("running");let r="";if(s?r=`[signal: ${s}]`:t!==0&&t!=null&&(r=`[exit: ${t}]`),r){let a=document.createElement("span");a.className=`bash-exit ${t===0?"ok":"fail"}`,a.textContent=r,i.after(a)}e===n.currentBashEl&&(n.currentBashEl=null)}var ut={auto:"\u25D1",light:"\u2600",dark:"\u263E"},Fe=["auto","light","dark"];function We(){return localStorage.getItem("theme")||"auto"}function Ke(e){document.documentElement.setAttribute("data-theme",e),o.themeBtn.textContent=ut[e],o.themeBtn.title=`Theme: ${e}`,localStorage.setItem("theme",e)}o.themeBtn.onclick=()=>{let e=We();Ke(Fe[(Fe.indexOf(e)+1)%3])};Ke(We());var ye={read:"cat",edit:"edit",execute:"exec",search:"find",delete:"rm"},we="run",ve={pending:"\u25CB",in_progress:"\u25C9",completed:"\u25CF"};function Ve(e){return n.replayTarget?.querySelector(`[id="${e}"]`)??document.getElementById(e)}function pt(e){return n.replayTarget?.querySelector(e)??document.querySelector(e)}var Ge="webagent_notify_tip_shown",Je="webagent_notify_tip_denied_shown";function ft(){if(typeof Notification>"u"||n.replayInProgress)return;let e=Notification.permission;if(e!=="granted"){if(e==="denied"){if(localStorage.getItem(Je))return;localStorage.setItem(Je,"1"),c("tip: notifications are blocked \u2014 allow in browser site settings to enable");return}localStorage.getItem(Ge)||(localStorage.setItem(Ge,"1"),c("tip: use /notify to enable background notifications"))}}function re(){n.pendingPromptDone&&(n.pendingToolCallIds.size>0||n.pendingPermissionRequestIds.size>0||(D(),k(),H(),I(!1),n.pendingPromptDone=!1,ft()))}function mt(){for(let e of n.pendingToolCallIds){let t=document.getElementById(`tc-${e}`);if(!t)continue;t.className="tool-call failed";let s=t.querySelector(".icon");s&&(s.textContent="\u2717")}for(let e of n.pendingPermissionRequestIds){let t=document.querySelector(`.permission[data-request-id="${e}"]`);if(!t||!t.querySelector("button"))continue;let i=t.querySelector(".title")?.textContent||"\u26BF";t.innerHTML=`<span style="opacity:0.5">${u(i)} \u2014 cancelled</span>`}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function gt(){for(let e of n.pendingToolCallIds){let t=document.getElementById(`tc-${e}`);if(!t)continue;t.className="tool-call completed";let s=t.querySelector(".icon");s&&(s.textContent="\u2713")}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function Se(e){if(Array.isArray(e))return{events:e,streaming:{thinking:!1,assistant:!1}};let t=e;return{events:t.events??[],streaming:t.streaming??{thinking:!1,assistant:!1},total:typeof t.total=="number"?t.total:void 0,hasMore:typeof t.hasMore=="boolean"?t.hasMore:void 0}}var Qe=200;async function N(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${Qe}`);if(!t.ok)return!1;let s=await t.json(),{events:i,streaming:r,hasMore:a}=Se(s),l=document.createDocumentFragment(),d=Te(i);n.replayTarget=l;for(let p=0;p<i.length;p++){let m=JSON.parse(i[p].data);Ee(i[p].type,m,i,p,d)}return n.replayTarget=null,o.messages.style.display="none",o.messages.appendChild(l),o.messages.style.display="",i.length&&(n.lastEventSeq=i[i.length-1].seq,n.oldestLoadedSeq=i[0].seq),n.hasMoreHistory=a===!0,n.hasMoreHistory&&ht(),Ye(),Ie(i,r),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,ze()}}function Ie(e,t){if(t.thinking&&e.length){for(let s=e.length-1;s>=0;s--)if(e[s].type==="thinking"){let i=JSON.parse(e[s].data),r=o.messages.querySelectorAll(".thinking"),a=r[r.length-1];if(a){n.currentThinkingEl=a,n.currentThinkingText=i.text;let l=a.querySelector("summary");l&&(l.textContent="\u283F thinking...",l.classList.add("active"))}break}}if(t.assistant&&e.length){for(let s=e.length-1;s>=0;s--)if(e[s].type==="assistant_message"){let i=JSON.parse(e[s].data),r=o.messages.querySelectorAll(".msg.assistant"),a=r[r.length-1];a&&(n.currentAssistantEl=a,n.currentAssistantText=i.text);break}}}function Ye(){let e=o.messages.querySelector("[data-sync-boundary]");e&&e.removeAttribute("data-sync-boundary");let t=o.messages.lastElementChild;t&&t.setAttribute("data-sync-boundary","")}async function be(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=`/api/v1/sessions/${e}/events?after=${n.lastEventSeq}`,s=await fetch(t);if(!s.ok)return!1;let i=await s.json(),{events:r,streaming:a}=Se(i),l=o.messages.querySelector("[data-sync-boundary]");if(l)for(;l.nextElementSibling;)l.nextElementSibling.remove();if(n.currentAssistantEl=null,n.currentAssistantText="",n.currentThinkingEl=null,n.currentThinkingText="",n.currentBashEl=null,r.length===0)return Ie(r,a),!0;let d=document.createDocumentFragment(),p=Te(r);n.replayTarget=d;for(let m=0;m<r.length;m++){let g=JSON.parse(r[m].data);Ee(r[m].type,g,r,m,p)}return n.replayTarget=null,o.messages.appendChild(d),n.lastEventSeq=r[r.length-1].seq,Ye(),Ie(r,a),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,ze()}}var X=null;function ht(){ae();let e=document.createElement("div");e.id="history-sentinel",e.className="history-sentinel",e.textContent="\u2191 loading\u2026",o.messages.prepend(e),typeof IntersectionObserver=="function"&&(X=new IntersectionObserver(t=>{t[0]?.isIntersecting&&!n.loadingOlderEvents&&n.hasMoreHistory&&n.sessionId&&yt(n.sessionId)},{root:o.messages,rootMargin:"200px 0px 0px 0px"}),X.observe(e))}function ae(){X&&(X.disconnect(),X=null),document.getElementById("history-sentinel")?.remove()}Ae(ae);async function yt(e){if(n.loadingOlderEvents||!n.hasMoreHistory||n.oldestLoadedSeq<=0)return!1;n.loadingOlderEvents=!0;try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${Qe}&before=${n.oldestLoadedSeq}`);if(!t.ok||e!==n.sessionId)return!1;let s=await t.json(),{events:i,hasMore:r}=Se(s);if(i.length===0)return n.hasMoreHistory=!1,ae(),!0;let a=document.createDocumentFragment(),l=Te(i);n.replayTarget=a;for(let g=0;g<i.length;g++){let v=JSON.parse(i[g].data);Ee(i[g].type,v,i,g,l)}n.replayTarget=null;let d=o.messages,p=d.scrollHeight,m=document.getElementById("history-sentinel");return m?m.after(a):d.prepend(a),d.scrollTop+=d.scrollHeight-p,n.oldestLoadedSeq=i[0].seq,n.hasMoreHistory=r===!0,n.hasMoreHistory||ae(),!0}catch{return!1}finally{n.loadingOlderEvents=!1}}function Xe(){for(let[e,t]of n.unconfirmedPermissions){let s=document.querySelector(`.permission[data-request-id="${e}"]`);if(!s||!s.querySelector("button")){n.unconfirmedPermissions.delete(e);continue}t.denied?se(t.sessionId,e).catch(()=>{}):ne(t.sessionId,e,t.optionId).catch(()=>{});let i=s.dataset.title?`\u26BF ${u(s.dataset.title)}`:"\u26BF";s.innerHTML=`<span style="opacity:0.5">${i} \u2014 ${u(t.optionName)}</span>`,n.unconfirmedPermissions.delete(e)}}function Te(e){let t=new Set;for(let s of e)s.type==="permission_response"&&t.add(JSON.parse(s.data).requestId);return{toolCalls:new Map,permissions:new Map,resolvedPermissions:t,currentBashEl:null}}function Ee(e,t,s,i,r){switch(e){case"user_message":{let a=R("user",t.text);if(t.images)for(let l of t.images){let d=document.createElement("img");d.className="user-image",d.src=l.path,a.appendChild(d)}break}case"assistant_message":R("assistant",t.text);break;case"thinking":{let a=document.createElement("details");a.className="thinking",a.innerHTML=`<summary>\u283F thought</summary><div class="thinking-content">${u(t.text)}</div>`,E(a);break}case"tool_call":{let a=ye[t.kind]||we,l=document.createElement("div");l.className="tool-call",l.id=`tc-${t.id}`;let d=`<span class="icon">${a}</span> ${u(t.title)}`,p=t.rawInput;p&&p.command?d+=`<span class="tc-detail">$ ${u(p.command)}</span>`:p&&p.path&&(d+=`<span class="tc-detail">${u(p.path)}</span>`),l.innerHTML=d;let m=t.kind==="edit"?he(p):null;if(m){let v=document.createElement("details");v.innerHTML=`<summary>diff</summary><div class="diff-view">${m}</div>`,l.appendChild(v)}let g=l.querySelector(".tc-detail");g&&l.addEventListener("click",v=>{v.target.closest("details")||g.classList.toggle("expanded")}),E(l),r&&r.toolCalls.set(t.id,l);break}case"tool_call_update":{let a=r?r.toolCalls.get(t.id):Ve(`tc-${t.id}`);if(a){let l=t.status==="completed"?"\u2713":t.status==="failed"?"\u2717":"\u2026";a.className=`tool-call ${t.status}`;let d=a.querySelector(".icon");d&&(d.textContent=l)}(t.status==="completed"||t.status==="failed")&&n.pendingToolCallIds.delete(t.id);break}case"plan":{let a=document.createElement("div");a.className="plan",a.innerHTML='<div class="plan-title">\u2015 plan</div>'+(t.entries||[]).map(l=>`<div class="plan-entry">${ve[l.status]||"?"} ${u(l.content)}</div>`).join(""),E(a);break}case"permission_request":{let a=document.createElement("div");a.className="permission",a.dataset.requestId=t.requestId,a.dataset.title=t.title||"",a.innerHTML=`<span class="title" style="opacity:0.5">\u26BF ${u(t.title)}</span> `,!(r?r.resolvedPermissions.has(t.requestId):s&&s.slice(i+1).some(d=>d.type==="permission_response"&&JSON.parse(d.data).requestId===t.requestId))&&t.options&&(a.querySelector(".title").style.opacity="1",t.options.forEach(d=>{let p=document.createElement("button"),m=(d.kind||"").includes("allow");p.className=m?"allow":"deny",p.textContent=d.name,p.onclick=()=>{(d.kind||"").includes("reject")||(d.kind||"").includes("deny")?se(n.sessionId,t.requestId).catch(()=>{}):ne(n.sessionId,t.requestId,d.optionId).catch(()=>{}),a.innerHTML=`<span style="opacity:0.5">\u26BF ${u(t.title)} \u2014 ${u(d.name)}</span>`},a.appendChild(p)})),E(a),r&&r.permissions.set(t.requestId,a);break}case"permission_response":{let a=r?r.permissions.get(t.requestId):pt(`.permission[data-request-id="${t.requestId}"]`);if(a){let l=a.dataset.title?`\u26BF ${a.dataset.title}`:"\u26BF",d=t.optionName||(t.denied?"denied":"allowed");a.innerHTML=`<span style="opacity:0.5">${u(l)} \u2014 ${u(d)}</span>`}break}case"bash_command":{let a=Q(t.command,!1);r?r.currentBashEl=a:a.id="bash-replay-pending";break}case"bash_result":{let a=r?r.currentBashEl:Ve("bash-replay-pending");if(a){if(r||a.removeAttribute("id"),t.output){let l=a.querySelector(".bash-output");l&&(l.textContent=t.output,l.classList.add("has-content"))}Y(a,t.code,t.signal),r&&(r.currentBashEl=null)}break}case"prompt_done":n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,I(!1);break}}function ze(){let e=n.replayQueue;n.replayQueue=[];for(let t of e)wt(t)||C(t)}function wt(e){switch(e.type){case"tool_call":return!!document.getElementById(`tc-${e.id}`);case"permission_request":return!!document.querySelector(`.permission[data-request-id="${e.requestId}"]`);case"thought_chunk":return!!n.currentThinkingEl;case"message_chunk":return!!n.currentAssistantEl;default:return!1}}function C(e){if(n.replayInProgress){console.log("[handleEvent-DEBUG] QUEUED (replayInProgress):",e.type),n.replayQueue.push(e);return}if(!(e.sessionId&&e.type!=="session_created"&&e.type!=="session_deleted"&&(!n.sessionId||e.sessionId!==n.sessionId)))switch(e.type){case"connected":e.cancelTimeout!=null&&(n.cancelTimeout=e.cancelTimeout);break;case"session_created":if(!n.awaitingNewSession&&n.sessionId&&e.sessionId!==n.sessionId)break;if(n.awaitingNewSession=!1,n.sessionId=e.sessionId,n.sessionCwd=e.cwd||n.sessionCwd,n.sessionTitle=e.title||null,e.configOptions?.length&&pe(e.configOptions),De(n.sessionId),n.clientId&&V(n.clientId,!document.hidden,n.sessionId).catch(()=>{}),oe(n.sessionId,n.sessionTitle),B("connected","connected"),o.input.disabled=!1,o.sendBtn.disabled=!1,o.input.placeholder="",I(!!e.busyKind),n.newTurnStarted=!1,e.busyKind==="bash"){let t=document.getElementById("bash-replay-pending");t&&(t.removeAttribute("id"),t.querySelector(".bash-cmd")?.classList.add("running"),n.currentBashEl=t)}else n.currentBashEl=null;o.messages.children.length===0&&c(`Session created: ${n.sessionTitle||e.sessionId.slice(0,8)+"\u2026"}`),O();break;case"user_message":{if(n.sentMessageForSession===e.sessionId){n.sentMessageForSession=null;break}if(k(),H(),n.newTurnStarted=!0,n.turnEnded=!1,e.sessionId===n.sessionId){let t=R("user",e.text);if(e.images)for(let s of e.images){let i=document.createElement("img");i.className="user-image",i.src=s.path,t.appendChild(i)}}break}case"message_chunk":if(n.turnEnded)break;D(),k(),n.currentAssistantEl||(n.currentAssistantEl=R("assistant",""),n.currentAssistantText=""),n.currentAssistantText+=e.text,n.currentAssistantEl.innerHTML=me(n.currentAssistantText),S();break;case"thought_chunk":if(n.turnEnded)break;D(),n.currentThinkingEl||(n.currentThinkingEl=document.createElement("details"),n.currentThinkingEl.className="thinking",n.currentThinkingEl.innerHTML='<summary class="active">\u283F thinking...</summary><div class="thinking-content"></div>',n.currentThinkingText="",E(n.currentThinkingEl)),n.currentThinkingText+=e.text,n.currentThinkingEl.querySelector(".thinking-content").textContent=n.currentThinkingText,S();break;case"tool_call":{if(n.turnEnded)break;n.pendingToolCallIds.add(e.id),I(!0),D(),k(),H();let t=ye[e.kind]||we,s=document.createElement("div");s.className="tool-call",s.id=`tc-${e.id}`;let i=`<span class="icon">${t}</span> ${u(e.title)}`,r=e.rawInput;r&&r.command?i+=`<span class="tc-detail">$ ${u(r.command)}</span>`:r&&r.path&&(i+=`<span class="tc-detail">${u(r.path)}</span>`),s.innerHTML=i;let a=e.kind==="edit"?he(r):null;if(a){let d=document.createElement("details");d.innerHTML=`<summary>diff</summary><div class="diff-view">${a}</div>`,s.appendChild(d)}let l=s.querySelector(".tc-detail");l&&s.addEventListener("click",d=>{d.target.closest("details")||l.classList.toggle("expanded")}),E(s);break}case"tool_call_update":{let t=document.getElementById(`tc-${e.id}`);if((e.status==="completed"||e.status==="failed")&&n.pendingToolCallIds.delete(e.id),t){let s=e.status==="completed"?"\u2713":e.status==="failed"?"\u2717":"\u2026";t.className=`tool-call ${e.status}`;let i=t.querySelector(".icon");if(i&&(i.textContent=s),e.content&&e.content.length&&!t.querySelector("details")){let r=e.content.map(a=>a.type==="terminal"?`[terminal ${a.terminalId}]`:a.content?.text?a.content.text:Array.isArray(a.content)?a.content.map(l=>l.text||"").join(""):"").filter(Boolean).join(`
|
|
8
|
-
`);if(r){let a=document.createElement("details");a.innerHTML=`<summary>output</summary><div class="tc-content">${u(r)}</div>`,t.appendChild(a)}}}re(),S();break}case"plan":{k(),H();let t=document.createElement("div");t.className="plan",t.innerHTML='<div class="plan-title">\u2015 plan</div>'+e.entries.map(s=>`<div class="plan-entry">${ve[s.status]||"?"} ${u(s.content)}</div>`).join(""),E(t);break}case"permission_request":{if(n.turnEnded||document.querySelector(`.permission[data-request-id="${e.requestId}"]`))break;n.pendingPermissionRequestIds.add(e.requestId),I(!0),k();let t=document.createElement("div");t.className="permission",t.dataset.requestId=e.requestId,t.dataset.title=e.title||"",t.innerHTML=`<span class="title">\u26BF ${u(e.title)}</span> `,e.options.forEach(s=>{let i=document.createElement("button"),r=(s.kind||"").includes("allow");i.className=r?"allow":"deny",i.textContent=s.name,i.onclick=()=>{let a=(s.kind||"").includes("reject")||(s.kind||"").includes("deny");a?se(n.sessionId,e.requestId).catch(()=>{}):ne(n.sessionId,e.requestId,s.optionId).catch(()=>{}),n.pendingPermissionRequestIds.delete(e.requestId),n.unconfirmedPermissions.set(e.requestId,{sessionId:n.sessionId,optionId:s.optionId,optionName:s.name,denied:a}),t.innerHTML=`<span style="opacity:0.5">\u26BF ${u(e.title)} \u2014 ${u(s.name)}</span>`,re()},t.appendChild(i)}),E(t);break}case"permission_resolved":{n.pendingPermissionRequestIds.delete(e.requestId),n.unconfirmedPermissions.delete(e.requestId);let t=document.querySelector(`.permission[data-request-id="${e.requestId}"]`);if(e.sessionId===n.sessionId&&t){let s=t.dataset.title?`\u26BF ${t.dataset.title}`:"\u26BF",i=e.optionName||(e.denied?"denied":"allowed");t.innerHTML=`<span style="opacity:0.5">${u(s)} \u2014 ${u(i)}</span>`}re();break}case"bash_command":{if(n.sentBashForSession===e.sessionId){n.sentBashForSession=null;break}e.sessionId===n.sessionId&&(Q(e.command,!0),I(!0));break}case"bash_output":{if(e.sessionId!==n.sessionId)break;if(n.currentBashEl){let t=n.currentBashEl.querySelector(".bash-output");if(e.stream==="stderr"){let s=document.createElement("span");s.className="stderr",s.textContent=e.text,t.appendChild(s)}else t.appendChild(document.createTextNode(e.text));t.classList.add("has-content"),t.scrollTop=t.scrollHeight,S()}break}case"bash_done":{if(e.sessionId!==n.sessionId)break;Y(n.currentBashEl,e.code,e.signal),e.error&&c(`err: ${e.error}`),I(!1);break}case"prompt_done":{if(J(),e.stopReason==="cancelled"&&n.newTurnStarted){n.newTurnStarted=!1,k(),H();break}n.newTurnStarted=!1,e.stopReason==="cancelled"?mt():gt(),n.turnEnded=!0,n.pendingPromptDone=!0,re();break}case"session_deleted":e.sessionId===n.sessionId&&(c("warn: This session has been deleted."),o.input.disabled=!0,o.sendBtn.disabled=!0,o.input.placeholder="Session deleted");break;case"session_expired":y(),c("warn: Previous session expired, created new one."),M();break;case"config_set":{Be(e.configId,e.value);let t=x(e.configId),s=t?.name||e.configId,i=t?.options.find(r=>r.value===e.value)?.name||e.value;c(`ok: ${s}: ${i}`),e.configId==="mode"&&_(),O();break}case"config_option_update":e.configOptions?.length&&pe(e.configOptions);break;case"session_title_updated":e.sessionId===n.sessionId&&(n.sessionTitle=e.title,oe(n.sessionId,n.sessionTitle));break;case"error":n.awaitingNewSession=!1,n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,D(),k(),H(),c(`err: ${e.message}`),I(!1);break}}async function vt(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await fetch("/api/v1/push/vapid-key");if(!t.ok)return;let{publicKey:s}=await t.json(),r=(await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:St(s)})).toJSON();await fetch("/api/v1/push/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:r.endpoint,keys:r.keys,clientId:n.clientId})})}catch(e){console.error("[push] subscribe failed:",e)}}async function It(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await e.pushManager.getSubscription();if(!t)return;let s=t.endpoint;await t.unsubscribe(),await fetch("/api/v1/push/unsubscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:s})})}catch(e){console.error("[push] unsubscribe failed:",e)}}function St(e){let t="=".repeat((4-e.length%4)%4),s=(e+t).replace(/-/g,"+").replace(/_/g,"/"),i=atob(s),r=new Uint8Array(i.length);for(let a=0;a<i.length;a++)r[a]=i.charCodeAt(a);return r}async function ke(){try{let e=await navigator.serviceWorker?.ready;return e?await e.pushManager.getSubscription()!==null:!1}catch{return!1}}async function xe(e){let t=e.split(/\s+/),s=t[0].toLowerCase(),i=t.slice(1).join(" ").trim();switch(s){case"/new":return y(),c("Creating new session\u2026"),M({cwd:i||n.sessionCwd}),!0;case"/pwd":return c(`\u{1F4C1} ${n.sessionCwd||"unknown"}`),!0;case"/rename":{if(!n.sessionId)return c("err: No active session"),!0;if(!i)return c(`Current: ${n.sessionTitle||"(untitled)"}`),c("Usage: /rename <new title>"),!0;try{await Ne(n.sessionId,i),c(`Renamed \u2192 ${i}`)}catch{c("err: Failed to rename session")}return!0}case"/sessions":return c("Removed. Use /switch to see all sessions."),!0;case"/exit":{if(!n.sessionId)return c("warn: No active session"),!0;let r=n.sessionId;try{let l=(await te()).find(d=>d.id!==r);if(n.busy&&G(),de(r).catch(()=>{}),$=null,l){n.sessionSwitchGen++;let d=n.sessionSwitchGen;y(),n.sessionId=null;let[p]=await Promise.all([P(l.id),N(l.id)]);if(d!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:p.id,cwd:p.cwd,title:p.title,configOptions:p.configOptions,busyKind:p.busyKind}),S(!0)}else y(),n.sessionId=null,c("Creating new session\u2026"),M({inheritFromSessionId:null})}catch{c("err: Failed to exit session")}return!0}case"/prune":{try{let l=(await(await fetch("/api/v1/sessions")).json()).filter(d=>d.id!==n.sessionId);if(l.length===0)return c("No other sessions to prune."),!0;for(let d of l)de(d.id).catch(()=>{});$=null,c(`Pruned ${l.length} session(s).`)}catch{c("err: Failed to prune sessions")}return!0}case"/switch":{if(!i)return c("Usage: /switch <title or id prefix>"),!0;try{let a=await(await fetch("/api/v1/sessions")).json(),l=i.toLowerCase(),d=a.find(g=>g.id.startsWith(i)||g.title&&g.title.toLowerCase().includes(l));if(!d)return c(`err: No session matching "${i}"`),!0;n.sessionSwitchGen++;let p=n.sessionSwitchGen;y(),n.sessionId=null;let[m]=await Promise.all([P(d.id),N(d.id)]);if(p!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:m.id,cwd:m.cwd,title:m.title,configOptions:m.configOptions,busyKind:m.busyKind}),S(!0)}catch{y(),n.sessionId=null,c("err: Failed to switch session")}return!0}case"/cancel":return n.busy?(G(),c("^X")):c("Nothing to cancel."),!0;case"/help":case"?":c("? \u2014 Show help"),c("/help \u2014 Show help (alias)"),c("!<command> \u2014 Run bash command");for(let r of Ze){let a=r.args?`${r.cmd} ${r.args}`:r.cmd;c(`${a} \u2014 ${r.desc}`)}c("--- Shortcuts ---");for(let r of bt)c(`${r.key} \u2014 ${r.desc}`);return!0;case"/model":case"/mode":case"/think":{let a={"/model":"model","/mode":"mode","/think":"reasoning_effort"}[s],l=x(a);if(!i){let v=l?.options.find(ee=>ee.value===l.currentValue)?.name||l?.currentValue||"unknown";return c(`${l?.name||a}: ${v}`),c(`Type ${s} + space to pick from list`),!0}if(!l)return c(`err: ${s.slice(1)} is not available.`),!0;let d=i.trim(),p=v=>v.toLowerCase().replace(/[\s_]+/g,"-"),m=p(d),g=l.options.find(v=>p(v.value)===m||p(v.name)===m);if(!g){let v=l.options.filter(ee=>p(ee.value).includes(m)||p(ee.name).includes(m));if(v.length===1)g=v[0];else if(v.length>1)return c(`err: Ambiguous "${i}". Type ${s} + space to see options.`),!0}return g?(l.currentValue=g.value,_(),O(),c(`${l.name} \u2192 ${g.name}`),await K(n.sessionId,a,g.value).catch(()=>{}),!0):(c(`err: Unknown "${i}". Type ${s} + space to see options.`),!0)}case"/notify":{if(typeof Notification>"u")return c("err: notifications not supported in this browser"),!0;let r=i.toLowerCase();if(r==="on"){if(Notification.permission==="denied")return c("notify: blocked \u2014 allow in browser site settings to enable"),!0;if(Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted")return c("notify: blocked \u2014 allow in browser site settings to enable"),!0;let l=await ke();return await vt(),c(l?"notify: already enabled":"notify: enabled"),!0}if(r==="off")return await It(),c("notify: disabled"),!0;let a=Notification.permission;return a==="denied"?c("notify: blocked \u2014 allow in browser site settings to enable"):a==="granted"&&await ke()?c("notify: enabled"):c("notify: off \u2014 use /notify on to enable"),!0}default:return!1}}var Ze=[{cmd:"/cancel",args:"",desc:"Cancel current response"},{cmd:"/exit",args:"",desc:"Close current session"},{cmd:"/mode",args:"[name]",desc:"Pick or switch mode"},{cmd:"/model",args:"[name]",desc:"Pick or switch model"},{cmd:"/new",args:"[cwd]",desc:"New session"},{cmd:"/notify",args:"[on|off]",desc:"Toggle background notifications"},{cmd:"/prune",args:"",desc:"Delete all sessions except current"},{cmd:"/pwd",args:"",desc:"Show working directory"},{cmd:"/rename",args:"<new title>",desc:"Rename current session"},{cmd:"/switch",args:"<title|id>",desc:"Switch to session"},{cmd:"/think",args:"[level]",desc:"Pick or switch reasoning effort"}],bt=[{key:"Enter",desc:"Send message"},{key:"Shift+Enter",desc:"New line"},{key:"^X",desc:"Cancel current response"},{key:"^M",desc:"Cycle mode (Agent \u2192 Plan \u2192 Autopilot)"},{key:"^U",desc:"Upload image"}],b=-1,f=[],w="commands",z=null,$=null,W=null,Ce=!1;function Z(){let e=o.input.value;if(W!==null){if(e===W)return;W=null}let t=e.match(/^\/new /);if(t){let l=e.slice(t[0].length).toLowerCase();Et(l);return}let s=e.match(/^\/switch /);if(s){let l=e.slice(s[0].length).toLowerCase();Tt(l,"switch");return}let i=e.match(/^\/(model|mode|think) /);if(i){let d={model:"model",mode:"mode",think:"reasoning_effort"}[i[1]],p=e.slice(i[0].length).toLowerCase();kt(d,p);return}let r=e.match(/^\/notify /);if(r){let l=e.slice(r[0].length).toLowerCase();xt(l);return}if(!e.startsWith("/")||e.includes(" ")){h();return}w="commands";let a=e.toLowerCase();if(f=Ze.filter(l=>l.cmd.startsWith(a)),f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}async function Tt(e,t="switch"){if(!$)try{$=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{$=null},5e3)}catch{return}if(w=t,f=$.filter(i=>e?i.title&&i.title.toLowerCase().includes(e)||i.id.startsWith(e):!0),f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}async function Et(e){if(!$)try{$=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{$=null},5e3)}catch{return}w="new";let t=new Map;for(let i of $){let r=t.get(i.cwd);(!r||(i.last_active_at||i.created_at)>r.time)&&t.set(i.cwd,{cwd:i.cwd,time:i.last_active_at||i.created_at})}let s=[...t.values()].sort((i,r)=>r.time.localeCompare(i.time));if(e&&(s=s.filter(i=>i.cwd.toLowerCase().includes(e))),f=s,f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}function kt(e,t){let s=x(e);if(!s){h();return}if(w="config",z=e,f=s.options.filter(i=>t?i.value.toLowerCase().includes(t)||i.name.toLowerCase().includes(t):!0),f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}var Ct=[{value:"on",name:"on",desc:"Enable background notifications"},{value:"off",name:"off",desc:"Disable background notifications"}];async function xt(e){if(w="notify",f=Ct.filter(i=>e?i.value.includes(e)||i.name.includes(e):!0),f.length===0){h();return}Ce=await ke();let t=Ce?"on":"off",s=f.findIndex(i=>i.value===t);b=s>=0?s:0,F(),o.slashMenu.classList.add("active")}function F(){if(w==="new"){let t=(n.sessionCwd||"").toLowerCase();o.slashMenu.innerHTML=f.map((s,i)=>{let r=s.cwd.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${r?' style="color:var(--green)"':""}>${u((r?"* ":" ")+s.cwd)}</span></div>`}).join("")}else if(w==="config"){let t=ie(z)?.toLowerCase()||"";o.slashMenu.innerHTML=f.map((s,i)=>{let r=s.value.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${r?' style="color:var(--green)"':""}>${u((r?"* ":" ")+s.name)}</span></div>`}).join("")}else if(w==="notify"){let t=Ce?"on":"off";o.slashMenu.innerHTML=f.map((s,i)=>{let r=s.value===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${r?' style="color:var(--green)"':""}>${u((r?"* ":" ")+s.name)}</span><span class="slash-desc">${u(s.desc)}</span></div>`}).join("")}else w==="switch"?o.slashMenu.innerHTML=f.map((t,s)=>{let i=t.id===n.sessionId,r=i?"* ":" ",a=t.title||t.id.slice(0,8)+"\u2026",l=je(t.last_active_at||t.created_at);return`<div class="slash-item${s===b?" selected":""}" data-idx="${s}"><span class="slash-cmd"${i?' style="color:var(--green)"':""}>${u(r+a)}</span><span class="slash-desc">${u(t.cwd)} (${u(l)})</span></div>`}).join(""):o.slashMenu.innerHTML=f.map((t,s)=>{let i=t.args?`${t.cmd} ${t.args}`:t.cmd;return`<div class="slash-item${s===b?" selected":""}" data-idx="${s}"><span class="slash-cmd">${u(i)}</span><span class="slash-desc">${u(t.desc)}</span></div>`}).join("");let e=o.slashMenu.querySelector(".selected");e&&e.scrollIntoView({block:"nearest"})}function h(){o.slashMenu.classList.remove("active"),b=-1,f=[],w="commands",W=o.input.value}function Mt(e){if(!(e<0||e>=f.length)){if(w==="commands"){let t=f[e];o.input.value=t.cmd+(t.args?" ":""),h(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(W=null,Z())}else if(w==="config"){let t=f[e],s={model:"/model",mode:"/mode",reasoning_effort:"/think"}[z]||`/${z}`;o.input.value=`${s} ${t.name}`,h(),o.input.focus()}else if(w==="notify"){let t=f[e];o.input.value=`/notify ${t.value}`,h(),o.input.focus()}else if(w==="new"){let t=f[e];o.input.value=`/new ${t.cwd}`,h(),o.input.focus()}else if(w==="switch"){let t=f[e];o.input.value=`/switch ${t.title||t.id}`,h(),o.input.focus()}L()}}async function Lt(e){if(!(e<0||e>=f.length)){if(w==="new"){let t=f[e];o.input.value="",h(),y(),c("Creating new session\u2026"),M({cwd:t.cwd})}else if(w==="config"){let t=f[e],s=z,i=x(s);o.input.value="",h(),i&&(i.currentValue=t.value),_(),O(),c(`${i?.name||s} \u2192 ${t.name}`),await K(n.sessionId,s,t.value).catch(()=>{})}else if(w==="switch"){let t=f[e];o.input.value="",h(),y(),n.sessionId=null,c("Switching\u2026"),Promise.all([P(t.id),N(t.id)]).then(([s,i])=>{C({type:"session_created",sessionId:s.id,cwd:s.cwd,title:s.title,configOptions:s.configOptions,busyKind:s.busyKind}),i&&S(!0)}).catch(()=>{y(),n.sessionId=null,c("err: Failed to switch session")})}else if(w==="notify"){let t=f[e];o.input.value=`/notify ${t.value}`,h(),xe(o.input.value),o.input.value=""}else{let t=f[e];o.input.value=t.cmd+(t.args?" ":""),h(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(W=null,Z())}L()}}function et(e){return o.slashMenu.classList.contains("active")?e.key==="ArrowDown"?(b=(b+1)%f.length,F(),!0):e.key==="ArrowUp"?(b=(b-1+f.length)%f.length,F(),!0):e.key==="Tab"?(Mt(b),!0):!1:!1}o.slashMenu.addEventListener("mousedown",e=>{e.preventDefault();let t=e.target.closest(".slash-item");t&&Lt(Number(t.dataset.idx))});o.input.addEventListener("input",()=>{Z(),o.inputArea.classList.toggle("bash-mode",o.input.value.startsWith("!"))});function tt(e){return new Promise(t=>{let s=new FileReader;s.onload=()=>{let i=s.result.split(",")[1];t({data:i,mimeType:e.type,previewUrl:s.result})},s.readAsDataURL(e)})}function nt(e){n.pendingImages.push(e),le(),o.input.focus()}function le(){if(o.attachPreview.innerHTML="",n.pendingImages.length===0){o.attachPreview.classList.remove("active");return}o.attachPreview.classList.add("active"),n.pendingImages.forEach((e,t)=>{let s=document.createElement("span");s.className="attach-thumb",s.innerHTML=`<img src="${e.previewUrl}"><button class="remove">\xD7</button>`,s.querySelector(".remove").addEventListener("click",()=>{n.pendingImages.splice(t,1),le()}),o.attachPreview.appendChild(s)})}o.attachBtn.onclick=()=>o.fileInput.click();o.fileInput.onchange=async()=>{for(let e of o.fileInput.files)e.type.startsWith("image/")&&nt(await tt(e));o.fileInput.value=""};o.input.addEventListener("paste",async e=>{for(let t of e.clipboardData.items)t.type.startsWith("image/")&&(e.preventDefault(),nt(await tt(t.getAsFile())))});function Me(){return n.clientId!==null}n._onCancelTimeout=()=>c("warn: Agent not responding to cancel");function st(){let e=o.input.value.trim();if(!e&&n.pendingImages.length===0)return;if((e.startsWith("/")||e==="?"||e.startsWith("? "))&&n.pendingImages.length===0){o.input.value="",o.input.style.height="auto",L(),Le(),xe(e);return}if(e.startsWith("!")&&n.pendingImages.length===0){let i=e.slice(1).trim();if(!i)return;if(!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!Me()){c("warn: Not connected, please retry");return}o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),L(),Q(i,!0),n.sentBashForSession=n.sessionId,qe(n.sessionId,i).catch(()=>{}),I(!0);return}if(n.busy)return;if(o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),L(),!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!Me()){c("warn: Not connected, please retry");return}let t=R("user",e||"(image)");for(let i of n.pendingImages){let r=document.createElement("img");r.className="user-image",r.src=i.previewUrl,t.appendChild(r)}let s=n.pendingImages.slice();n.pendingImages.length=0,le(),s.length>0?Promise.all(s.map(i=>fetch(`/api/v1/sessions/${n.sessionId}/images`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({data:i.data,mimeType:i.mimeType})}).then(r=>r.json()).then(r=>({data:i.data,mimeType:i.mimeType,path:r.url})))).then(i=>{if(!Me()){t.remove(),c("warn: Not connected, please retry"),I(!1);return}ue(n.sessionId,e||"What is in this image?",i.map(r=>({data:r.data,mimeType:r.mimeType,path:r.path}))).catch(()=>{})}):ue(n.sessionId,e).catch(()=>{}),n.turnEnded=!1,n.sentMessageForSession=n.sessionId,I(!0),Ue()}function it(){G()&&c("^X")}function ot(){let e=o.input.value.trim();return e.startsWith("/")||e.startsWith("!")||e==="?"||e.startsWith("? ")}function Le(){n.busy&&(ot()?(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel")):(o.sendBtn.textContent="^X",o.sendBtn.title="Cancel (Ctrl+X)",o.sendBtn.classList.add("cancel")))}o.sendBtn.onclick=()=>{n.busy&&!ot()?it():st()};o.input.addEventListener("keydown",e=>{if(et(e)){e.preventDefault();return}if(e.key==="Enter"&&!e.shiftKey){e.preventDefault(),h(),st();return}if(e.key==="u"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey){e.preventDefault(),o.fileInput.click();return}});document.addEventListener("keydown",e=>{if(e.key==="x"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&n.busy){e.preventDefault(),it();return}e.key==="Escape"&&o.slashMenu.classList.contains("active")&&(e.preventDefault(),h(),o.input.focus())});function rt(){let e=x("mode");if(!e||!e.options.length)return;let t=e.options.findIndex(i=>i.value===e.currentValue),s=e.options[(t+1)%e.options.length];e.currentValue=s.value,K(n.sessionId,"mode",s.value).catch(()=>{}),c(`Mode \u2192 ${s.name}`),_()}document.addEventListener("keydown",e=>{e.key==="m"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&(e.preventDefault(),rt())});o.prompt.addEventListener("click",rt);o.newBtn.addEventListener("click",()=>{o.input.value="/new ",L(),Le(),Z(),o.input.focus()});o.input.addEventListener("input",()=>{L(),Le()});o.input.addEventListener("focus",L);o.input.addEventListener("input",()=>{o.input.style.height="auto",o.input.style.height=Math.min(o.input.scrollHeight,200)+"px"});async function $t(e){try{let t=await navigator.serviceWorker?.ready;if(!t)return;let s=await t.pushManager.getSubscription();if(!s)return;await fetch("/api/v1/push/register-client",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientId:e,endpoint:s.endpoint})})}catch{}}function Pe(){B("connecting","connecting");let e=new EventSource("/api/v1/events/stream");n.eventSource=e,e.onmessage=async t=>{let s=JSON.parse(t.data);if(s.type==="connected"){n.clientId=s.clientId,V(s.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),$t(s.clientId);return}C(s)},e.onerror=()=>{e.close(),Ht(),setTimeout(Pe,3e3)},Pt()}async function Pt(){B("connecting","session loading");let e=n.sessionSwitchGen,t=Re();if(t&&t===n.sessionId&&n.lastEventSeq>0){if(await $e(t,!0,e),e!==n.sessionSwitchGen)return;Xe(),S(!1);return}if(t){if(y(),await $e(t,!1,e),e!==n.sessionSwitchGen)return;S(!0);return}try{let s=await te();if(e!==n.sessionSwitchGen)return;if(s.length>0){if(y(),await $e(s[0].id,!1,e),e!==n.sessionSwitchGen)return;S(!0);return}}catch{}e===n.sessionSwitchGen&&M()}async function $e(e,t,s){if(t){try{let i=await P(e);if(s!==n.sessionSwitchGen)return;C({type:"session_created",sessionId:i.id,cwd:i.cwd,title:i.title,configOptions:i.configOptions,busyKind:i.busyKind})}catch{if(s!==n.sessionSwitchGen)return;y(),c("warn: Previous session expired, created new one."),M();return}if(s!==n.sessionSwitchGen)return;await be(e)}else{n.sessionId=null;let i=N(e),r;try{let[a,l]=await Promise.all([P(e),i]);if(s!==n.sessionSwitchGen)return;r=a,l||c("warn: Failed to load history.")}catch{if(s!==n.sessionSwitchGen)return;y(),c("warn: Previous session expired, created new one."),M();return}C({type:"session_created",sessionId:r.id,cwd:r.cwd,title:r.title,configOptions:r.configOptions,busyKind:r.busyKind})}}function Ht(){B("disconnected","disconnected"),n.eventSource=null,n.clientId=null,k(),H(),n.currentBashEl&&Y(n.currentBashEl,null,"disconnected"),n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,n.turnEnded=!1,J(),I(!1)}document.addEventListener("visibilitychange",()=>{n.clientId&&V(n.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),!document.hidden&&n.sessionId&&n.lastEventSeq>0&&!n.replayInProgress&&be(n.sessionId).then(()=>S(!1))});Pe();"serviceWorker"in navigator&&(navigator.serviceWorker.register("/sw.js"),navigator.serviceWorker.addEventListener("message",e=>{if(e.data?.type==="navigate"&&e.data.sessionId){let t=e.data.sessionId;if(n.sessionId===t)return;n.sessionSwitchGen++;let s=n.sessionSwitchGen;history.replaceState(null,"",`#${t}`),y(),n.sessionId=null,c("Switching\u2026"),Promise.all([P(t),N(t)]).then(([i,r])=>{s===n.sessionSwitchGen&&(C({type:"session_created",sessionId:i.id,cwd:i.cwd,title:i.title,configOptions:i.configOptions,busyKind:i.busyKind}),r&&S(!0))}).catch(()=>{y(),n.sessionId=null,c("err: Failed to switch session")})}}));
|
package/lib/bridge.js
DELETED
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
import { spawn, ChildProcess } from "node:child_process";
|
|
2
|
-
import { Writable, Readable } from "node:stream";
|
|
3
|
-
import { EventEmitter } from "node:events";
|
|
4
|
-
import * as acp from "@agentclientprotocol/sdk";
|
|
5
|
-
export class AgentBridge extends EventEmitter {
|
|
6
|
-
proc = null;
|
|
7
|
-
conn = null;
|
|
8
|
-
permissionResolvers = new Map();
|
|
9
|
-
permissionRequestSessions = new Map();
|
|
10
|
-
silentSessions = new Set(); // Sessions that don't emit events
|
|
11
|
-
silentBuffers = new Map(); // Text buffers for silent sessions
|
|
12
|
-
agentCmd;
|
|
13
|
-
constructor(agentCmd) {
|
|
14
|
-
super();
|
|
15
|
-
this.agentCmd = agentCmd;
|
|
16
|
-
}
|
|
17
|
-
async start() {
|
|
18
|
-
const [cmd, ...args] = this.agentCmd.split(/\s+/);
|
|
19
|
-
this.proc = spawn(cmd, args, {
|
|
20
|
-
stdio: ["pipe", "pipe", "inherit"],
|
|
21
|
-
});
|
|
22
|
-
if (!this.proc.stdin || !this.proc.stdout) {
|
|
23
|
-
throw new Error(`Failed to start: ${this.agentCmd}`);
|
|
24
|
-
}
|
|
25
|
-
const input = Writable.toWeb(this.proc.stdin);
|
|
26
|
-
const output = Readable.toWeb(this.proc.stdout);
|
|
27
|
-
const stream = acp.ndJsonStream(input, output);
|
|
28
|
-
const client = {
|
|
29
|
-
requestPermission: async (params) => this.handlePermission(params),
|
|
30
|
-
sessionUpdate: async (params) => this.handleSessionUpdate(params),
|
|
31
|
-
readTextFile: async (params) => this.handleReadFile(params),
|
|
32
|
-
writeTextFile: async (params) => this.handleWriteFile(params),
|
|
33
|
-
};
|
|
34
|
-
this.conn = new acp.ClientSideConnection((_agent) => client, stream);
|
|
35
|
-
const init = await this.conn.initialize({
|
|
36
|
-
protocolVersion: acp.PROTOCOL_VERSION,
|
|
37
|
-
clientCapabilities: {
|
|
38
|
-
fs: { readTextFile: true, writeTextFile: true },
|
|
39
|
-
terminal: true,
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
const agentInfo = init.agentInfo;
|
|
43
|
-
this.emit("event", {
|
|
44
|
-
type: "connected",
|
|
45
|
-
agent: {
|
|
46
|
-
name: agentInfo?.name ?? "unknown",
|
|
47
|
-
version: agentInfo?.version ?? "?",
|
|
48
|
-
},
|
|
49
|
-
configOptions: [],
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
async newSession(cwd, opts) {
|
|
53
|
-
if (!this.conn)
|
|
54
|
-
throw new Error("Not connected");
|
|
55
|
-
const session = await this.conn.newSession({ cwd, mcpServers: [] });
|
|
56
|
-
if (!opts?.silent) {
|
|
57
|
-
this.emit("event", {
|
|
58
|
-
type: "session_created",
|
|
59
|
-
sessionId: session.sessionId,
|
|
60
|
-
cwd,
|
|
61
|
-
configOptions: session.configOptions ?? [],
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
return session.sessionId;
|
|
65
|
-
}
|
|
66
|
-
async loadSession(sessionId, cwd) {
|
|
67
|
-
if (!this.conn)
|
|
68
|
-
throw new Error("Not connected");
|
|
69
|
-
const session = await this.conn.loadSession({ sessionId, cwd, mcpServers: [] });
|
|
70
|
-
this.emit("event", {
|
|
71
|
-
type: "session_created",
|
|
72
|
-
sessionId: session.sessionId,
|
|
73
|
-
cwd,
|
|
74
|
-
configOptions: session.configOptions ?? [],
|
|
75
|
-
});
|
|
76
|
-
return { sessionId: session.sessionId, configOptions: session.configOptions ?? [] };
|
|
77
|
-
}
|
|
78
|
-
async setConfigOption(sessionId, configId, value) {
|
|
79
|
-
if (!this.conn)
|
|
80
|
-
throw new Error("Not connected");
|
|
81
|
-
const result = await this.conn.setSessionConfigOption({ sessionId, configId, value });
|
|
82
|
-
return result.configOptions ?? [];
|
|
83
|
-
}
|
|
84
|
-
async prompt(sessionId, text, images) {
|
|
85
|
-
if (!this.conn)
|
|
86
|
-
throw new Error("Not connected");
|
|
87
|
-
try {
|
|
88
|
-
const promptParts = [];
|
|
89
|
-
if (images) {
|
|
90
|
-
for (const img of images) {
|
|
91
|
-
promptParts.push({ type: "image", data: img.data, mimeType: img.mimeType });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
promptParts.push({ type: "text", text });
|
|
95
|
-
const result = await this.conn.prompt({
|
|
96
|
-
sessionId,
|
|
97
|
-
prompt: promptParts,
|
|
98
|
-
});
|
|
99
|
-
this.emit("event", {
|
|
100
|
-
type: "prompt_done",
|
|
101
|
-
sessionId,
|
|
102
|
-
stopReason: result.stopReason ?? "end_turn",
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
const message = err instanceof Error ? err.message : (typeof err === "string" ? err : JSON.stringify(err));
|
|
107
|
-
if (/cancel/i.test(message)) {
|
|
108
|
-
this.emit("event", {
|
|
109
|
-
type: "prompt_done",
|
|
110
|
-
sessionId,
|
|
111
|
-
stopReason: "cancelled",
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
this.emit("event", { type: "error", sessionId, message });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async cancel(sessionId) {
|
|
119
|
-
for (const [requestId, requestSessionId] of this.permissionRequestSessions) {
|
|
120
|
-
if (requestSessionId === sessionId) {
|
|
121
|
-
this.denyPermission(requestId);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
await this.conn?.cancel({ sessionId });
|
|
125
|
-
}
|
|
126
|
-
/** Send a prompt and collect the full text response without emitting events. */
|
|
127
|
-
async promptForText(sessionId, text) {
|
|
128
|
-
if (!this.conn)
|
|
129
|
-
throw new Error("Not connected");
|
|
130
|
-
this.silentSessions.add(sessionId);
|
|
131
|
-
this.silentBuffers.set(sessionId, "");
|
|
132
|
-
try {
|
|
133
|
-
await this.conn.prompt({ sessionId, prompt: [{ type: "text", text }] });
|
|
134
|
-
return this.silentBuffers.get(sessionId) ?? "";
|
|
135
|
-
}
|
|
136
|
-
catch (err) {
|
|
137
|
-
const message = err instanceof Error ? err.message : (typeof err === "string" ? err : JSON.stringify(err));
|
|
138
|
-
if (/cancel/i.test(message)) {
|
|
139
|
-
return "";
|
|
140
|
-
}
|
|
141
|
-
throw err;
|
|
142
|
-
}
|
|
143
|
-
finally {
|
|
144
|
-
this.silentSessions.delete(sessionId);
|
|
145
|
-
this.silentBuffers.delete(sessionId);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
resolvePermission(requestId, optionId) {
|
|
149
|
-
const resolve = this.permissionResolvers.get(requestId);
|
|
150
|
-
if (resolve) {
|
|
151
|
-
resolve({ outcome: { outcome: "selected", optionId } });
|
|
152
|
-
this.permissionResolvers.delete(requestId);
|
|
153
|
-
this.permissionRequestSessions.delete(requestId);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
denyPermission(requestId) {
|
|
157
|
-
const resolve = this.permissionResolvers.get(requestId);
|
|
158
|
-
if (resolve) {
|
|
159
|
-
resolve({ outcome: { outcome: "cancelled" } });
|
|
160
|
-
this.permissionResolvers.delete(requestId);
|
|
161
|
-
this.permissionRequestSessions.delete(requestId);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
async shutdown() {
|
|
165
|
-
// Reject all pending permissions
|
|
166
|
-
for (const [id, resolve] of this.permissionResolvers) {
|
|
167
|
-
resolve({ outcome: { outcome: "cancelled" } });
|
|
168
|
-
}
|
|
169
|
-
this.permissionResolvers.clear();
|
|
170
|
-
this.permissionRequestSessions.clear();
|
|
171
|
-
if (this.proc && this.proc.exitCode === null) {
|
|
172
|
-
this.proc.kill();
|
|
173
|
-
await new Promise((resolve) => {
|
|
174
|
-
const timer = setTimeout(() => {
|
|
175
|
-
this.proc?.kill(process.platform === "win32" ? undefined : "SIGKILL");
|
|
176
|
-
resolve();
|
|
177
|
-
}, 5000);
|
|
178
|
-
this.proc?.on("exit", () => {
|
|
179
|
-
clearTimeout(timer);
|
|
180
|
-
resolve();
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
this.proc = null;
|
|
185
|
-
this.conn = null;
|
|
186
|
-
}
|
|
187
|
-
// --- ACP Client callbacks ---
|
|
188
|
-
handlePermission(params) {
|
|
189
|
-
const requestId = crypto.randomUUID();
|
|
190
|
-
const title = params.toolCall?.title ?? "Permission requested";
|
|
191
|
-
const toolCallId = params.toolCall?.toolCallId ?? null;
|
|
192
|
-
return new Promise((resolve) => {
|
|
193
|
-
// Register resolver BEFORE emitting, so synchronous auto-approve can find it
|
|
194
|
-
this.permissionResolvers.set(requestId, resolve);
|
|
195
|
-
this.permissionRequestSessions.set(requestId, params.sessionId);
|
|
196
|
-
this.emit("event", {
|
|
197
|
-
type: "permission_request",
|
|
198
|
-
requestId,
|
|
199
|
-
sessionId: params.sessionId,
|
|
200
|
-
title,
|
|
201
|
-
toolCallId,
|
|
202
|
-
options: params.options,
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
handleSessionUpdate(params) {
|
|
207
|
-
const update = params.update;
|
|
208
|
-
const sessionId = params.sessionId;
|
|
209
|
-
// Silent sessions: only buffer text, don't emit events
|
|
210
|
-
if (this.silentSessions.has(sessionId)) {
|
|
211
|
-
if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
|
|
212
|
-
const buf = (this.silentBuffers.get(sessionId) ?? "") + update.content.text;
|
|
213
|
-
this.silentBuffers.set(sessionId, buf);
|
|
214
|
-
}
|
|
215
|
-
return Promise.resolve();
|
|
216
|
-
}
|
|
217
|
-
switch (update.sessionUpdate) {
|
|
218
|
-
case "agent_message_chunk":
|
|
219
|
-
if (update.content.type === "text") {
|
|
220
|
-
this.emit("event", {
|
|
221
|
-
type: "message_chunk",
|
|
222
|
-
sessionId,
|
|
223
|
-
text: update.content.text,
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
break;
|
|
227
|
-
case "agent_thought_chunk":
|
|
228
|
-
if (update.content.type === "text") {
|
|
229
|
-
this.emit("event", {
|
|
230
|
-
type: "thought_chunk",
|
|
231
|
-
sessionId,
|
|
232
|
-
text: update.content.text,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
break;
|
|
236
|
-
case "tool_call":
|
|
237
|
-
this.emit("event", {
|
|
238
|
-
type: "tool_call",
|
|
239
|
-
sessionId,
|
|
240
|
-
id: update.toolCallId ?? "",
|
|
241
|
-
title: update.title ?? "",
|
|
242
|
-
kind: update.kind ?? "unknown",
|
|
243
|
-
rawInput: update.rawInput,
|
|
244
|
-
});
|
|
245
|
-
break;
|
|
246
|
-
case "tool_call_update":
|
|
247
|
-
this.emit("event", {
|
|
248
|
-
type: "tool_call_update",
|
|
249
|
-
sessionId,
|
|
250
|
-
id: update.toolCallId ?? "",
|
|
251
|
-
status: update.status ?? "",
|
|
252
|
-
content: update.content ?? undefined,
|
|
253
|
-
});
|
|
254
|
-
break;
|
|
255
|
-
case "plan":
|
|
256
|
-
this.emit("event", {
|
|
257
|
-
type: "plan",
|
|
258
|
-
sessionId,
|
|
259
|
-
entries: update.entries ?? [],
|
|
260
|
-
});
|
|
261
|
-
break;
|
|
262
|
-
case "config_option_update":
|
|
263
|
-
this.emit("event", {
|
|
264
|
-
type: "config_option_update",
|
|
265
|
-
sessionId,
|
|
266
|
-
configOptions: update.configOptions ?? [],
|
|
267
|
-
});
|
|
268
|
-
break;
|
|
269
|
-
}
|
|
270
|
-
return Promise.resolve();
|
|
271
|
-
}
|
|
272
|
-
async handleReadFile(params) {
|
|
273
|
-
const { readFile } = await import("node:fs/promises");
|
|
274
|
-
const content = await readFile(params.path, "utf-8");
|
|
275
|
-
return { content };
|
|
276
|
-
}
|
|
277
|
-
async handleWriteFile(params) {
|
|
278
|
-
const { writeFile, mkdir } = await import("node:fs/promises");
|
|
279
|
-
const { dirname } = await import("node:path");
|
|
280
|
-
await mkdir(dirname(params.path), { recursive: true });
|
|
281
|
-
await writeFile(params.path, params.content);
|
|
282
|
-
return {};
|
|
283
|
-
}
|
|
284
|
-
}
|
package/lib/config.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { parse as parseTOML } from "smol-toml";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
const ConfigSchema = z.object({
|
|
5
|
-
port: z.number().int().positive().default(6800),
|
|
6
|
-
data_dir: z.string().default("data"),
|
|
7
|
-
default_cwd: z.string().default(process.cwd()),
|
|
8
|
-
public_dir: z.string().default("dist"),
|
|
9
|
-
agent_cmd: z.string().default("copilot --acp"),
|
|
10
|
-
limits: z.object({
|
|
11
|
-
bash_output: z.number().int().positive().default(1_048_576), // 1 MB
|
|
12
|
-
image_upload: z.number().int().positive().default(10_485_760), // 10 MB
|
|
13
|
-
cancel_timeout: z.number().int().nonnegative().default(10_000), // 10s; 0 disables
|
|
14
|
-
}).default({
|
|
15
|
-
bash_output: 1_048_576,
|
|
16
|
-
image_upload: 10_485_760,
|
|
17
|
-
cancel_timeout: 10_000,
|
|
18
|
-
}),
|
|
19
|
-
push: z.object({
|
|
20
|
-
vapid_subject: z.string().default("mailto:webagent@localhost"),
|
|
21
|
-
}).default({
|
|
22
|
-
vapid_subject: "mailto:webagent@localhost",
|
|
23
|
-
}),
|
|
24
|
-
});
|
|
25
|
-
let _config = null;
|
|
26
|
-
function parseArgs() {
|
|
27
|
-
const idx = process.argv.indexOf("--config");
|
|
28
|
-
if (idx !== -1 && idx + 1 < process.argv.length) {
|
|
29
|
-
return process.argv[idx + 1];
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
export function loadConfig() {
|
|
34
|
-
const configPath = parseArgs();
|
|
35
|
-
let raw = {};
|
|
36
|
-
if (configPath) {
|
|
37
|
-
try {
|
|
38
|
-
const content = readFileSync(configPath, "utf-8");
|
|
39
|
-
raw = parseTOML(content);
|
|
40
|
-
console.log(`[config] loaded: ${configPath}`);
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
console.error(`[config] failed to read ${configPath}:`, err);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
console.log("[config] no --config provided, using defaults");
|
|
49
|
-
}
|
|
50
|
-
const result = ConfigSchema.safeParse(raw);
|
|
51
|
-
if (!result.success) {
|
|
52
|
-
console.error("[config] validation error:", result.error.format());
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
_config = result.data;
|
|
56
|
-
return _config;
|
|
57
|
-
}
|
|
58
|
-
export function getConfig() {
|
|
59
|
-
if (!_config)
|
|
60
|
-
throw new Error("Config not loaded. Call loadConfig() first.");
|
|
61
|
-
return _config;
|
|
62
|
-
}
|