@lelouchhe/webagent 0.2.5 → 0.3.0
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/config.toml +6 -0
- package/dist/index.html +5 -4
- package/dist/js/app.2562YGRO.js +10 -0
- package/dist/{styles.01a6wdjv.css → styles.008ve1hx.css} +69 -7
- package/lib/bridge.js +72 -3
- package/lib/config.js +4 -0
- package/lib/daemon.js +11 -0
- package/lib/event-handler.js +4 -4
- package/lib/routes.js +86 -54
- package/lib/server.js +12 -1
- package/lib/store.js +36 -0
- package/lib/title-service.js +4 -0
- package/package.json +1 -1
- package/dist/js/app.6BGATUZ7.js +0 -10
package/config.toml
CHANGED
|
@@ -27,5 +27,11 @@ image_upload = 10_485_760
|
|
|
27
27
|
# does not respond within this time the UI resets to idle. Set to 0 to disable.
|
|
28
28
|
cancel_timeout = 10_000
|
|
29
29
|
|
|
30
|
+
# Max recent paths shown in /new menu (default 10, 0 = show all)
|
|
31
|
+
recent_paths = 10
|
|
32
|
+
|
|
33
|
+
# Days to keep unused paths before auto-cleanup (default 30, 0 = keep forever)
|
|
34
|
+
recent_paths_ttl = 30
|
|
35
|
+
|
|
30
36
|
[push]
|
|
31
37
|
vapid_subject = "mailto:noreply@example.com"
|
package/dist/index.html
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
<link rel="icon" href="/icons/icon.svg" type="image/svg+xml">
|
|
8
8
|
<link rel="apple-touch-icon" href="/icons/icon-180.png">
|
|
9
9
|
<link rel="manifest" href="/manifest.json">
|
|
10
|
-
<meta name="theme-color" content="#0d1117">
|
|
10
|
+
<meta name="theme-color" content="#0d1117" media="(prefers-color-scheme: dark)">
|
|
11
|
+
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
|
11
12
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
12
|
-
<meta name="apple-mobile-web-app-status-bar-style" content="
|
|
13
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
|
13
14
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
14
15
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.3.2/dist/purify.min.js"></script>
|
|
15
16
|
<script>document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || 'auto');</script>
|
|
16
|
-
<link rel="stylesheet" href="/styles.
|
|
17
|
+
<link rel="stylesheet" href="/styles.008ve1hx.css">
|
|
17
18
|
</head>
|
|
18
19
|
<body>
|
|
19
20
|
|
|
@@ -41,6 +42,6 @@
|
|
|
41
42
|
</div>
|
|
42
43
|
<div id="status-bar"></div>
|
|
43
44
|
|
|
44
|
-
<script src="/js/app.
|
|
45
|
+
<script src="/js/app.2562YGRO.js"></script>
|
|
45
46
|
</body>
|
|
46
47
|
</html>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var he=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 a=`HTTP ${s.status}`;try{let r=await s.json();r.error&&(a=String(r.error))}catch{}throw new he(s.status,a)}let i=await s.text();if(i)return JSON.parse(i)}function _(e,t){return U(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}function je(e){let t={};return e?.cwd&&(t.cwd=e.cwd),e?.inheritFromSessionId&&(t.inheritFromSessionId=e.inheritFromSessionId),_("/api/v1/sessions",t)}function ye(e){return U("/api/v1/sessions/"+e,{method:"DELETE"})}function ie(){return U("/api/v1/sessions")}function M(e){return U("/api/v1/sessions/"+e)}function we(e,t,s){let i={text:t};return s?.length&&(i.images=s),_("/api/v1/sessions/"+e+"/prompt",i)}function Fe(e){return _("/api/v1/sessions/"+e+"/cancel",{})}function oe(e,t,s){return _("/api/v1/sessions/"+e+"/permissions/"+t,{optionId:s})}function re(e,t){return _("/api/v1/sessions/"+e+"/permissions/"+t,{denied:!0})}function G(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 Ue(e,t){return U("/api/v1/sessions/"+e+"/title",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})}function We(e,t){return _("/api/v1/sessions/"+e+"/bash",{command:t})}function J(e,t,s){let i={visible:t};return s&&(i.sessionId=s),_("/api/beta/clients/"+e+"/visibility",i)}function Ke(){return _("/api/v1/bridge/reload",{})}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"),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,serverVersion:null,agentName:null,agentVersion:null,_cancelTimerId:null,_onCancelTimeout:null,lastEventSeq:0,oldestLoadedSeq:0,hasMoreHistory:!1,loadingOlderEvents:!1,replayInProgress:!1,replayTarget:null,replayQueue:[],unconfirmedPermissions:new Map,agentReloading:!1,recentPathsLimit:10},Tt={disconnected:"is-disconnected",connecting:"is-connecting",connected:"is-connected"};function O(e,t=e){o.status.textContent="",o.status.className=`status-dot ${Tt[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 ae(e){return x(e)?.currentValue??null}function Ve(e,t){let s=x(e);s&&(s.currentValue=t)}function ve(e){n.configOptions=e,N(),A()}function N(){o.inputArea.classList.remove("plan-mode","autopilot-mode");let e=ae("mode")||"";e.includes("#plan")?o.inputArea.classList.add("plan-mode"):e.includes("#autopilot")&&o.inputArea.classList.add("autopilot-mode")}function A(){if(!o.statusBar)return;let e=ae("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 h(e){n.busy=e,e?(o.sendBtn.textContent="^C",o.sendBtn.title="Cancel (Ctrl+C)",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 L({cwd:e,inheritFromSessionId:t=n.sessionId}={}){n.awaitingNewSession=!0,je({cwd:e,inheritFromSessionId:t}).catch(()=>{})}var Ge=[];function Je(e){Ge.push(e)}function w(){for(let e of Ge)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="Message or ?",h(!1),n.sessionTitle=null,n.sessionCwd=null,n.configOptions=[],ce(null,null),o.statusBar&&(o.statusBar.textContent="")}function Q(){return!n.busy||!n.sessionId?!1:(Fe(n.sessionId).catch(()=>{}),Y(),n.cancelTimeout>0&&(n._cancelTimerId=setTimeout(()=>{n._cancelTimerId=null,n.busy&&(n.turnEnded=!0,h(!1),n._onCancelTimeout?.())},n.cancelTimeout)),!0)}function Y(){n._cancelTimerId!=null&&(clearTimeout(n._cancelTimerId),n._cancelTimerId=null)}function Qe(){return location.hash.slice(1)||null}function le(e){history.replaceState(null,"",`#${e}`)}function ce(e,t){o.sessionInfo.textContent=t||(e?e.slice(0,8)+"\u2026":""),document.title=t||">_"}O("disconnected");var q="idle",Se=[],Et="11.11.1",Ye=`https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@${Et}/build`,kt={dark:"github-dark.min.css",light:"github.min.css",auto:""},$=null;function Ct(){let e=document.documentElement.getAttribute("data-theme")||"auto";return e==="light"?"light":e==="dark"?"dark":window.matchMedia("(prefers-color-scheme: light)").matches?"light":"dark"}function be(){let e=Ct(),t=kt[e],s=`${Ye}/styles/${t}`;if($){if($.href===s)return;$.href=s;return}$=document.createElement("link"),$.rel="stylesheet",$.href=s,document.head.appendChild($)}function xt(){return new Promise((e,t)=>{let s=document.createElement("script");s.src=`${Ye}/highlight.min.js`,s.onload=()=>e(),s.onerror=()=>t(new Error("Failed to load highlight.js")),document.head.appendChild(s)})}async function Lt(){if(q==="ready")return!0;if(q==="loading")return new Promise(e=>{Se.push(()=>e(q==="ready"))});q="loading";try{return be(),await xt(),q="ready",Se.splice(0).forEach(t=>t()),!0}catch{return q="idle",Se.splice(0).forEach(t=>t()),!1}}function Mt(e){let t=globalThis.hljs;if(t)for(let s of e.querySelectorAll("pre code"))s.dataset.highlighted||t.highlightElement(s)}function $t(e){for(let t of e.classList)if(t.startsWith("language-"))return t.slice(9);return""}function Pt(e){let t=e.querySelectorAll("pre");for(let s of t){let i=s.querySelector("code");if(!i||s.parentElement?.classList.contains("code-block-wrapper"))continue;let a=$t(i),r=document.createElement("div");r.className="code-block-wrapper";let l=document.createElement("div");l.className="code-toolbar";let d=document.createElement("button");d.className="copy-btn",d.textContent="cp",d.type="button",l.appendChild(d),s.replaceWith(r),r.appendChild(l),r.appendChild(s)}}var Ie=new WeakMap;function ze(e){let t=e.target.closest(".copy-btn");if(!t)return;let i=t.closest(".code-block-wrapper")?.querySelector("code");if(!i)return;let a=i.textContent||"";navigator.clipboard.writeText(a).then(()=>{let r=Ie.get(t);r&&clearTimeout(r),t.textContent="\u2713",t.classList.add("copied");let l=setTimeout(()=>{t.textContent="cp",t.classList.remove("copied"),Ie.delete(t)},1500);Ie.set(t,l)}).catch(()=>{let r=document.createRange();r.selectNodeContents(i);let l=window.getSelection();l?.removeAllRanges(),l?.addRange(r)})}async function z(e){if(!e.querySelector("pre code"))return;Pt(e),await Lt()&&Mt(e)}function Ze(){q==="ready"&&$&&be()}typeof window<"u"&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{q==="ready"&&$&&be()});marked.setOptions({breaks:!0,gfm:!0});function de(e){return DOMPurify.sanitize(marked.parse(e))}function D(e,t){let s=document.createElement("div");return s.className=`msg ${e}`,s.innerHTML=e==="user"?p(t).replace(/\n/g,"<br>"):de(t),E(s),s}function c(e){let t=document.createElement("div");return t.className="system-msg",t.textContent=e,E(t),t}function P(){let e=n.currentAssistantEl;n.currentAssistantEl=null,n.currentAssistantText="",e&&typeof e.querySelector=="function"&&z(e)}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 R=null,Ht=80;function Ee(e){return e.scrollHeight-e.scrollTop-e.clientHeight<Ht}function _t(){n.followMessages=Ee(o.messages)}o.messages.addEventListener("scroll",_t);function Nt(){return n.followMessages||Ee(o.messages)}function E(e,t=!1){if(n.replayTarget)return n.replayTarget.appendChild(e),e;let s=t||Nt();return o.messages.appendChild(e),I(s),e}function et(){j(),R=document.createElement("div"),R.id="waiting",R.innerHTML='<span class="cursor">\u258C</span>',E(R,!0)}function j(){R&&(R.remove(),R=null)}var Te=!1;function I(e){let t=o.messages;if(e||n.followMessages){typeof requestAnimationFrame=="function"?Te||(Te=!0,requestAnimationFrame(()=>{Te=!1,t.scrollTop=t.scrollHeight})):t.scrollTop=t.scrollHeight,n.followMessages=!0;return}n.followMessages=Ee(t)}function p(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function tt(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 ke(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">${p(i)}</span>`):i.startsWith("@@")?s.push(`<span class="diff-hunk">${p(i)}</span>`):i.startsWith("-")?s.push(`<span class="diff-del">${p(i)}</span>`):i.startsWith("+")?s.push(`<span class="diff-add">${p(i)}</span>`):s.push(p(i)));return s.join(`
|
|
3
|
+
`)}if(e&&typeof e=="object"){let t=[];if(e.path&&t.push(`<span class="diff-file">*** ${p(e.path)}</span>`),e.old_str!=null)for(let s of String(e.old_str).split(`
|
|
4
|
+
`))t.push(`<span class="diff-del">- ${p(s)}</span>`);if(e.new_str!=null)for(let s of String(e.new_str).split(`
|
|
5
|
+
`))t.push(`<span class="diff-add">+ ${p(s)}</span>`);if(e.file_text!=null)for(let s of String(e.file_text).split(`
|
|
6
|
+
`))t.push(`<span class="diff-add">+ ${p(s)}</span>`);return t.length>(e.path?1:0)?t.join(`
|
|
7
|
+
`):null}return null}function Z(e,t=!1){let s=document.createElement("div");return s.className="bash-block",s.innerHTML=`<span class="bash-cmd${t?" running":""}">${p(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 X(e,t,s){if(!e)return;let i=e.querySelector(".bash-cmd");i.classList.remove("running");let a="";if(s?a=`[signal: ${s}]`:t!==0&&t!=null&&(a=`[exit: ${t}]`),a){let r=document.createElement("span");r.className=`bash-exit ${t===0?"ok":"fail"}`,r.textContent=a,i.after(r)}e===n.currentBashEl&&(n.currentBashEl=null)}var qt={auto:"\u25D1",light:"\u2600",dark:"\u263E"},Xe=["auto","light","dark"];function nt(){return localStorage.getItem("theme")||"auto"}function st(e){document.documentElement.setAttribute("data-theme",e),o.themeBtn.textContent=qt[e],o.themeBtn.title=`Theme: ${e}`,localStorage.setItem("theme",e);for(let t of it)t()}var it=[];function ot(e){it.push(e)}o.themeBtn.onclick=()=>{let e=nt();st(Xe[(Xe.indexOf(e)+1)%3])};st(nt());var Ce={task_complete:"\u2714",read:"cat",edit:"edit",execute:"exec",search:"find",delete:"rm"},xe="run",Le={pending:"\u25CB",in_progress:"\u25C9",completed:"\u25CF"};function rt(e){return n.replayTarget?.querySelector(`[id="${e}"]`)??document.getElementById(e)}function Bt(e){return n.replayTarget?.querySelector(e)??document.querySelector(e)}var at="webagent_notify_tip_shown",lt="webagent_notify_tip_denied_shown";function Ot(){if(typeof Notification>"u"||n.replayInProgress)return;let e=Notification.permission;if(e!=="granted"){if(e==="denied"){if(localStorage.getItem(lt))return;localStorage.setItem(lt,"1"),c("tip: notifications are blocked \u2014 allow in browser site settings to enable");return}localStorage.getItem(at)||(localStorage.setItem(at,"1"),c("tip: use /notify to enable background notifications"))}}function ue(){n.pendingPromptDone&&(n.pendingToolCallIds.size>0||n.pendingPermissionRequestIds.size>0||(j(),k(),P(),h(!1),n.pendingPromptDone=!1,Ot()))}function At(){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">${p(i)} \u2014 cancelled</span>`}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function Rt(){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 $e(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 ct=200;async function B(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${ct}`);if(!t.ok)return!1;let s=await t.json(),{events:i,streaming:a,hasMore:r}=$e(s),l=document.createDocumentFragment(),d=He(i);n.replayTarget=l;for(let u=0;u<i.length;u++){let f=JSON.parse(i[u].data);_e(i[u].type,f,i,u,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=r===!0,n.hasMoreHistory&&Dt(),dt(),Me(i,a),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,pt()}}function Me(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),a=o.messages.querySelectorAll(".thinking"),r=a[a.length-1];if(r){n.currentThinkingEl=r,n.currentThinkingText=i.text;let l=r.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),a=o.messages.querySelectorAll(".msg.assistant"),r=a[a.length-1];r&&(n.currentAssistantEl=r,n.currentAssistantText=i.text);break}}}function dt(){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 Pe(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:a,streaming:r}=$e(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,a.length===0)return Me(a,r),!0;let d=document.createDocumentFragment(),u=He(a);n.replayTarget=d;for(let f=0;f<a.length;f++){let g=JSON.parse(a[f].data);_e(a[f].type,g,a,f,u)}return n.replayTarget=null,o.messages.appendChild(d),n.lastEventSeq=a[a.length-1].seq,dt(),Me(a,r),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,pt()}}var ee=null;function Dt(){pe();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"&&(ee=new IntersectionObserver(t=>{t[0]?.isIntersecting&&!n.loadingOlderEvents&&n.hasMoreHistory&&n.sessionId&&jt(n.sessionId)},{root:o.messages,rootMargin:"200px 0px 0px 0px"}),ee.observe(e))}function pe(){ee&&(ee.disconnect(),ee=null),document.getElementById("history-sentinel")?.remove()}Je(pe);async function jt(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=${ct}&before=${n.oldestLoadedSeq}`);if(!t.ok||e!==n.sessionId)return!1;let s=await t.json(),{events:i,hasMore:a}=$e(s);if(i.length===0)return n.hasMoreHistory=!1,pe(),!0;let r=document.createDocumentFragment(),l=He(i);n.replayTarget=r;for(let g=0;g<i.length;g++){let S=JSON.parse(i[g].data);_e(i[g].type,S,i,g,l)}n.replayTarget=null;let d=o.messages,u=d.scrollHeight,f=document.getElementById("history-sentinel");return f?f.after(r):d.prepend(r),d.scrollTop+=d.scrollHeight-u,n.oldestLoadedSeq=i[0].seq,n.hasMoreHistory=a===!0,n.hasMoreHistory||pe(),!0}catch{return!1}finally{n.loadingOlderEvents=!1}}function ut(){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?re(t.sessionId,e).catch(()=>{}):oe(t.sessionId,e,t.optionId).catch(()=>{});let i=s.dataset.title?`\u26BF ${p(s.dataset.title)}`:"\u26BF";s.innerHTML=`<span style="opacity:0.5">${i} \u2014 ${p(t.optionName)}</span>`,n.unconfirmedPermissions.delete(e)}}function He(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 _e(e,t,s,i,a){switch(e){case"user_message":{let r=D("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,r.appendChild(d)}break}case"assistant_message":{let l=(n.replayTarget||o.messages).lastElementChild;if(l&&l.classList.contains("msg")&&l.classList.contains("assistant")){let f=(l.getAttribute("data-raw")||"")+t.text;l.setAttribute("data-raw",f),l.innerHTML=de(f),z(l);break}let d=D("assistant",t.text);d.setAttribute("data-raw",t.text),z(d);break}case"thinking":{let l=(n.replayTarget||o.messages).lastElementChild;if(l&&l.classList.contains("thinking")){let u=l.querySelector(".thinking-content");if(u){u.textContent+=`
|
|
8
|
+
`+t.text;break}}let d=document.createElement("details");d.className="thinking",d.innerHTML=`<summary>\u283F thought</summary><div class="thinking-content">${p(t.text)}</div>`,E(d);break}case"tool_call":{let r=Ce[t.kind]||xe,l=document.createElement("div");l.className="tool-call",l.id=`tc-${t.id}`,l.dataset.kind=t.kind;let d=`<span class="icon">${r}</span> ${p(t.title)}`,u=t.rawInput;u&&u.command?d+=`<span class="tc-detail">$ ${p(u.command)}</span>`:u&&u.path&&(d+=`<span class="tc-detail">${p(u.path)}</span>`),l.innerHTML=d;let f=t.kind==="edit"?ke(u):null;if(f){let S=document.createElement("details");S.innerHTML=`<summary>diff</summary><div class="diff-view">${f}</div>`,l.appendChild(S)}let g=l.querySelector(".tc-detail");g&&l.addEventListener("click",S=>{S.target.closest("details")||g.classList.toggle("expanded")}),E(l),a&&a.toolCalls.set(t.id,l);break}case"tool_call_update":{let r=a?a.toolCalls.get(t.id):rt(`tc-${t.id}`);if(r){let l=t.status==="completed"?"\u2713":t.status==="failed"?"\u2717":"\u2026";r.className=`tool-call ${t.status}`;let d=r.querySelector(".icon");if(d&&(d.textContent=l),t.content&&t.content.length&&!r.querySelector("details")&&!r.querySelector(".tc-summary")){let u=t.content.map(f=>f.type==="terminal"?`[terminal ${f.terminalId}]`:f.content?.text?f.content.text:Array.isArray(f.content)?f.content.map(g=>g.text||"").join(""):"").filter(Boolean).join(`
|
|
9
|
+
`);if(u)if(r.dataset.kind==="task_complete"){let f=document.createElement("div");f.className="tc-summary",f.textContent=u,r.appendChild(f)}else{let f=document.createElement("details");f.innerHTML=`<summary>output</summary><div class="tc-content">${p(u)}</div>`,r.appendChild(f)}}}(t.status==="completed"||t.status==="failed")&&n.pendingToolCallIds.delete(t.id);break}case"plan":{let r=document.createElement("div");r.className="plan",r.innerHTML='<div class="plan-title">\u2015 plan</div>'+(t.entries||[]).map(l=>`<div class="plan-entry">${Le[l.status]||"?"} ${p(l.content)}</div>`).join(""),E(r);break}case"permission_request":{let r=document.createElement("div");r.className="permission",r.dataset.requestId=t.requestId,r.dataset.title=t.title||"",r.innerHTML=`<span class="title" style="opacity:0.5">\u26BF ${p(t.title)}</span> `,!(a?a.resolvedPermissions.has(t.requestId):s&&s.slice(i+1).some(d=>d.type==="permission_response"&&JSON.parse(d.data).requestId===t.requestId))&&t.options&&(r.querySelector(".title").style.opacity="1",t.options.forEach(d=>{let u=document.createElement("button"),f=(d.kind||"").includes("allow");u.className=f?"allow":"deny",u.textContent=d.name,u.onclick=()=>{(d.kind||"").includes("reject")||(d.kind||"").includes("deny")?re(n.sessionId,t.requestId).catch(()=>{}):oe(n.sessionId,t.requestId,d.optionId).catch(()=>{}),r.innerHTML=`<span style="opacity:0.5">\u26BF ${p(t.title)} \u2014 ${p(d.name)}</span>`},r.appendChild(u)})),E(r),a&&a.permissions.set(t.requestId,r);break}case"permission_response":{let r=a?a.permissions.get(t.requestId):Bt(`.permission[data-request-id="${t.requestId}"]`);if(r){let l=r.dataset.title?`\u26BF ${r.dataset.title}`:"\u26BF",d=t.optionName||(t.denied?"denied":"allowed");r.innerHTML=`<span style="opacity:0.5">${p(l)} \u2014 ${p(d)}</span>`}break}case"bash_command":{let r=Z(t.command,!1);a?a.currentBashEl=r:r.id="bash-replay-pending";break}case"bash_result":{let r=a?a.currentBashEl:rt("bash-replay-pending");if(r){if(a||r.removeAttribute("id"),t.output){let l=r.querySelector(".bash-output");l&&(l.textContent=t.output,l.classList.add("has-content"))}X(r,t.code,t.signal),a&&(a.currentBashEl=null)}break}case"prompt_done":n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,h(!1);break}}function pt(){let e=n.replayQueue;n.replayQueue=[];for(let t of e)Ft(t)||C(t)}function Ft(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":if(e.cancelTimeout!=null&&(n.cancelTimeout=e.cancelTimeout),e.recentPathsLimit!=null&&(n.recentPathsLimit=e.recentPathsLimit),n.agentReloading){n.agentReloading=!1;let t=e.agent?.name??"",s=e.agent?.version??"",i=t&&s?`${t} ${s}`:"Agent";c(`${i} reloaded.`),h(!1)}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&&ve(e.configOptions),le(n.sessionId),n.clientId&&J(n.clientId,!document.hidden,n.sessionId).catch(()=>{}),ce(n.sessionId,n.sessionTitle),O("connected","connected"),o.input.disabled=!1,o.sendBtn.disabled=!1,o.input.placeholder="Message or ?",h(!!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"}`),A();break;case"user_message":{if(n.sentMessageForSession===e.sessionId){n.sentMessageForSession=null;break}if(k(),P(),n.newTurnStarted=!0,n.turnEnded=!1,e.sessionId===n.sessionId){let t=D("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;j(),k(),n.currentAssistantEl||(n.currentAssistantEl=D("assistant",""),n.currentAssistantText=""),n.currentAssistantText+=e.text,n.currentAssistantEl.innerHTML=de(n.currentAssistantText),I();break;case"thought_chunk":if(n.turnEnded)break;j(),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,I();break;case"tool_call":{if(n.turnEnded)break;n.pendingToolCallIds.add(e.id),h(!0),j(),k(),P();let t=Ce[e.kind]||xe,s=document.createElement("div");s.className="tool-call",s.id=`tc-${e.id}`,s.dataset.kind=e.kind;let i=`<span class="icon">${t}</span> ${p(e.title)}`,a=e.rawInput;a&&a.command?i+=`<span class="tc-detail">$ ${p(a.command)}</span>`:a&&a.path&&(i+=`<span class="tc-detail">${p(a.path)}</span>`),s.innerHTML=i;let r=e.kind==="edit"?ke(a):null;if(r){let d=document.createElement("details");d.innerHTML=`<summary>diff</summary><div class="diff-view">${r}</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")&&!t.querySelector(".tc-summary")){let a=e.content.map(r=>r.type==="terminal"?`[terminal ${r.terminalId}]`:r.content?.text?r.content.text:Array.isArray(r.content)?r.content.map(l=>l.text||"").join(""):"").filter(Boolean).join(`
|
|
10
|
+
`);if(a)if(t.dataset.kind==="task_complete"){let r=document.createElement("div");r.className="tc-summary",r.textContent=a,t.appendChild(r)}else{let r=document.createElement("details");r.innerHTML=`<summary>output</summary><div class="tc-content">${p(a)}</div>`,t.appendChild(r)}}}ue(),I();break}case"plan":{k(),P();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">${Le[s.status]||"?"} ${p(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),h(!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 ${p(e.title)}</span> `,e.options.forEach(s=>{let i=document.createElement("button"),a=(s.kind||"").includes("allow");i.className=a?"allow":"deny",i.textContent=s.name,i.onclick=()=>{let r=(s.kind||"").includes("reject")||(s.kind||"").includes("deny");r?re(n.sessionId,e.requestId).catch(()=>{}):oe(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:r}),t.innerHTML=`<span style="opacity:0.5">\u26BF ${p(e.title)} \u2014 ${p(s.name)}</span>`,ue()},t.appendChild(i)}),E(t);break}case"permission_response":{console.log("[PERM-DEBUG] permission_response received:",e.requestId,"sessionId:",e.sessionId,"state.sessionId:",n.sessionId),n.pendingPermissionRequestIds.delete(e.requestId),n.unconfirmedPermissions.delete(e.requestId);let t=document.querySelector(`.permission[data-request-id="${e.requestId}"]`);if(console.log("[PERM-DEBUG] permTarget found:",!!t,"sessionMatch:",e.sessionId===n.sessionId),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">${p(s)} \u2014 ${p(i)}</span>`,console.log("[PERM-DEBUG] DOM updated successfully")}else console.log("[PERM-DEBUG] DOM NOT updated \u2014 check failed");ue();break}case"bash_command":{if(n.sentBashForSession===e.sessionId){n.sentBashForSession=null;break}e.sessionId===n.sessionId&&(Z(e.command,!0),h(!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,I()}break}case"bash_done":{if(e.sessionId!==n.sessionId)break;X(n.currentBashEl,e.code,e.signal),e.error&&c(`err: ${e.error}`),h(!1);break}case"prompt_done":{if(Y(),e.stopReason==="cancelled"&&n.newTurnStarted){n.newTurnStarted=!1,k(),P();break}n.newTurnStarted=!1,e.stopReason==="cancelled"?At():Rt(),n.turnEnded=!0,n.pendingPromptDone=!0,ue();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":{let t=n.sessionCwd;w(),c("warn: Previous session expired, created new one."),L({cwd:t||void 0});break}case"config_set":{Ve(e.configId,e.value);let t=x(e.configId),s=t?.name||e.configId,i=t?.options.find(a=>a.value===e.value)?.name||e.value;c(`ok: ${s}: ${i}`),e.configId==="mode"&&N(),A();break}case"config_option_update":e.configOptions?.length&&ve(e.configOptions);break;case"session_title_updated":e.sessionId===n.sessionId&&(n.sessionTitle=e.title,ce(n.sessionId,n.sessionTitle));break;case"agent_reloading":n.agentReloading=!0,c("Agent reloading\u2026"),h(!0);break;case"agent_reloading_failed":c(`err: Agent reload failed: ${e.error}`),h(!1);break;case"error":n.awaitingNewSession=!1,n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,j(),k(),P(),c(`err: ${e.message}`),h(!1);break}}async function Ut(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await fetch("/api/beta/push/vapid-key");if(!t.ok)return;let{publicKey:s}=await t.json(),a=(await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:Kt(s)})).toJSON();await fetch("/api/beta/push/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:a.endpoint,keys:a.keys,clientId:n.clientId})})}catch(e){console.error("[push] subscribe failed:",e)}}async function Wt(){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/beta/push/unsubscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:s})})}catch(e){console.error("[push] unsubscribe failed:",e)}}function Kt(e){let t="=".repeat((4-e.length%4)%4),s=(e+t).replace(/-/g,"+").replace(/_/g,"/"),i=atob(s),a=new Uint8Array(i.length);for(let r=0;r<i.length;r++)a[r]=i.charCodeAt(r);return a}async function Ne(){try{let e=await navigator.serviceWorker?.ready;return e?await e.pushManager.getSubscription()!==null:!1}catch{return!1}}async function Be(e){let t=e.split(/\s+/),s=t[0].toLowerCase(),i=t.slice(1).join(" ").trim();switch(s){case"/new":{let a=i||n.sessionCwd;return w(),c("Creating new session\u2026"),L({cwd:a||void 0}),!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 Ue(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 a=n.sessionId;try{let l=(await ie()).find(d=>d.id!==a);if(n.busy&&Q(),ye(a).catch(()=>{}),K=null,l){n.sessionSwitchGen++;let d=n.sessionSwitchGen;w(),n.sessionId=null,le(l.id);let[u]=await Promise.all([M(l.id),B(l.id)]);if(d!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:u.id,cwd:u.cwd,title:u.title,configOptions:u.configOptions,busyKind:u.busyKind}),I(!0)}else{let d=n.sessionCwd;w(),n.sessionId=null,c("Creating new session\u2026"),L({cwd:d||void 0,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;await Promise.all(l.map(d=>ye(d.id).catch(()=>{}))),K=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 r=await(await fetch("/api/v1/sessions")).json(),l=i.toLowerCase(),d=r.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 u=n.sessionSwitchGen;w(),n.sessionId=null;let[f]=await Promise.all([M(d.id),B(d.id)]);if(u!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:f.id,cwd:f.cwd,title:f.title,configOptions:f.configOptions,busyKind:f.busyKind}),I(!0)}catch{w(),n.sessionId=null,c("err: Failed to switch session")}return!0}case"/cancel":return n.busy?(Q(),c("^C")):c("Nothing to cancel."),!0;case"/reload":return c("Reloading agent\u2026"),Ke().then(()=>{}).catch(a=>{c(`err: ${a instanceof Error?a.message:"Failed to reload agent"}`)}),!0;case"/help":case"?":{let a=[];n.serverVersion&&a.push(`WebAgent ${n.serverVersion}`),n.agentName&&n.agentVersion&&a.push(`${n.agentName} ${n.agentVersion}`),a.length&&c(a.join(" \xB7 ")),c("? \u2014 Show help"),c("!<command> \u2014 Run bash command");for(let r of ft){let l=r.args?`${r.cmd} ${r.args}`:r.cmd;c(`${l} \u2014 ${r.desc}`)}c("--- Shortcuts ---");for(let r of Vt)c(`${r.key} \u2014 ${r.desc}`);c("--- Tips ---");for(let r of Gt)c(r.text);return!0}case"/model":case"/mode":case"/think":{let r={"/model":"model","/mode":"mode","/think":"reasoning_effort"}[s],l=x(r);if(!i){let S=l?.options.find(se=>se.value===l.currentValue)?.name||l?.currentValue||"unknown";return c(`${l?.name||r}: ${S}`),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(),u=S=>S.toLowerCase().replace(/[\s_]+/g,"-"),f=u(d),g=l.options.find(S=>u(S.value)===f||u(S.name)===f);if(!g){let S=l.options.filter(se=>u(se.value).includes(f)||u(se.name).includes(f));if(S.length===1)g=S[0];else if(S.length>1)return c(`err: Ambiguous "${i}". Type ${s} + space to see options.`),!0}return g?(l.currentValue=g.value,N(),A(),c(`${l.name} \u2192 ${g.name}`),await G(n.sessionId,r,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 a=i.toLowerCase();if(a==="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 Ne();return await Ut(),c(l?"notify: already enabled":"notify: enabled"),!0}if(a==="off")return await Wt(),c("notify: disabled"),!0;let r=Notification.permission;return r==="denied"?c("notify: blocked \u2014 allow in browser site settings to enable"):r==="granted"&&await Ne()?c("notify: enabled"):c("notify: off \u2014 use /notify on to enable"),!0}default:return!1}}var ft=[{cmd:"/cancel",args:"",desc:"Cancel current response"},{cmd:"/exit",args:"",desc:"Close current session"},{cmd:"/help",args:"",desc:"Show help (or type ?)"},{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:"/reload",args:"",desc:"Reload agent CLI"},{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"}],Vt=[{key:"Enter",desc:"Send message"},{key:"Shift+Enter",desc:"New line"},{key:"^C",desc:"Cancel current response"},{key:"^M",desc:"Cycle mode (Agent \u2192 Plan \u2192 Autopilot)"},{key:"^U",desc:"Upload image"}],Gt=[{text:"Tap \u276F prompt to cycle mode"}],b=-1,m=[],v="commands",te=null,K=null,V=null,qe=!1;function Oe(){let e=o.input.value;if(V!==null){if(e===V)return;V=null}let t=e.match(/^\/new /);if(t){let l=e.slice(t[0].length).toLowerCase();Qt(l);return}let s=e.match(/^\/switch /);if(s){let l=e.slice(s[0].length).toLowerCase();Jt(l,"switch");return}let i=e.match(/^\/(model|mode|think) /);if(i){let d={model:"model",mode:"mode",think:"reasoning_effort"}[i[1]],u=e.slice(i[0].length).toLowerCase();Yt(d,u);return}let a=e.match(/^\/notify /);if(a){let l=e.slice(a[0].length).toLowerCase();Zt(l);return}if(!e.startsWith("/")||e.includes(" ")){y();return}v="commands";let r=e.toLowerCase();if(m=ft.filter(l=>l.cmd.startsWith(r)),m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}async function Jt(e,t="switch"){if(!K)try{K=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{K=null},5e3)}catch{return}if(v=t,m=K.filter(i=>e?i.title&&i.title.toLowerCase().includes(e)||i.id.startsWith(e):!0),m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}var fe=null;async function Qt(e){if(!fe)try{let s=n.recentPathsLimit||0,i=s>0?`/api/v1/recent-paths?limit=${s}`:"/api/v1/recent-paths";fe=(await(await fetch(i)).json()).map(r=>({cwd:r.cwd,time:r.last_used_at})),setTimeout(()=>{fe=null},5e3)}catch{return}v="new";let t=fe;if(e&&(t=t.filter(s=>s.cwd.toLowerCase().includes(e))),m=t,m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}function Yt(e,t){let s=x(e);if(!s){y();return}if(v="config",te=e,m=s.options.filter(i=>t?i.value.toLowerCase().includes(t)||i.name.toLowerCase().includes(t):!0),m.length===0){y();return}b=0,F(),o.slashMenu.classList.add("active")}var zt=[{value:"on",name:"on",desc:"Enable background notifications"},{value:"off",name:"off",desc:"Disable background notifications"}];async function Zt(e){if(v="notify",m=zt.filter(i=>e?i.value.includes(e)||i.name.includes(e):!0),m.length===0){y();return}qe=await Ne();let t=qe?"on":"off",s=m.findIndex(i=>i.value===t);b=s>=0?s:0,F(),o.slashMenu.classList.add("active")}function F(){if(v==="new"){let t=(n.sessionCwd||"").toLowerCase();o.slashMenu.innerHTML=m.map((s,i)=>{let a=s.cwd.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${a?' style="color:var(--green)"':""}>${p((a?"* ":" ")+s.cwd)}</span></div>`}).join("")}else if(v==="config"){let t=ae(te)?.toLowerCase()||"";o.slashMenu.innerHTML=m.map((s,i)=>{let a=s.value.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${a?' style="color:var(--green)"':""}>${p((a?"* ":" ")+s.name)}</span></div>`}).join("")}else if(v==="notify"){let t=qe?"on":"off";o.slashMenu.innerHTML=m.map((s,i)=>{let a=s.value===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${a?' style="color:var(--green)"':""}>${p((a?"* ":" ")+s.name)}</span><span class="slash-desc">${p(s.desc)}</span></div>`}).join("")}else v==="switch"?o.slashMenu.innerHTML=m.map((t,s)=>{let i=t.id===n.sessionId,a=i?"* ":" ",r=t.title||t.id.slice(0,8)+"\u2026",l=tt(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)"':""}>${p(a+r)}</span><span class="slash-desc">${p(t.cwd)} (${p(l)})</span></div>`}).join(""):o.slashMenu.innerHTML=m.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">${p(i)}</span><span class="slash-desc">${p(t.desc)}</span></div>`}).join("");let e=o.slashMenu.querySelector(".selected");e&&e.scrollIntoView({block:"nearest"})}function y(){o.slashMenu.classList.remove("active"),b=-1,m=[],v="commands",V=o.input.value}function Xt(e){if(!(e<0||e>=m.length)){if(v==="commands"){let t=m[e];o.input.value=t.cmd+(t.args?" ":""),y(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(V=null,Oe())}else if(v==="config"){let t=m[e],s={model:"/model",mode:"/mode",reasoning_effort:"/think"}[te]||`/${te}`;o.input.value=`${s} ${t.name}`,y(),o.input.focus()}else if(v==="notify"){let t=m[e];o.input.value=`/notify ${t.value}`,y(),o.input.focus()}else if(v==="new"){let t=m[e];o.input.value=`/new ${t.cwd}`,y(),o.input.focus()}else if(v==="switch"){let t=m[e];o.input.value=`/switch ${t.title||t.id}`,y(),o.input.focus()}}}async function en(e){if(!(e<0||e>=m.length))if(v==="new"){let t=m[e];o.input.value="",y(),w(),c("Creating new session\u2026"),L({cwd:t.cwd})}else if(v==="config"){let t=m[e],s=te,i=x(s);o.input.value="",y(),i&&(i.currentValue=t.value),N(),A(),c(`${i?.name||s} \u2192 ${t.name}`),await G(n.sessionId,s,t.value).catch(()=>{})}else if(v==="switch"){let t=m[e];o.input.value="",y(),w(),n.sessionId=null,c("Switching\u2026"),Promise.all([M(t.id),B(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&&I(!0)}).catch(()=>{w(),n.sessionId=null,c("err: Failed to switch session")})}else if(v==="notify"){let t=m[e];o.input.value=`/notify ${t.value}`,y(),Be(o.input.value),o.input.value=""}else{let t=m[e];o.input.value=t.cmd+(t.args?" ":""),y(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(V=null,Oe())}}function mt(e){return o.slashMenu.classList.contains("active")?e.key==="ArrowDown"?(b=(b+1)%m.length,F(),!0):e.key==="ArrowUp"?(b=(b-1+m.length)%m.length,F(),!0):e.key==="Tab"?(Xt(b),!0):!1:!1}o.slashMenu.addEventListener("mousedown",e=>{e.preventDefault();let t=e.target.closest(".slash-item");t&&en(Number(t.dataset.idx))});o.input.addEventListener("input",()=>{Oe(),o.inputArea.classList.toggle("bash-mode",o.input.value.startsWith("!"))});function gt(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 ht(e){n.pendingImages.push(e),me(),o.input.focus()}function me(){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),me()}),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/")&&ht(await gt(e));o.fileInput.value=""};o.input.addEventListener("paste",async e=>{for(let t of e.clipboardData.items)t.type.startsWith("image/")&&(e.preventDefault(),ht(await gt(t.getAsFile())))});var H=document.createElement("div");H.id="lightbox-overlay";var ne=document.createElement("img");H.appendChild(ne);document.body.appendChild(H);var ge=1,tn=.5,nn=5;function sn(){return H.classList.contains("active")}function on(e){ne.src=e,ge=1,ne.style.transform="",H.classList.add("active")}function yt(){H.classList.remove("active"),ne.src=""}document.getElementById("messages").addEventListener("click",e=>{let t=e.target;t.tagName==="IMG"&&t.classList.contains("user-image")&&on(t.src)});H.addEventListener("click",e=>{e.target===H&&yt()});document.addEventListener("keydown",e=>{e.key==="Escape"&&sn()&&yt()});H.addEventListener("wheel",e=>{e.preventDefault();let t=e.deltaY>0?-.15:.15;ge=Math.min(nn,Math.max(tn,ge+t)),ne.style.transform=`scale(${ge})`},{passive:!1});function Ae(){return n.clientId!==null}n._onCancelTimeout=()=>c("warn: Agent not responding to cancel");function wt(){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",It(),Be(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(!Ae()){c("warn: Not connected, please retry");return}o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),Z(i,!0),n.sentBashForSession=n.sessionId,We(n.sessionId,i).catch(()=>{}),h(!0);return}if(n.busy)return;if(o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!Ae()){c("warn: Not connected, please retry");return}let t=D("user",e||"(image)");for(let i of n.pendingImages){let a=document.createElement("img");a.className="user-image",a.src=i.previewUrl,t.appendChild(a)}let s=n.pendingImages.slice();n.pendingImages.length=0,me(),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(a=>a.json()).then(a=>({data:i.data,mimeType:i.mimeType,path:a.url})))).then(i=>{if(!Ae()){t.remove(),c("warn: Not connected, please retry"),h(!1);return}we(n.sessionId,e||"What is in this image?",i.map(a=>({data:a.data,mimeType:a.mimeType,path:a.path}))).catch(()=>{})}):we(n.sessionId,e).catch(()=>{}),n.turnEnded=!1,n.sentMessageForSession=n.sessionId,h(!0),et()}function vt(){Q()&&c("^C")}function St(){let e=o.input.value.trim();return e.startsWith("/")||e.startsWith("!")||e==="?"||e.startsWith("? ")}function It(){n.busy&&(St()?(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel")):(o.sendBtn.textContent="^C",o.sendBtn.title="Cancel (Ctrl+C)",o.sendBtn.classList.add("cancel")))}o.sendBtn.onclick=()=>{n.busy&&!St()?vt():wt()};o.input.addEventListener("keydown",e=>{if(mt(e)){e.preventDefault();return}if(e.key==="Enter"&&!e.shiftKey){e.preventDefault(),y(),wt();return}if(e.key==="u"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey){e.preventDefault(),o.fileInput.click();return}});document.addEventListener("keydown",e=>{if(e.key==="c"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&n.busy&&!(window.getSelection()?.toString()||o.input.selectionStart!==o.input.selectionEnd)){e.preventDefault(),vt();return}e.key==="Escape"&&o.slashMenu.classList.contains("active")&&(e.preventDefault(),y(),o.input.focus())});function bt(){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,G(n.sessionId,"mode",s.value).catch(()=>{}),c(`Mode \u2192 ${s.name}`),N()}document.addEventListener("keydown",e=>{e.key==="m"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&(e.preventDefault(),bt())});o.prompt.addEventListener("click",bt);o.input.addEventListener("input",It);o.input.addEventListener("input",()=>{o.input.style.height="auto",o.input.style.height=Math.min(o.input.scrollHeight,200)+"px"});async function rn(e){try{let t=await navigator.serviceWorker?.ready;if(!t)return;let s=await t.pushManager.getSubscription();if(!s)return;await fetch("/api/beta/push/register-client",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientId:e,endpoint:s.endpoint})})}catch{}}function De(){O("connecting","connecting");let e=new EventSource("/api/v1/events/stream");n.eventSource=e,e.onmessage=async t=>{let s=JSON.parse(t.data);s.type==="connected"&&s.clientId&&(n.clientId=s.clientId,J(s.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),rn(s.clientId),!s.agent)||C(s)},e.onerror=()=>{e.close(),ln(),setTimeout(De,3e3)},an()}async function an(){O("connecting","session loading");let e=n.sessionSwitchGen,t=Qe();if(t&&t===n.sessionId&&n.lastEventSeq>0){if(await Re(t,!0,e),e!==n.sessionSwitchGen)return;ut(),I(!1);return}if(t){if(w(),await Re(t,!1,e),e!==n.sessionSwitchGen)return;I(!0);return}try{let s=await ie();if(e!==n.sessionSwitchGen)return;if(s.length>0){if(w(),await Re(s[0].id,!1,e),e!==n.sessionSwitchGen)return;I(!0);return}}catch{}e===n.sessionSwitchGen&&L()}async function Re(e,t,s){if(t){try{let i=await M(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;let i=n.sessionCwd;w(),c("warn: Previous session expired, created new one."),L({cwd:i||void 0});return}if(s!==n.sessionSwitchGen)return;await Pe(e)}else{n.sessionId=null;let i=B(e),a;try{let[r,l]=await Promise.all([M(e),i]);if(s!==n.sessionSwitchGen)return;a=r,l||c("warn: Failed to load history.")}catch{if(s!==n.sessionSwitchGen)return;let r=n.sessionCwd;w(),c("warn: Previous session expired, created new one."),L({cwd:r||void 0});return}C({type:"session_created",sessionId:a.id,cwd:a.cwd,title:a.title,configOptions:a.configOptions,busyKind:a.busyKind})}}function ln(){O("disconnected","disconnected"),n.eventSource=null,n.clientId=null,k(),P(),n.currentBashEl&&X(n.currentBashEl,null,"disconnected"),n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,n.turnEnded=!1,Y(),h(!1)}document.addEventListener("visibilitychange",()=>{n.clientId&&J(n.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),!document.hidden&&n.sessionId&&n.lastEventSeq>0&&!n.replayInProgress&&Pe(n.sessionId).then(()=>I(!1))});o.messages.addEventListener("click",ze);ot(Ze);De();fetch("/api/v1/version").then(e=>e.json()).then(e=>{typeof e.server=="string"&&(n.serverVersion=e.server);let t=e.agent;t&&(n.agentName=t.name??null,n.agentVersion=t.version??null)}).catch(()=>{});"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}`),w(),n.sessionId=null,c("Switching\u2026"),Promise.all([M(t),B(t)]).then(([i,a])=>{s===n.sessionSwitchGen&&(C({type:"session_created",sessionId:i.id,cwd:i.cwd,title:i.title,configOptions:i.configOptions,busyKind:i.busyKind}),a&&I(!0))}).catch(()=>{w(),n.sessionId=null,c("err: Failed to switch session")})}}));
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
--blue: #58a6ff;
|
|
12
12
|
--radius: 4px;
|
|
13
13
|
--user-bg: #1e3a5f;
|
|
14
|
-
--code-bg: #
|
|
14
|
+
--code-bg: #0d2818;
|
|
15
15
|
--permission-bg: #1c1305;
|
|
16
16
|
--permission-title: #d29922;
|
|
17
17
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
--red: #cf222e;
|
|
28
28
|
--blue: #0969da;
|
|
29
29
|
--user-bg: #ddf4ff;
|
|
30
|
-
--code-bg: #
|
|
30
|
+
--code-bg: #f0faf0;
|
|
31
31
|
--permission-bg: #fff4cc;
|
|
32
32
|
--permission-title: #7a4f00;
|
|
33
33
|
}
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
--red: #cf222e;
|
|
45
45
|
--blue: #0969da;
|
|
46
46
|
--user-bg: #ddf4ff;
|
|
47
|
-
--code-bg: #
|
|
47
|
+
--code-bg: #f0faf0;
|
|
48
48
|
--permission-bg: #fff4cc;
|
|
49
49
|
--permission-title: #7a4f00;
|
|
50
50
|
}
|
|
@@ -181,17 +181,79 @@ body {
|
|
|
181
181
|
padding: 4px 14px;
|
|
182
182
|
}
|
|
183
183
|
.msg.assistant pre {
|
|
184
|
-
background:
|
|
185
|
-
padding:
|
|
186
|
-
border-radius: 4px;
|
|
184
|
+
background: none;
|
|
185
|
+
padding: 8px 14px;
|
|
187
186
|
overflow-x: auto;
|
|
188
|
-
margin:
|
|
187
|
+
margin: 0;
|
|
189
188
|
font-size: 15px;
|
|
189
|
+
border-radius: 0;
|
|
190
190
|
}
|
|
191
191
|
.msg.assistant code {
|
|
192
192
|
font-family: "SF Mono", "Fira Code", monospace;
|
|
193
193
|
font-size: 15px;
|
|
194
194
|
}
|
|
195
|
+
|
|
196
|
+
/* Code block wrapper — aligned with prompt/permission/thinking style */
|
|
197
|
+
.code-block-wrapper {
|
|
198
|
+
margin: 8px -14px;
|
|
199
|
+
background: var(--code-bg);
|
|
200
|
+
border-left: 3px solid var(--green);
|
|
201
|
+
border-radius: 0 var(--radius) var(--radius) 0;
|
|
202
|
+
position: relative;
|
|
203
|
+
overflow: hidden;
|
|
204
|
+
}
|
|
205
|
+
.code-block-wrapper pre {
|
|
206
|
+
margin: 0;
|
|
207
|
+
padding: 8px 14px;
|
|
208
|
+
}
|
|
209
|
+
.code-block-wrapper pre code,
|
|
210
|
+
.code-block-wrapper pre code.hljs {
|
|
211
|
+
background: none;
|
|
212
|
+
padding: 0;
|
|
213
|
+
}
|
|
214
|
+
.code-toolbar {
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: center;
|
|
217
|
+
justify-content: flex-end;
|
|
218
|
+
padding: 2px 6px;
|
|
219
|
+
position: absolute;
|
|
220
|
+
top: 0;
|
|
221
|
+
right: 0;
|
|
222
|
+
z-index: 1;
|
|
223
|
+
font-size: 12px;
|
|
224
|
+
color: var(--text-dim);
|
|
225
|
+
gap: 8px;
|
|
226
|
+
opacity: 0;
|
|
227
|
+
transition: opacity 0.15s;
|
|
228
|
+
}
|
|
229
|
+
.code-block-wrapper:hover .code-toolbar {
|
|
230
|
+
opacity: 1;
|
|
231
|
+
}
|
|
232
|
+
.copy-btn {
|
|
233
|
+
background: transparent;
|
|
234
|
+
color: var(--text-dim);
|
|
235
|
+
border: 1px solid var(--border);
|
|
236
|
+
border-radius: var(--radius);
|
|
237
|
+
min-width: 28px;
|
|
238
|
+
height: 22px;
|
|
239
|
+
padding: 0 4px;
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
font-family: inherit;
|
|
242
|
+
cursor: pointer;
|
|
243
|
+
display: inline-flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
justify-content: center;
|
|
246
|
+
line-height: 1;
|
|
247
|
+
transition: color 0.15s, border-color 0.15s;
|
|
248
|
+
}
|
|
249
|
+
.copy-btn:hover {
|
|
250
|
+
color: var(--text);
|
|
251
|
+
border-color: var(--text-dim);
|
|
252
|
+
}
|
|
253
|
+
.copy-btn.copied {
|
|
254
|
+
color: var(--green);
|
|
255
|
+
border-color: var(--green);
|
|
256
|
+
}
|
|
195
257
|
.msg.assistant p { margin: 4px 0; }
|
|
196
258
|
.msg.assistant ul, .msg.assistant ol { padding-left: 20px; margin: 4px 0; }
|
|
197
259
|
|
package/lib/bridge.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn, ChildProcess } from "node:child_process";
|
|
|
2
2
|
import { Writable, Readable } from "node:stream";
|
|
3
3
|
import { EventEmitter } from "node:events";
|
|
4
4
|
import * as acp from "@agentclientprotocol/sdk";
|
|
5
|
+
import { interruptBashProc } from "./session-manager.js";
|
|
5
6
|
export class AgentBridge extends EventEmitter {
|
|
6
7
|
proc = null;
|
|
7
8
|
conn = null;
|
|
@@ -10,6 +11,7 @@ export class AgentBridge extends EventEmitter {
|
|
|
10
11
|
silentSessions = new Set(); // Sessions that don't emit events
|
|
11
12
|
silentBuffers = new Map(); // Text buffers for silent sessions
|
|
12
13
|
agentCmd;
|
|
14
|
+
reloading = false;
|
|
13
15
|
constructor(agentCmd) {
|
|
14
16
|
super();
|
|
15
17
|
this.agentCmd = agentCmd;
|
|
@@ -161,6 +163,72 @@ export class AgentBridge extends EventEmitter {
|
|
|
161
163
|
this.permissionRequestSessions.delete(requestId);
|
|
162
164
|
}
|
|
163
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Restart the agent subprocess. Cancels all active work, cleans up state,
|
|
168
|
+
* shuts down the old process, and starts a new one. Sessions are restored
|
|
169
|
+
* lazily via ensureResumed() on next user interaction.
|
|
170
|
+
*/
|
|
171
|
+
async restart(sessions, titleService) {
|
|
172
|
+
if (this.reloading)
|
|
173
|
+
throw new Error("Already reloading");
|
|
174
|
+
this.reloading = true;
|
|
175
|
+
this.emit("event", { type: "agent_reloading" });
|
|
176
|
+
console.log("[bridge] reloading agent...");
|
|
177
|
+
try {
|
|
178
|
+
// 1. Cancel all active prompts + kill bash procs
|
|
179
|
+
for (const sessionId of [...sessions.activePrompts]) {
|
|
180
|
+
const proc = sessions.runningBashProcs.get(sessionId);
|
|
181
|
+
if (proc) {
|
|
182
|
+
interruptBashProc(proc);
|
|
183
|
+
sessions.runningBashProcs.delete(sessionId);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
await this.cancel(sessionId);
|
|
187
|
+
}
|
|
188
|
+
catch { /* best-effort */ }
|
|
189
|
+
}
|
|
190
|
+
// 2. Flush buffers to persist partial content
|
|
191
|
+
for (const sessionId of sessions.liveSessions) {
|
|
192
|
+
sessions.flushBuffers(sessionId);
|
|
193
|
+
}
|
|
194
|
+
// 3. Clean up SessionManager state
|
|
195
|
+
sessions.pendingPermissions.clear();
|
|
196
|
+
sessions.activePrompts.clear();
|
|
197
|
+
// 4. Clean up bridge-side silent session state
|
|
198
|
+
this.silentSessions.clear();
|
|
199
|
+
this.silentBuffers.clear();
|
|
200
|
+
// 5. Invalidate title service session
|
|
201
|
+
titleService.invalidate();
|
|
202
|
+
// 5. Clear liveSessions so ensureResumed() will re-register on next access
|
|
203
|
+
sessions.liveSessions.clear();
|
|
204
|
+
// 6. Shutdown old process
|
|
205
|
+
await this.shutdown();
|
|
206
|
+
// 7. Start new process with retry (exponential backoff, max 3 attempts)
|
|
207
|
+
let lastError;
|
|
208
|
+
for (let i = 0; i < 3; i++) {
|
|
209
|
+
try {
|
|
210
|
+
await this.start();
|
|
211
|
+
lastError = null;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
lastError = err;
|
|
216
|
+
console.error(`[bridge] start attempt ${i + 1} failed:`, err);
|
|
217
|
+
if (i < 2)
|
|
218
|
+
await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (lastError || !this.conn) {
|
|
222
|
+
const msg = lastError instanceof Error ? lastError.message : String(lastError);
|
|
223
|
+
this.emit("event", { type: "agent_reloading_failed", error: msg });
|
|
224
|
+
throw lastError;
|
|
225
|
+
}
|
|
226
|
+
console.log("[bridge] agent reloaded successfully");
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
this.reloading = false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
164
232
|
async shutdown() {
|
|
165
233
|
// Reject all pending permissions
|
|
166
234
|
for (const [id, resolve] of this.permissionResolvers) {
|
|
@@ -169,16 +237,17 @@ export class AgentBridge extends EventEmitter {
|
|
|
169
237
|
this.permissionResolvers.clear();
|
|
170
238
|
this.permissionRequestSessions.clear();
|
|
171
239
|
if (this.proc && this.proc.exitCode === null) {
|
|
172
|
-
this.proc
|
|
240
|
+
const proc = this.proc;
|
|
173
241
|
await new Promise((resolve) => {
|
|
174
242
|
const timer = setTimeout(() => {
|
|
175
|
-
|
|
243
|
+
proc.kill(process.platform === "win32" ? undefined : "SIGKILL");
|
|
176
244
|
resolve();
|
|
177
245
|
}, 5000);
|
|
178
|
-
|
|
246
|
+
proc.on("exit", () => {
|
|
179
247
|
clearTimeout(timer);
|
|
180
248
|
resolve();
|
|
181
249
|
});
|
|
250
|
+
proc.kill();
|
|
182
251
|
});
|
|
183
252
|
}
|
|
184
253
|
this.proc = null;
|
package/lib/config.js
CHANGED
|
@@ -11,10 +11,14 @@ const ConfigSchema = z.object({
|
|
|
11
11
|
bash_output: z.number().int().positive().default(1_048_576), // 1 MB
|
|
12
12
|
image_upload: z.number().int().positive().default(10_485_760), // 10 MB
|
|
13
13
|
cancel_timeout: z.number().int().nonnegative().default(10_000), // 10s; 0 disables
|
|
14
|
+
recent_paths: z.number().int().nonnegative().default(10), // /new menu display limit; 0 = show all
|
|
15
|
+
recent_paths_ttl: z.number().int().nonnegative().default(30), // days before auto-cleanup; 0 = keep forever
|
|
14
16
|
}).default({
|
|
15
17
|
bash_output: 1_048_576,
|
|
16
18
|
image_upload: 10_485_760,
|
|
17
19
|
cancel_timeout: 10_000,
|
|
20
|
+
recent_paths: 10,
|
|
21
|
+
recent_paths_ttl: 30,
|
|
18
22
|
}),
|
|
19
23
|
push: z.object({
|
|
20
24
|
vapid_subject: z.string().default("mailto:webagent@localhost"),
|
package/lib/daemon.js
CHANGED
|
@@ -8,6 +8,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
const PID_FILE = "webagent.pid";
|
|
10
10
|
const LOG_FILE = "webagent.log";
|
|
11
|
+
const LOG_MAX_LINES = 5_000;
|
|
11
12
|
const RESTART_DELAY_INITIAL = 1_000;
|
|
12
13
|
const RESTART_DELAY_MAX = 30_000;
|
|
13
14
|
const STABLE_THRESHOLD_MS = 60_000;
|
|
@@ -85,6 +86,16 @@ async function cmdStart(pidFile, logFile, args) {
|
|
|
85
86
|
}
|
|
86
87
|
const resolved = resolveArgs(args);
|
|
87
88
|
const daemonJs = join(__dirname, "daemon.js");
|
|
89
|
+
// Truncate log to last LOG_MAX_LINES before starting
|
|
90
|
+
if (existsSync(logFile)) {
|
|
91
|
+
try {
|
|
92
|
+
const lines = readFileSync(logFile, "utf-8").split("\n");
|
|
93
|
+
if (lines.length > LOG_MAX_LINES) {
|
|
94
|
+
writeFileSync(logFile, lines.slice(-LOG_MAX_LINES).join("\n"));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch { /* best-effort */ }
|
|
98
|
+
}
|
|
88
99
|
const log = openSync(logFile, "a");
|
|
89
100
|
const child = spawn(process.execPath, [daemonJs, "__supervisor", ...resolved], { detached: true, stdio: ["ignore", log, log], cwd: process.cwd(), windowsHide: true });
|
|
90
101
|
child.unref();
|
package/lib/event-handler.js
CHANGED
|
@@ -4,6 +4,7 @@ export function handleAgentEvent(event, sessions, store, bridge, config, sseMana
|
|
|
4
4
|
switch (event.type) {
|
|
5
5
|
case "connected":
|
|
6
6
|
event.cancelTimeout = config.cancelTimeout;
|
|
7
|
+
event.recentPathsLimit = config.recentPathsLimit;
|
|
7
8
|
if (event.agent)
|
|
8
9
|
sessions.agentInfo = event.agent;
|
|
9
10
|
break;
|
|
@@ -64,14 +65,13 @@ export function handleAgentEvent(event, sessions, store, bridge, config, sseMana
|
|
|
64
65
|
});
|
|
65
66
|
// Broadcast both so the frontend can render then collapse the permission card
|
|
66
67
|
sseManager.broadcast(event);
|
|
67
|
-
|
|
68
|
-
type: "
|
|
68
|
+
sseManager.broadcast({
|
|
69
|
+
type: "permission_response",
|
|
69
70
|
sessionId: event.sessionId,
|
|
70
71
|
requestId: event.requestId,
|
|
71
72
|
optionName,
|
|
72
73
|
denied: false,
|
|
73
|
-
};
|
|
74
|
-
sseManager.broadcast(resolvedEvent);
|
|
74
|
+
});
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
}
|
package/lib/routes.js
CHANGED
|
@@ -58,9 +58,10 @@ export function createRequestHandler(deps) {
|
|
|
58
58
|
version: "v1",
|
|
59
59
|
endpoints: {
|
|
60
60
|
sessions: "/api/v1/sessions",
|
|
61
|
+
paths: "/api/v1/recent-paths",
|
|
61
62
|
config: "/api/v1/config",
|
|
62
63
|
events_stream: "/api/v1/events/stream",
|
|
63
|
-
prompt: "/api/
|
|
64
|
+
prompt: "/api/beta/prompt",
|
|
64
65
|
push: "/api/beta/push",
|
|
65
66
|
clients: "/api/beta/clients",
|
|
66
67
|
},
|
|
@@ -79,9 +80,23 @@ export function createRequestHandler(deps) {
|
|
|
79
80
|
json(res, 200, {
|
|
80
81
|
configOptions: sessions?.cachedConfigOptions ?? [],
|
|
81
82
|
cancelTimeout: deps.limits.cancel_timeout ?? 0,
|
|
83
|
+
recentPathsLimit: deps.limits.recent_paths ?? 10,
|
|
82
84
|
});
|
|
83
85
|
return;
|
|
84
86
|
}
|
|
87
|
+
// GET /api/v1/recent-paths
|
|
88
|
+
if (url.startsWith("/api/v1/recent-paths") && req.method === "GET") {
|
|
89
|
+
const params = new URLSearchParams(url.split("?")[1] ?? "");
|
|
90
|
+
const limitParam = params.get("limit");
|
|
91
|
+
const limit = limitParam != null ? Math.max(0, parseInt(limitParam, 10)) : 0;
|
|
92
|
+
const ttlDays = deps.limits.recent_paths_ttl ?? 30;
|
|
93
|
+
const paths = store.listRecentPaths({
|
|
94
|
+
limit: isNaN(limit) ? 0 : limit,
|
|
95
|
+
ttlDays,
|
|
96
|
+
});
|
|
97
|
+
json(res, 200, paths);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
85
100
|
// GET /api/v1/version
|
|
86
101
|
if (url === "/api/v1/version" && req.method === "GET") {
|
|
87
102
|
json(res, 200, {
|
|
@@ -90,6 +105,26 @@ export function createRequestHandler(deps) {
|
|
|
90
105
|
});
|
|
91
106
|
return;
|
|
92
107
|
}
|
|
108
|
+
// --- POST /api/v1/bridge/reload ---
|
|
109
|
+
if (url === "/api/v1/bridge/reload" && req.method === "POST") {
|
|
110
|
+
const bridge = getBridge?.();
|
|
111
|
+
if (!bridge) {
|
|
112
|
+
json(res, 503, { error: "Agent not ready yet" });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (bridge.reloading) {
|
|
116
|
+
json(res, 409, { error: "Already reloading" });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await bridge.restart(sessions, titleService);
|
|
121
|
+
json(res, 200, { ok: true });
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
json(res, 500, { error: errorMessage(err) });
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
93
128
|
// --- Permissions (session-scoped) ---
|
|
94
129
|
// GET /api/v1/sessions/:id/permissions
|
|
95
130
|
const permListMatch = url.match(/^\/api\/v1\/sessions\/([^/]+)\/permissions\/?(\?.*)?$/);
|
|
@@ -140,18 +175,14 @@ export function createRequestHandler(deps) {
|
|
|
140
175
|
await bridge.resolvePermission(requestId, optionId);
|
|
141
176
|
}
|
|
142
177
|
sessions.pendingPermissions.delete(requestId);
|
|
143
|
-
// Store event and broadcast
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
type: "permission_resolved",
|
|
178
|
+
// Store event and broadcast (same type so SSE drops are recoverable via sync)
|
|
179
|
+
const permEventData = { requestId, optionName, denied };
|
|
180
|
+
store.saveEvent(perm.sessionId, "permission_response", { ...permEventData, optionId });
|
|
181
|
+
sseManager.broadcast({
|
|
182
|
+
type: "permission_response",
|
|
149
183
|
sessionId: perm.sessionId,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
denied,
|
|
153
|
-
};
|
|
154
|
-
sseManager.broadcast(permEvent);
|
|
184
|
+
...permEventData,
|
|
185
|
+
});
|
|
155
186
|
json(res, 200, { ok: true });
|
|
156
187
|
return;
|
|
157
188
|
}
|
|
@@ -249,6 +280,7 @@ export function createRequestHandler(deps) {
|
|
|
249
280
|
const storedImages = body.images?.map(i => ({ path: i.path, mimeType: i.mimeType }));
|
|
250
281
|
store.saveEvent(sessionId, "user_message", { text: body.text, ...(storedImages?.length && { images: storedImages }) });
|
|
251
282
|
store.updateSessionLastActive(sessionId);
|
|
283
|
+
store.touchRecentPath(session.cwd);
|
|
252
284
|
const userMsgEvent = { type: "user_message", sessionId, text: body.text, images: storedImages };
|
|
253
285
|
sseManager.broadcast(userMsgEvent);
|
|
254
286
|
// Generate title (fire-and-forget)
|
|
@@ -619,48 +651,6 @@ export function createRequestHandler(deps) {
|
|
|
619
651
|
json(res, 200, envelope, req);
|
|
620
652
|
return;
|
|
621
653
|
}
|
|
622
|
-
// POST /api/v1/prompt — quick one-shot prompt (create temp session + send)
|
|
623
|
-
if (url === "/api/v1/prompt" && req.method === "POST") {
|
|
624
|
-
if (!sessions || !getBridge) {
|
|
625
|
-
json(res, 503, { error: "Agent not available" });
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
const bridge = getBridge();
|
|
629
|
-
if (!bridge) {
|
|
630
|
-
json(res, 503, { error: "Agent not available" });
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
let body;
|
|
634
|
-
try {
|
|
635
|
-
body = JSON.parse(await readBody(req));
|
|
636
|
-
}
|
|
637
|
-
catch {
|
|
638
|
-
json(res, 400, { error: "Invalid JSON" });
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
const text = body.text;
|
|
642
|
-
if (!text || typeof text !== "string") {
|
|
643
|
-
json(res, 400, { error: "Missing required field: text" });
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
const cwd = body.cwd || undefined;
|
|
647
|
-
const { sessionId } = await sessions.createSession(bridge, cwd, undefined, "auto");
|
|
648
|
-
const streamUrl = `/api/v1/sessions/${sessionId}/events/stream`;
|
|
649
|
-
json(res, 202, { sessionId, streamUrl });
|
|
650
|
-
// Fire-and-forget: send the prompt asynchronously, tracking busy state
|
|
651
|
-
sessions.activePrompts.add(sessionId);
|
|
652
|
-
// Generate title (fire-and-forget)
|
|
653
|
-
if (titleService && !sessions.sessionHasTitle.has(sessionId)) {
|
|
654
|
-
titleService.generate(bridge, text, sessionId, (title) => {
|
|
655
|
-
const titleEvent = { type: "session_title_updated", sessionId, title };
|
|
656
|
-
sseManager.broadcast(titleEvent);
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
bridge.prompt(sessionId, text)
|
|
660
|
-
.catch(() => { })
|
|
661
|
-
.finally(() => sessions.activePrompts.delete(sessionId));
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
654
|
// --- SSE stream endpoints ---
|
|
665
655
|
// GET /api/v1/events/stream — global SSE stream
|
|
666
656
|
if (url.startsWith("/api/v1/events/stream") && req.method === "GET") {
|
|
@@ -800,6 +790,48 @@ export function createRequestHandler(deps) {
|
|
|
800
790
|
// --- Beta API routes ---
|
|
801
791
|
if (url.startsWith("/api/beta/")) {
|
|
802
792
|
res.setHeader("Content-Type", "application/json");
|
|
793
|
+
// POST /api/beta/prompt — quick one-shot prompt (create temp session + send)
|
|
794
|
+
if (url === "/api/beta/prompt" && req.method === "POST") {
|
|
795
|
+
if (!sessions || !getBridge) {
|
|
796
|
+
json(res, 503, { error: "Agent not available" });
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const bridge = getBridge();
|
|
800
|
+
if (!bridge) {
|
|
801
|
+
json(res, 503, { error: "Agent not available" });
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
let body;
|
|
805
|
+
try {
|
|
806
|
+
body = JSON.parse(await readBody(req));
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
json(res, 400, { error: "Invalid JSON" });
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const text = body.text;
|
|
813
|
+
if (!text || typeof text !== "string") {
|
|
814
|
+
json(res, 400, { error: "Missing required field: text" });
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const cwd = body.cwd || undefined;
|
|
818
|
+
const { sessionId } = await sessions.createSession(bridge, cwd, undefined, "auto");
|
|
819
|
+
const streamUrl = `/api/v1/sessions/${sessionId}/events/stream`;
|
|
820
|
+
json(res, 202, { sessionId, streamUrl });
|
|
821
|
+
// Fire-and-forget: send the prompt asynchronously, tracking busy state
|
|
822
|
+
sessions.activePrompts.add(sessionId);
|
|
823
|
+
// Generate title (fire-and-forget)
|
|
824
|
+
if (titleService && !sessions.sessionHasTitle.has(sessionId)) {
|
|
825
|
+
titleService.generate(bridge, text, sessionId, (title) => {
|
|
826
|
+
const titleEvent = { type: "session_title_updated", sessionId, title };
|
|
827
|
+
sseManager.broadcast(titleEvent);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
bridge.prompt(sessionId, text)
|
|
831
|
+
.catch(() => { })
|
|
832
|
+
.finally(() => sessions.activePrompts.delete(sessionId));
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
803
835
|
// POST /api/beta/clients/:clientId/visibility
|
|
804
836
|
const visMatch = url.match(/^\/api\/beta\/clients\/([^/]+)\/visibility$/);
|
|
805
837
|
if (visMatch && req.method === "POST") {
|
package/lib/server.js
CHANGED
|
@@ -11,6 +11,14 @@ import { createRequestHandler } from "./routes.js";
|
|
|
11
11
|
import { handleAgentEvent } from "./event-handler.js";
|
|
12
12
|
import { PushService } from "./push-service.js";
|
|
13
13
|
import { SseManager } from "./sse-manager.js";
|
|
14
|
+
// Prefix all console output with ISO-ish timestamps (YYYY-MM-DD HH:MM:SS)
|
|
15
|
+
for (const method of ["log", "error", "warn"]) {
|
|
16
|
+
const orig = console[method].bind(console);
|
|
17
|
+
console[method] = (...args) => {
|
|
18
|
+
const ts = new Date().toLocaleString("sv-SE", { hour12: false }).replace(",", "");
|
|
19
|
+
orig(ts, ...args);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
14
22
|
const config = loadConfig();
|
|
15
23
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
16
24
|
const PUBLIC_DIR = join(__dirname, "..", config.public_dir);
|
|
@@ -49,7 +57,10 @@ const server = createServer(createRequestHandler({
|
|
|
49
57
|
async function initBridge() {
|
|
50
58
|
const b = new AgentBridge(config.agent_cmd);
|
|
51
59
|
b.on("event", (event) => {
|
|
52
|
-
handleAgentEvent(event, sessions, store, b, {
|
|
60
|
+
handleAgentEvent(event, sessions, store, b, {
|
|
61
|
+
cancelTimeout: config.limits.cancel_timeout,
|
|
62
|
+
recentPathsLimit: config.limits.recent_paths,
|
|
63
|
+
}, sseManager, pushService);
|
|
53
64
|
});
|
|
54
65
|
await b.start();
|
|
55
66
|
bridge = b;
|
package/lib/store.js
CHANGED
|
@@ -58,6 +58,22 @@ export class Store {
|
|
|
58
58
|
if (!colNames.has("source")) {
|
|
59
59
|
this.db.exec("ALTER TABLE sessions ADD COLUMN source TEXT NOT NULL DEFAULT 'auto'");
|
|
60
60
|
}
|
|
61
|
+
// recent_paths: LRU path list for /new menu
|
|
62
|
+
const rpExists = this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='recent_paths'").get();
|
|
63
|
+
if (!rpExists) {
|
|
64
|
+
this.db.exec(`
|
|
65
|
+
CREATE TABLE recent_paths (
|
|
66
|
+
cwd TEXT PRIMARY KEY,
|
|
67
|
+
last_used_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%d %H:%M:%f', 'now'))
|
|
68
|
+
);
|
|
69
|
+
`);
|
|
70
|
+
// Backfill from existing sessions
|
|
71
|
+
this.db.exec(`
|
|
72
|
+
INSERT OR IGNORE INTO recent_paths (cwd, last_used_at)
|
|
73
|
+
SELECT cwd, MAX(COALESCE(last_active_at, created_at))
|
|
74
|
+
FROM sessions GROUP BY cwd;
|
|
75
|
+
`);
|
|
76
|
+
}
|
|
61
77
|
}
|
|
62
78
|
createSession(id, cwd, source = "auto") {
|
|
63
79
|
this.db.prepare("INSERT INTO sessions (id, cwd, source) VALUES (?, ?, ?)").run(id, cwd, source);
|
|
@@ -168,6 +184,26 @@ export class Store {
|
|
|
168
184
|
getAllSubscriptions() {
|
|
169
185
|
return this.db.prepare("SELECT * FROM push_subscriptions").all();
|
|
170
186
|
}
|
|
187
|
+
// --- Recent paths ---
|
|
188
|
+
touchRecentPath(cwd) {
|
|
189
|
+
this.db.prepare(`INSERT INTO recent_paths (cwd, last_used_at)
|
|
190
|
+
VALUES (?, strftime('%Y-%m-%d %H:%M:%f', 'now'))
|
|
191
|
+
ON CONFLICT(cwd) DO UPDATE SET last_used_at = strftime('%Y-%m-%d %H:%M:%f', 'now')`).run(cwd);
|
|
192
|
+
}
|
|
193
|
+
listRecentPaths(opts) {
|
|
194
|
+
const ttl = opts?.ttlDays ?? 0;
|
|
195
|
+
if (ttl > 0) {
|
|
196
|
+
this.db.prepare("DELETE FROM recent_paths WHERE last_used_at < strftime('%Y-%m-%d %H:%M:%f', 'now', ?)").run(`-${ttl} days`);
|
|
197
|
+
}
|
|
198
|
+
const limit = opts?.limit;
|
|
199
|
+
if (limit && limit > 0) {
|
|
200
|
+
return this.db.prepare("SELECT cwd, last_used_at FROM recent_paths ORDER BY last_used_at DESC LIMIT ?").all(limit);
|
|
201
|
+
}
|
|
202
|
+
return this.db.prepare("SELECT cwd, last_used_at FROM recent_paths ORDER BY last_used_at DESC").all();
|
|
203
|
+
}
|
|
204
|
+
deleteRecentPath(cwd) {
|
|
205
|
+
this.db.prepare("DELETE FROM recent_paths WHERE cwd = ?").run(cwd);
|
|
206
|
+
}
|
|
171
207
|
close() {
|
|
172
208
|
this.db.close();
|
|
173
209
|
}
|
package/lib/title-service.js
CHANGED
|
@@ -56,6 +56,10 @@ export class TitleService {
|
|
|
56
56
|
return;
|
|
57
57
|
await bridge.cancel(this.titleSessionId);
|
|
58
58
|
}
|
|
59
|
+
/** Clear the cached title session ID (e.g. after agent reload). */
|
|
60
|
+
invalidate() {
|
|
61
|
+
this.titleSessionId = null;
|
|
62
|
+
}
|
|
59
63
|
/** Ensure the dedicated title session exists. Returns session ID or null. */
|
|
60
64
|
async ensureTitleSession(bridge) {
|
|
61
65
|
if (this.titleSessionId)
|
package/package.json
CHANGED
package/dist/js/app.6BGATUZ7.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
var ue=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 ue(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 _e(e){let t={};return e?.cwd&&(t.cwd=e.cwd),e?.inheritFromSessionId&&(t.inheritFromSessionId=e.inheritFromSessionId),q("/api/v1/sessions",t)}function pe(e){return U("/api/v1/sessions/"+e,{method:"DELETE"})}function te(){return U("/api/v1/sessions")}function $(e){return U("/api/v1/sessions/"+e)}function fe(e,t,s){let i={text:t};return s?.length&&(i.images=s),q("/api/v1/sessions/"+e+"/prompt",i)}function Ne(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 qe(e,t){return U("/api/v1/sessions/"+e+"/title",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})}function Be(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/beta/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"),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,serverVersion:null,agentName:null,agentVersion:null,_cancelTimerId:null,_onCancelTimeout:null,lastEventSeq:0,oldestLoadedSeq:0,hasMoreHistory:!1,loadingOlderEvents:!1,replayInProgress:!1,replayTarget:null,replayQueue:[],unconfirmedPermissions:new Map},dt={disconnected:"is-disconnected",connecting:"is-connecting",connected:"is-connected"};function B(e,t=e){o.status.textContent="",o.status.className=`status-dot ${dt[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 Oe(e,t){let s=x(e);s&&(s.currentValue=t)}function me(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="^C",o.sendBtn.title="Cancel (Ctrl+C)",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,_e({cwd:e,inheritFromSessionId:t}).catch(()=>{})}var Ae=[];function Re(e){Ae.push(e)}function y(){for(let e of Ae)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="Message or ?",I(!1),n.sessionTitle=null,n.sessionCwd=null,n.configOptions=[],oe(null,null),o.statusBar&&(o.statusBar.textContent="")}function G(){return!n.busy||!n.sessionId?!1:(Ne(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 De(){return location.hash.slice(1)||null}function Fe(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 re(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"?p(t).replace(/\n/g,"<br>"):re(t),E(s),s}function c(e){let t=document.createElement("div");return t.className="system-msg",t.textContent=e,E(t),t}function P(){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,ut=80;function he(e){return e.scrollHeight-e.scrollTop-e.clientHeight<ut}function pt(){n.followMessages=he(o.messages)}o.messages.addEventListener("scroll",pt);function ft(){return n.followMessages||he(o.messages)}function E(e,t=!1){if(n.replayTarget)return n.replayTarget.appendChild(e),e;let s=t||ft();return o.messages.appendChild(e),S(s),e}function je(){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 ge=!1;function S(e){let t=o.messages;if(e||n.followMessages){typeof requestAnimationFrame=="function"?ge||(ge=!0,requestAnimationFrame(()=>{ge=!1,t.scrollTop=t.scrollHeight})):t.scrollTop=t.scrollHeight,n.followMessages=!0;return}n.followMessages=he(t)}function p(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function We(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 ye(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">${p(i)}</span>`):i.startsWith("@@")?s.push(`<span class="diff-hunk">${p(i)}</span>`):i.startsWith("-")?s.push(`<span class="diff-del">${p(i)}</span>`):i.startsWith("+")?s.push(`<span class="diff-add">${p(i)}</span>`):s.push(p(i)));return s.join(`
|
|
3
|
-
`)}if(e&&typeof e=="object"){let t=[];if(e.path&&t.push(`<span class="diff-file">*** ${p(e.path)}</span>`),e.old_str!=null)for(let s of String(e.old_str).split(`
|
|
4
|
-
`))t.push(`<span class="diff-del">- ${p(s)}</span>`);if(e.new_str!=null)for(let s of String(e.new_str).split(`
|
|
5
|
-
`))t.push(`<span class="diff-add">+ ${p(s)}</span>`);if(e.file_text!=null)for(let s of String(e.file_text).split(`
|
|
6
|
-
`))t.push(`<span class="diff-add">+ ${p(s)}</span>`);return 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":""}">${p(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 mt={auto:"\u25D1",light:"\u2600",dark:"\u263E"},Ue=["auto","light","dark"];function Ke(){return localStorage.getItem("theme")||"auto"}function Ve(e){document.documentElement.setAttribute("data-theme",e),o.themeBtn.textContent=mt[e],o.themeBtn.title=`Theme: ${e}`,localStorage.setItem("theme",e)}o.themeBtn.onclick=()=>{let e=Ke();Ve(Ue[(Ue.indexOf(e)+1)%3])};Ve(Ke());var we={task_complete:"\u2714",read:"cat",edit:"edit",execute:"exec",search:"find",delete:"rm"},ve="run",Ie={pending:"\u25CB",in_progress:"\u25C9",completed:"\u25CF"};function Ge(e){return n.replayTarget?.querySelector(`[id="${e}"]`)??document.getElementById(e)}function gt(e){return n.replayTarget?.querySelector(e)??document.querySelector(e)}var Je="webagent_notify_tip_shown",Qe="webagent_notify_tip_denied_shown";function ht(){if(typeof Notification>"u"||n.replayInProgress)return;let e=Notification.permission;if(e!=="granted"){if(e==="denied"){if(localStorage.getItem(Qe))return;localStorage.setItem(Qe,"1"),c("tip: notifications are blocked \u2014 allow in browser site settings to enable");return}localStorage.getItem(Je)||(localStorage.setItem(Je,"1"),c("tip: use /notify to enable background notifications"))}}function ae(){n.pendingPromptDone&&(n.pendingToolCallIds.size>0||n.pendingPermissionRequestIds.size>0||(D(),k(),P(),I(!1),n.pendingPromptDone=!1,ht()))}function yt(){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">${p(i)} \u2014 cancelled</span>`}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function wt(){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 be(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 Ye=200;async function N(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${Ye}`);if(!t.ok)return!1;let s=await t.json(),{events:i,streaming:r,hasMore:a}=be(s),l=document.createDocumentFragment(),d=Ee(i);n.replayTarget=l;for(let u=0;u<i.length;u++){let f=JSON.parse(i[u].data);ke(i[u].type,f,i,u,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&&vt(),ze(),Se(i,r),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,Xe()}}function Se(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 ze(){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 Te(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}=be(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 Se(r,a),!0;let d=document.createDocumentFragment(),u=Ee(r);n.replayTarget=d;for(let f=0;f<r.length;f++){let g=JSON.parse(r[f].data);ke(r[f].type,g,r,f,u)}return n.replayTarget=null,o.messages.appendChild(d),n.lastEventSeq=r[r.length-1].seq,ze(),Se(r,a),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,Xe()}}var z=null;function vt(){le();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"&&(z=new IntersectionObserver(t=>{t[0]?.isIntersecting&&!n.loadingOlderEvents&&n.hasMoreHistory&&n.sessionId&&It(n.sessionId)},{root:o.messages,rootMargin:"200px 0px 0px 0px"}),z.observe(e))}function le(){z&&(z.disconnect(),z=null),document.getElementById("history-sentinel")?.remove()}Re(le);async function It(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=${Ye}&before=${n.oldestLoadedSeq}`);if(!t.ok||e!==n.sessionId)return!1;let s=await t.json(),{events:i,hasMore:r}=be(s);if(i.length===0)return n.hasMoreHistory=!1,le(),!0;let a=document.createDocumentFragment(),l=Ee(i);n.replayTarget=a;for(let g=0;g<i.length;g++){let v=JSON.parse(i[g].data);ke(i[g].type,v,i,g,l)}n.replayTarget=null;let d=o.messages,u=d.scrollHeight,f=document.getElementById("history-sentinel");return f?f.after(a):d.prepend(a),d.scrollTop+=d.scrollHeight-u,n.oldestLoadedSeq=i[0].seq,n.hasMoreHistory=r===!0,n.hasMoreHistory||le(),!0}catch{return!1}finally{n.loadingOlderEvents=!1}}function Ze(){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 ${p(s.dataset.title)}`:"\u26BF";s.innerHTML=`<span style="opacity:0.5">${i} \u2014 ${p(t.optionName)}</span>`,n.unconfirmedPermissions.delete(e)}}function Ee(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 ke(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":{let l=(n.replayTarget||o.messages).lastElementChild;if(l&&l.classList.contains("msg")&&l.classList.contains("assistant")){let f=(l.getAttribute("data-raw")||"")+t.text;l.setAttribute("data-raw",f),l.innerHTML=re(f);break}R("assistant",t.text).setAttribute("data-raw",t.text);break}case"thinking":{let l=(n.replayTarget||o.messages).lastElementChild;if(l&&l.classList.contains("thinking")){let u=l.querySelector(".thinking-content");if(u){u.textContent+=`
|
|
8
|
-
`+t.text;break}}let d=document.createElement("details");d.className="thinking",d.innerHTML=`<summary>\u283F thought</summary><div class="thinking-content">${p(t.text)}</div>`,E(d);break}case"tool_call":{let a=we[t.kind]||ve,l=document.createElement("div");l.className="tool-call",l.id=`tc-${t.id}`,l.dataset.kind=t.kind;let d=`<span class="icon">${a}</span> ${p(t.title)}`,u=t.rawInput;u&&u.command?d+=`<span class="tc-detail">$ ${p(u.command)}</span>`:u&&u.path&&(d+=`<span class="tc-detail">${p(u.path)}</span>`),l.innerHTML=d;let f=t.kind==="edit"?ye(u):null;if(f){let v=document.createElement("details");v.innerHTML=`<summary>diff</summary><div class="diff-view">${f}</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):Ge(`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");if(d&&(d.textContent=l),t.content&&t.content.length&&!a.querySelector("details")&&!a.querySelector(".tc-summary")){let u=t.content.map(f=>f.type==="terminal"?`[terminal ${f.terminalId}]`:f.content?.text?f.content.text:Array.isArray(f.content)?f.content.map(g=>g.text||"").join(""):"").filter(Boolean).join(`
|
|
9
|
-
`);if(u)if(a.dataset.kind==="task_complete"){let f=document.createElement("div");f.className="tc-summary",f.textContent=u,a.appendChild(f)}else{let f=document.createElement("details");f.innerHTML=`<summary>output</summary><div class="tc-content">${p(u)}</div>`,a.appendChild(f)}}}(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">${Ie[l.status]||"?"} ${p(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 ${p(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 u=document.createElement("button"),f=(d.kind||"").includes("allow");u.className=f?"allow":"deny",u.textContent=d.name,u.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 ${p(t.title)} \u2014 ${p(d.name)}</span>`},a.appendChild(u)})),E(a),r&&r.permissions.set(t.requestId,a);break}case"permission_response":{let a=r?r.permissions.get(t.requestId):gt(`.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">${p(l)} \u2014 ${p(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:Ge("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 Xe(){let e=n.replayQueue;n.replayQueue=[];for(let t of e)St(t)||C(t)}function St(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&&me(e.configOptions),Fe(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="Message or ?",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(),P(),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=re(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(),P();let t=we[e.kind]||ve,s=document.createElement("div");s.className="tool-call",s.id=`tc-${e.id}`,s.dataset.kind=e.kind;let i=`<span class="icon">${t}</span> ${p(e.title)}`,r=e.rawInput;r&&r.command?i+=`<span class="tc-detail">$ ${p(r.command)}</span>`:r&&r.path&&(i+=`<span class="tc-detail">${p(r.path)}</span>`),s.innerHTML=i;let a=e.kind==="edit"?ye(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")&&!t.querySelector(".tc-summary")){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(`
|
|
10
|
-
`);if(r)if(t.dataset.kind==="task_complete"){let a=document.createElement("div");a.className="tc-summary",a.textContent=r,t.appendChild(a)}else{let a=document.createElement("details");a.innerHTML=`<summary>output</summary><div class="tc-content">${p(r)}</div>`,t.appendChild(a)}}}ae(),S();break}case"plan":{k(),P();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">${Ie[s.status]||"?"} ${p(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 ${p(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 ${p(e.title)} \u2014 ${p(s.name)}</span>`,ae()},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">${p(s)} \u2014 ${p(i)}</span>`}ae();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(),P();break}n.newTurnStarted=!1,e.stopReason==="cancelled"?yt():wt(),n.turnEnded=!0,n.pendingPromptDone=!0,ae();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":{Oe(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&&me(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(),P(),c(`err: ${e.message}`),I(!1);break}}async function bt(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await fetch("/api/beta/push/vapid-key");if(!t.ok)return;let{publicKey:s}=await t.json(),r=(await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:Et(s)})).toJSON();await fetch("/api/beta/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 Tt(){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/beta/push/unsubscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:s})})}catch(e){console.error("[push] unsubscribe failed:",e)}}function Et(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 Ce(){try{let e=await navigator.serviceWorker?.ready;return e?await e.pushManager.getSubscription()!==null:!1}catch{return!1}}async function Me(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 qe(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(),pe(r).catch(()=>{}),L=null,l){n.sessionSwitchGen++;let d=n.sessionSwitchGen;y(),n.sessionId=null;let[u]=await Promise.all([$(l.id),N(l.id)]);if(d!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:u.id,cwd:u.cwd,title:u.title,configOptions:u.configOptions,busyKind:u.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;await Promise.all(l.map(d=>pe(d.id).catch(()=>{}))),L=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 u=n.sessionSwitchGen;y(),n.sessionId=null;let[f]=await Promise.all([$(d.id),N(d.id)]);if(u!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:f.id,cwd:f.cwd,title:f.title,configOptions:f.configOptions,busyKind:f.busyKind}),S(!0)}catch{y(),n.sessionId=null,c("err: Failed to switch session")}return!0}case"/cancel":return n.busy?(G(),c("^C")):c("Nothing to cancel."),!0;case"/help":case"?":{let r=[];n.serverVersion&&r.push(`WebAgent ${n.serverVersion}`),n.agentName&&n.agentVersion&&r.push(`${n.agentName} ${n.agentVersion}`),r.length&&c(r.join(" \xB7 ")),c("? \u2014 Show help"),c("/help \u2014 Show help (alias)"),c("!<command> \u2014 Run bash command");for(let a of et){let l=a.args?`${a.cmd} ${a.args}`:a.cmd;c(`${l} \u2014 ${a.desc}`)}c("--- Shortcuts ---");for(let a of kt)c(`${a.key} \u2014 ${a.desc}`);c("--- Tips ---");for(let a of Ct)c(a.text);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(),u=v=>v.toLowerCase().replace(/[\s_]+/g,"-"),f=u(d),g=l.options.find(v=>u(v.value)===f||u(v.name)===f);if(!g){let v=l.options.filter(ee=>u(ee.value).includes(f)||u(ee.name).includes(f));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 Ce();return await bt(),c(l?"notify: already enabled":"notify: enabled"),!0}if(r==="off")return await Tt(),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 Ce()?c("notify: enabled"):c("notify: off \u2014 use /notify on to enable"),!0}default:return!1}}var et=[{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"}],kt=[{key:"Enter",desc:"Send message"},{key:"Shift+Enter",desc:"New line"},{key:"^C",desc:"Cancel current response"},{key:"^M",desc:"Cycle mode (Agent \u2192 Plan \u2192 Autopilot)"},{key:"^U",desc:"Upload image"}],Ct=[{text:"Tap \u276F prompt to cycle mode"}],b=-1,m=[],w="commands",Z=null,L=null,W=null,xe=!1;function Le(){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();Mt(l);return}let s=e.match(/^\/switch /);if(s){let l=e.slice(s[0].length).toLowerCase();xt(l,"switch");return}let i=e.match(/^\/(model|mode|think) /);if(i){let d={model:"model",mode:"mode",think:"reasoning_effort"}[i[1]],u=e.slice(i[0].length).toLowerCase();Lt(d,u);return}let r=e.match(/^\/notify /);if(r){let l=e.slice(r[0].length).toLowerCase();Pt(l);return}if(!e.startsWith("/")||e.includes(" ")){h();return}w="commands";let a=e.toLowerCase();if(m=et.filter(l=>l.cmd.startsWith(a)),m.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}async function xt(e,t="switch"){if(!L)try{L=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{L=null},5e3)}catch{return}if(w=t,m=L.filter(i=>e?i.title&&i.title.toLowerCase().includes(e)||i.id.startsWith(e):!0),m.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}async function Mt(e){if(!L)try{L=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{L=null},5e3)}catch{return}w="new";let t=new Map;for(let i of L){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))),m=s,m.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}function Lt(e,t){let s=x(e);if(!s){h();return}if(w="config",Z=e,m=s.options.filter(i=>t?i.value.toLowerCase().includes(t)||i.name.toLowerCase().includes(t):!0),m.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}var $t=[{value:"on",name:"on",desc:"Enable background notifications"},{value:"off",name:"off",desc:"Disable background notifications"}];async function Pt(e){if(w="notify",m=$t.filter(i=>e?i.value.includes(e)||i.name.includes(e):!0),m.length===0){h();return}xe=await Ce();let t=xe?"on":"off",s=m.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=m.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)"':""}>${p((r?"* ":" ")+s.cwd)}</span></div>`}).join("")}else if(w==="config"){let t=ie(Z)?.toLowerCase()||"";o.slashMenu.innerHTML=m.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)"':""}>${p((r?"* ":" ")+s.name)}</span></div>`}).join("")}else if(w==="notify"){let t=xe?"on":"off";o.slashMenu.innerHTML=m.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)"':""}>${p((r?"* ":" ")+s.name)}</span><span class="slash-desc">${p(s.desc)}</span></div>`}).join("")}else w==="switch"?o.slashMenu.innerHTML=m.map((t,s)=>{let i=t.id===n.sessionId,r=i?"* ":" ",a=t.title||t.id.slice(0,8)+"\u2026",l=We(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)"':""}>${p(r+a)}</span><span class="slash-desc">${p(t.cwd)} (${p(l)})</span></div>`}).join(""):o.slashMenu.innerHTML=m.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">${p(i)}</span><span class="slash-desc">${p(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,m=[],w="commands",W=o.input.value}function Ht(e){if(!(e<0||e>=m.length)){if(w==="commands"){let t=m[e];o.input.value=t.cmd+(t.args?" ":""),h(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(W=null,Le())}else if(w==="config"){let t=m[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=m[e];o.input.value=`/notify ${t.value}`,h(),o.input.focus()}else if(w==="new"){let t=m[e];o.input.value=`/new ${t.cwd}`,h(),o.input.focus()}else if(w==="switch"){let t=m[e];o.input.value=`/switch ${t.title||t.id}`,h(),o.input.focus()}}}async function _t(e){if(!(e<0||e>=m.length))if(w==="new"){let t=m[e];o.input.value="",h(),y(),c("Creating new session\u2026"),M({cwd:t.cwd})}else if(w==="config"){let t=m[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=m[e];o.input.value="",h(),y(),n.sessionId=null,c("Switching\u2026"),Promise.all([$(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=m[e];o.input.value=`/notify ${t.value}`,h(),Me(o.input.value),o.input.value=""}else{let t=m[e];o.input.value=t.cmd+(t.args?" ":""),h(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(W=null,Le())}}function tt(e){return o.slashMenu.classList.contains("active")?e.key==="ArrowDown"?(b=(b+1)%m.length,F(),!0):e.key==="ArrowUp"?(b=(b-1+m.length)%m.length,F(),!0):e.key==="Tab"?(Ht(b),!0):!1:!1}o.slashMenu.addEventListener("mousedown",e=>{e.preventDefault();let t=e.target.closest(".slash-item");t&&_t(Number(t.dataset.idx))});o.input.addEventListener("input",()=>{Le(),o.inputArea.classList.toggle("bash-mode",o.input.value.startsWith("!"))});function nt(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 st(e){n.pendingImages.push(e),ce(),o.input.focus()}function ce(){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),ce()}),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/")&&st(await nt(e));o.fileInput.value=""};o.input.addEventListener("paste",async e=>{for(let t of e.clipboardData.items)t.type.startsWith("image/")&&(e.preventDefault(),st(await nt(t.getAsFile())))});var H=document.createElement("div");H.id="lightbox-overlay";var X=document.createElement("img");H.appendChild(X);document.body.appendChild(H);var de=1,Nt=.5,qt=5;function Bt(){return H.classList.contains("active")}function Ot(e){X.src=e,de=1,X.style.transform="",H.classList.add("active")}function it(){H.classList.remove("active"),X.src=""}document.getElementById("messages").addEventListener("click",e=>{let t=e.target;t.tagName==="IMG"&&t.classList.contains("user-image")&&Ot(t.src)});H.addEventListener("click",e=>{e.target===H&&it()});document.addEventListener("keydown",e=>{e.key==="Escape"&&Bt()&&it()});H.addEventListener("wheel",e=>{e.preventDefault();let t=e.deltaY>0?-.15:.15;de=Math.min(qt,Math.max(Nt,de+t)),X.style.transform=`scale(${de})`},{passive:!1});function $e(){return n.clientId!==null}n._onCancelTimeout=()=>c("warn: Agent not responding to cancel");function ot(){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",lt(),Me(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(!$e()){c("warn: Not connected, please retry");return}o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),Q(i,!0),n.sentBashForSession=n.sessionId,Be(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"),!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!$e()){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,ce(),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(!$e()){t.remove(),c("warn: Not connected, please retry"),I(!1);return}fe(n.sessionId,e||"What is in this image?",i.map(r=>({data:r.data,mimeType:r.mimeType,path:r.path}))).catch(()=>{})}):fe(n.sessionId,e).catch(()=>{}),n.turnEnded=!1,n.sentMessageForSession=n.sessionId,I(!0),je()}function rt(){G()&&c("^C")}function at(){let e=o.input.value.trim();return e.startsWith("/")||e.startsWith("!")||e==="?"||e.startsWith("? ")}function lt(){n.busy&&(at()?(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel")):(o.sendBtn.textContent="^C",o.sendBtn.title="Cancel (Ctrl+C)",o.sendBtn.classList.add("cancel")))}o.sendBtn.onclick=()=>{n.busy&&!at()?rt():ot()};o.input.addEventListener("keydown",e=>{if(tt(e)){e.preventDefault();return}if(e.key==="Enter"&&!e.shiftKey){e.preventDefault(),h(),ot();return}if(e.key==="u"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey){e.preventDefault(),o.fileInput.click();return}});document.addEventListener("keydown",e=>{if(e.key==="c"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&n.busy&&!(window.getSelection()?.toString()||o.input.selectionStart!==o.input.selectionEnd)){e.preventDefault(),rt();return}e.key==="Escape"&&o.slashMenu.classList.contains("active")&&(e.preventDefault(),h(),o.input.focus())});function ct(){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(),ct())});o.prompt.addEventListener("click",ct);o.input.addEventListener("input",lt);o.input.addEventListener("input",()=>{o.input.style.height="auto",o.input.style.height=Math.min(o.input.scrollHeight,200)+"px"});async function At(e){try{let t=await navigator.serviceWorker?.ready;if(!t)return;let s=await t.pushManager.getSubscription();if(!s)return;await fetch("/api/beta/push/register-client",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientId:e,endpoint:s.endpoint})})}catch{}}function He(){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(()=>{}),At(s.clientId);return}C(s)},e.onerror=()=>{e.close(),Dt(),setTimeout(He,3e3)},Rt()}async function Rt(){B("connecting","session loading");let e=n.sessionSwitchGen,t=De();if(t&&t===n.sessionId&&n.lastEventSeq>0){if(await Pe(t,!0,e),e!==n.sessionSwitchGen)return;Ze(),S(!1);return}if(t){if(y(),await Pe(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 Pe(s[0].id,!1,e),e!==n.sessionSwitchGen)return;S(!0);return}}catch{}e===n.sessionSwitchGen&&M()}async function Pe(e,t,s){if(t){try{let i=await $(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 Te(e)}else{n.sessionId=null;let i=N(e),r;try{let[a,l]=await Promise.all([$(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 Dt(){B("disconnected","disconnected"),n.eventSource=null,n.clientId=null,k(),P(),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&&Te(n.sessionId).then(()=>S(!1))});He();fetch("/api/v1/version").then(e=>e.json()).then(e=>{typeof e.server=="string"&&(n.serverVersion=e.server);let t=e.agent;t&&(n.agentName=t.name??null,n.agentVersion=t.version??null)}).catch(()=>{});"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([$(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")})}}));
|