@lelouchhe/webagent 0.1.10 → 0.2.2

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.
@@ -0,0 +1,74 @@
1
+ const TITLE_MODEL = "claude-haiku-4.5";
2
+ export class TitleService {
3
+ titleSessionId = null;
4
+ activeSourceSessions = new Set();
5
+ cancelledSourceSessions = new Set();
6
+ defaultCwd;
7
+ store;
8
+ sessions;
9
+ constructor(store, sessions, defaultCwd) {
10
+ this.store = store;
11
+ this.sessions = sessions;
12
+ this.defaultCwd = defaultCwd;
13
+ }
14
+ /** Generate a title for the session (non-blocking, fire-and-forget). */
15
+ generate(bridge, userMessage, sessionId, onTitle) {
16
+ if (this.sessions.sessionHasTitle.has(sessionId) || this.activeSourceSessions.has(sessionId))
17
+ return;
18
+ this._generate(bridge, userMessage, sessionId).then((title) => {
19
+ if (title && onTitle)
20
+ onTitle(title);
21
+ }).catch((err) => {
22
+ console.error(`[title] generation failed:`, err);
23
+ });
24
+ }
25
+ async _generate(bridge, userMessage, sessionId) {
26
+ this.activeSourceSessions.add(sessionId);
27
+ const tsId = await this.ensureTitleSession(bridge);
28
+ if (!tsId) {
29
+ this.activeSourceSessions.delete(sessionId);
30
+ this.cancelledSourceSessions.delete(sessionId);
31
+ return;
32
+ }
33
+ try {
34
+ const prompt = `Generate a short title (max 30 chars, no quotes) for a chat that starts with this message. Reply with ONLY the title, nothing else:\n\n${userMessage.slice(0, 500)}`;
35
+ const title = await bridge.promptForText(tsId, prompt);
36
+ if (!title || this.cancelledSourceSessions.has(sessionId))
37
+ return;
38
+ // User may have set a title while generation was in flight
39
+ if (this.sessions.sessionHasTitle.has(sessionId))
40
+ return;
41
+ const cleaned = title.replace(/^["']|["']$/g, "").trim().slice(0, 30);
42
+ if (!cleaned)
43
+ return;
44
+ this.store.updateSessionTitle(sessionId, cleaned);
45
+ this.sessions.sessionHasTitle.add(sessionId);
46
+ return cleaned;
47
+ }
48
+ finally {
49
+ this.activeSourceSessions.delete(sessionId);
50
+ this.cancelledSourceSessions.delete(sessionId);
51
+ }
52
+ }
53
+ async cancel(sessionId, bridge) {
54
+ this.cancelledSourceSessions.add(sessionId);
55
+ if (!this.titleSessionId || !this.activeSourceSessions.has(sessionId))
56
+ return;
57
+ await bridge.cancel(this.titleSessionId);
58
+ }
59
+ /** Ensure the dedicated title session exists. Returns session ID or null. */
60
+ async ensureTitleSession(bridge) {
61
+ if (this.titleSessionId)
62
+ return this.titleSessionId;
63
+ try {
64
+ const id = await bridge.newSession(this.defaultCwd, { silent: true });
65
+ this.sessions.liveSessions.add(id);
66
+ await bridge.setConfigOption(id, "model", TITLE_MODEL).catch(() => []);
67
+ this.titleSessionId = id;
68
+ return id;
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ }
package/lib/types.js ADDED
@@ -0,0 +1,13 @@
1
+ // --- Utility ---
2
+ export function errorMessage(err) {
3
+ if (err instanceof Error)
4
+ return err.message;
5
+ if (typeof err === "string")
6
+ return err;
7
+ try {
8
+ return JSON.stringify(err);
9
+ }
10
+ catch {
11
+ return String(err);
12
+ }
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lelouchhe/webagent",
3
- "version": "0.1.10",
3
+ "version": "0.2.2",
4
4
  "description": "A terminal-style web UI for ACP-compatible agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,14 +30,16 @@
30
30
  "config.toml"
31
31
  ],
32
32
  "scripts": {
33
- "prepublishOnly": "npm run build",
33
+ "compile": "tsc -p tsconfig.build.json",
34
+ "prepublishOnly": "npm run compile && npm run build",
34
35
  "build": "node scripts/build.js",
35
36
  "start": "node --experimental-strip-types src/server.ts --config config.toml",
36
- "test": "npx tsc -p tsconfig.build.json && node --experimental-strip-types --test test/*.test.ts",
37
+ "test": "npm run compile && node --experimental-strip-types --test test/*.test.ts",
37
38
  "test:e2e": "playwright test",
38
39
  "dev": "node scripts/build.js --watch & node --experimental-strip-types --watch src/server.ts --config config.dev.toml",
39
40
  "dev:start": "node scripts/build.js --dev && node --experimental-strip-types src/server.ts --config config.dev.toml &",
40
- "dev:stop": "lsof -ti:6801 | xargs kill 2>/dev/null || true"
41
+ "dev:stop": "lsof -ti:6801 | xargs kill 2>/dev/null || true",
42
+ "screenshots": "playwright test --config scripts/screenshots.config.ts"
41
43
  },
42
44
  "dependencies": {
43
45
  "@agentclientprotocol/sdk": "^0.14.1",
@@ -1,8 +0,0 @@
1
- var ce=class extends Error{name="ApiError";status;constructor(t,s){super(s),this.status=t}};async function U(e,t){let s=await fetch(e,t);if(!s.ok){let r=`HTTP ${s.status}`;try{let a=await s.json();a.error&&(r=String(a.error))}catch{}throw new ce(s.status,r)}let i=await s.text();if(i)return JSON.parse(i)}function q(e,t){return U(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})}function He(e){let t={};return e?.cwd&&(t.cwd=e.cwd),e?.inheritFromSessionId&&(t.inheritFromSessionId=e.inheritFromSessionId),q("/api/v1/sessions",t)}function de(e){return U("/api/v1/sessions/"+e,{method:"DELETE"})}function te(){return U("/api/v1/sessions")}function P(e){return U("/api/v1/sessions/"+e)}function ue(e,t,s){let i={text:t};return s?.length&&(i.images=s),q("/api/v1/sessions/"+e+"/prompt",i)}function _e(e){return q("/api/v1/sessions/"+e+"/cancel",{})}function ne(e,t,s){return q("/api/v1/sessions/"+e+"/permissions/"+t,{optionId:s})}function se(e,t){return q("/api/v1/sessions/"+e+"/permissions/"+t,{denied:!0})}function K(e,t,s){let i=t.replace(/_/g,"-");return U("/api/v1/sessions/"+e+"/"+i,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:s})})}function Ne(e,t){return U("/api/v1/sessions/"+e+"/title",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t})})}function qe(e,t){return q("/api/v1/sessions/"+e+"/bash",{command:t})}function V(e,t,s){let i={visible:t};return s&&(i.sessionId=s),q("/api/v1/clients/"+e+"/visibility",i)}var T=e=>document.querySelector(e),o={messages:T("#messages"),input:T("#input"),sendBtn:T("#send-btn"),prompt:T("#input-prompt"),status:T("#status"),sessionInfo:T("#session-info"),newBtn:T("#new-btn"),attachBtn:T("#attach-btn"),fileInput:T("#file-input"),attachPreview:T("#attach-preview"),themeBtn:T("#theme-btn"),slashMenu:T("#slash-menu"),inputArea:T("#input-area"),statusBar:T("#status-bar")},n={eventSource:null,clientId:null,sessionId:null,sessionSwitchGen:0,sessionCwd:null,sessionTitle:null,awaitingNewSession:!1,configOptions:[],currentAssistantEl:null,currentAssistantText:"",currentThinkingEl:null,currentThinkingText:"",busy:!1,pendingImages:[],currentBashEl:null,followMessages:!0,pendingToolCallIds:new Set,pendingPermissionRequestIds:new Set,pendingPromptDone:!1,turnEnded:!1,newTurnStarted:!1,sentMessageForSession:null,sentBashForSession:null,cancelTimeout:1e4,_cancelTimerId:null,_onCancelTimeout:null,lastEventSeq:0,oldestLoadedSeq:0,hasMoreHistory:!1,loadingOlderEvents:!1,replayInProgress:!1,replayTarget:null,replayQueue:[],unconfirmedPermissions:new Map},at={disconnected:"is-disconnected",connecting:"is-connecting",connected:"is-connected"};function B(e,t=e){o.status.textContent="",o.status.className=`status-dot ${at[e]}`,o.status.dataset.state=e,o.status.setAttribute("aria-label",t),o.status.setAttribute("title",t)}function x(e){return n.configOptions.find(t=>t.id===e)}function ie(e){return x(e)?.currentValue??null}function Be(e,t){let s=x(e);s&&(s.currentValue=t)}function pe(e){n.configOptions=e,_(),O()}function _(){o.inputArea.classList.remove("plan-mode","autopilot-mode");let e=ie("mode")||"";e.includes("#plan")?o.inputArea.classList.add("plan-mode"):e.includes("#autopilot")&&o.inputArea.classList.add("autopilot-mode")}function O(){if(!o.statusBar)return;let e=ie("model"),t=n.sessionCwd||"";if(o.statusBar.textContent="",t){e&&o.statusBar.appendChild(document.createTextNode(e+" \xB7 "));let s=document.createElement("span");s.className="status-cwd",s.textContent=t,o.statusBar.appendChild(s)}else e&&(o.statusBar.textContent=e)}function I(e){n.busy=e,e?(o.sendBtn.textContent="^X",o.sendBtn.title="Cancel (Ctrl+X)",o.sendBtn.classList.add("cancel"),o.prompt.classList.add("busy")):(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel"),o.prompt.classList.remove("busy"))}function M({cwd:e,inheritFromSessionId:t=n.sessionId}={}){n.awaitingNewSession=!0,He({cwd:e,inheritFromSessionId:t}).catch(()=>{})}var Oe=[];function Ae(e){Oe.push(e)}function y(){for(let e of Oe)e();o.messages.innerHTML="",n.currentAssistantEl=null,n.currentAssistantText="",n.currentThinkingEl=null,n.currentThinkingText="",n.pendingImages.length=0,n.followMessages=!0,n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.unconfirmedPermissions.clear(),n.pendingPromptDone=!1,n.turnEnded=!1,n.newTurnStarted=!1,n._cancelTimerId=null,n.lastEventSeq=0,n.oldestLoadedSeq=0,n.hasMoreHistory=!1,n.loadingOlderEvents=!1,n.replayInProgress=!1,n.replayQueue=[],o.attachPreview.innerHTML="",o.attachPreview.classList.remove("active"),o.input.disabled=!1,o.sendBtn.disabled=!1,o.input.placeholder="",I(!1),n.sessionTitle=null,n.sessionCwd=null,n.configOptions=[],oe(null,null),o.statusBar&&(o.statusBar.textContent="")}function L(){o.newBtn.classList.toggle("hidden",o.input.value.length>0)}function G(){return!n.busy||!n.sessionId?!1:(_e(n.sessionId).catch(()=>{}),J(),n.cancelTimeout>0&&(n._cancelTimerId=setTimeout(()=>{n._cancelTimerId=null,n.busy&&(n.turnEnded=!0,I(!1),n._onCancelTimeout?.())},n.cancelTimeout)),!0)}function J(){n._cancelTimerId!=null&&(clearTimeout(n._cancelTimerId),n._cancelTimerId=null)}function Re(){return location.hash.slice(1)||null}function De(e){history.replaceState(null,"",`#${e}`)}function oe(e,t){o.sessionInfo.textContent=t||(e?e.slice(0,8)+"\u2026":""),document.title=t||">_"}B("disconnected");marked.setOptions({breaks:!0,gfm:!0});function me(e){return DOMPurify.sanitize(marked.parse(e))}function R(e,t){let s=document.createElement("div");return s.className=`msg ${e}`,s.innerHTML=e==="user"?u(t).replace(/\n/g,"<br>"):me(t),E(s),s}function c(e){let t=document.createElement("div");return t.className="system-msg",t.textContent=e,E(t),t}function H(){n.currentAssistantEl=null,n.currentAssistantText=""}function k(){if(n.currentThinkingEl){let e=n.currentThinkingEl.querySelector("summary");e.textContent="\u283F thought",e.classList.remove("active"),e.style.animation="none",n.currentThinkingEl=null,n.currentThinkingText=""}}var A=null,lt=80;function ge(e){return e.scrollHeight-e.scrollTop-e.clientHeight<lt}function ct(){n.followMessages=ge(o.messages)}o.messages.addEventListener("scroll",ct);function dt(){return n.followMessages||ge(o.messages)}function E(e,t=!1){if(n.replayTarget)return n.replayTarget.appendChild(e),e;let s=t||dt();return o.messages.appendChild(e),S(s),e}function Ue(){D(),A=document.createElement("div"),A.id="waiting",A.innerHTML='<span class="cursor">\u258C</span>',E(A,!0)}function D(){A&&(A.remove(),A=null)}var fe=!1;function S(e){let t=o.messages;if(e||n.followMessages){typeof requestAnimationFrame=="function"?fe||(fe=!0,requestAnimationFrame(()=>{fe=!1,t.scrollTop=t.scrollHeight})):t.scrollTop=t.scrollHeight,n.followMessages=!0;return}n.followMessages=ge(t)}function u(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function je(e){if(!e)return"";let t=new Date(e.endsWith("Z")?e:e+"Z"),s=i=>String(i).padStart(2,"0");return`${t.getFullYear()}-${s(t.getMonth()+1)}-${s(t.getDate())} ${s(t.getHours())}:${s(t.getMinutes())}`}function he(e){if(typeof e=="string"&&e.includes("*** Begin Patch")){let t=e.split(`
2
- `),s=[];for(let i of t)i.startsWith("*** Begin Patch")||i.startsWith("*** End Patch")||(i.startsWith("*** Update File:")||i.startsWith("*** Add File:")||i.startsWith("*** Delete File:")?s.push(`<span class="diff-file">${u(i)}</span>`):i.startsWith("@@")?s.push(`<span class="diff-hunk">${u(i)}</span>`):i.startsWith("-")?s.push(`<span class="diff-del">${u(i)}</span>`):i.startsWith("+")?s.push(`<span class="diff-add">${u(i)}</span>`):s.push(u(i)));return s.join(`
3
- `)}if(e&&typeof e=="object"){let t=[];if(e.path&&t.push(`<span class="diff-file">*** ${u(e.path)}</span>`),e.old_str!=null)for(let s of String(e.old_str).split(`
4
- `))t.push(`<span class="diff-del">- ${u(s)}</span>`);if(e.new_str!=null)for(let s of String(e.new_str).split(`
5
- `))t.push(`<span class="diff-add">+ ${u(s)}</span>`);return e.file_text!=null&&t.push(`<span class="diff-add">+ (new file, ${e.file_text.split(`
6
- `).length} lines)</span>`),t.length>(e.path?1:0)?t.join(`
7
- `):null}return null}function Q(e,t=!1){let s=document.createElement("div");return s.className="bash-block",s.innerHTML=`<span class="bash-cmd${t?" running":""}">${u(e)}</span><div class="bash-output"></div>`,s.querySelector(".bash-cmd").addEventListener("click",()=>{let i=s.querySelector(".bash-output");i.style.display==="none"?i.style.display="block":i.classList.contains("has-content")&&(i.style.display="none")}),E(s),t&&(n.currentBashEl=s),s}function Y(e,t,s){if(!e)return;let i=e.querySelector(".bash-cmd");i.classList.remove("running");let r="";if(s?r=`[signal: ${s}]`:t!==0&&t!=null&&(r=`[exit: ${t}]`),r){let a=document.createElement("span");a.className=`bash-exit ${t===0?"ok":"fail"}`,a.textContent=r,i.after(a)}e===n.currentBashEl&&(n.currentBashEl=null)}var ut={auto:"\u25D1",light:"\u2600",dark:"\u263E"},Fe=["auto","light","dark"];function We(){return localStorage.getItem("theme")||"auto"}function Ke(e){document.documentElement.setAttribute("data-theme",e),o.themeBtn.textContent=ut[e],o.themeBtn.title=`Theme: ${e}`,localStorage.setItem("theme",e)}o.themeBtn.onclick=()=>{let e=We();Ke(Fe[(Fe.indexOf(e)+1)%3])};Ke(We());var ye={read:"cat",edit:"edit",execute:"exec",search:"find",delete:"rm"},we="run",ve={pending:"\u25CB",in_progress:"\u25C9",completed:"\u25CF"};function Ve(e){return n.replayTarget?.querySelector(`[id="${e}"]`)??document.getElementById(e)}function pt(e){return n.replayTarget?.querySelector(e)??document.querySelector(e)}var Ge="webagent_notify_tip_shown",Je="webagent_notify_tip_denied_shown";function ft(){if(typeof Notification>"u"||n.replayInProgress)return;let e=Notification.permission;if(e!=="granted"){if(e==="denied"){if(localStorage.getItem(Je))return;localStorage.setItem(Je,"1"),c("tip: notifications are blocked \u2014 allow in browser site settings to enable");return}localStorage.getItem(Ge)||(localStorage.setItem(Ge,"1"),c("tip: use /notify to enable background notifications"))}}function re(){n.pendingPromptDone&&(n.pendingToolCallIds.size>0||n.pendingPermissionRequestIds.size>0||(D(),k(),H(),I(!1),n.pendingPromptDone=!1,ft()))}function mt(){for(let e of n.pendingToolCallIds){let t=document.getElementById(`tc-${e}`);if(!t)continue;t.className="tool-call failed";let s=t.querySelector(".icon");s&&(s.textContent="\u2717")}for(let e of n.pendingPermissionRequestIds){let t=document.querySelector(`.permission[data-request-id="${e}"]`);if(!t||!t.querySelector("button"))continue;let i=t.querySelector(".title")?.textContent||"\u26BF";t.innerHTML=`<span style="opacity:0.5">${u(i)} \u2014 cancelled</span>`}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function gt(){for(let e of n.pendingToolCallIds){let t=document.getElementById(`tc-${e}`);if(!t)continue;t.className="tool-call completed";let s=t.querySelector(".icon");s&&(s.textContent="\u2713")}n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear()}function Se(e){if(Array.isArray(e))return{events:e,streaming:{thinking:!1,assistant:!1}};let t=e;return{events:t.events??[],streaming:t.streaming??{thinking:!1,assistant:!1},total:typeof t.total=="number"?t.total:void 0,hasMore:typeof t.hasMore=="boolean"?t.hasMore:void 0}}var Qe=200;async function N(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${Qe}`);if(!t.ok)return!1;let s=await t.json(),{events:i,streaming:r,hasMore:a}=Se(s),l=document.createDocumentFragment(),d=Te(i);n.replayTarget=l;for(let p=0;p<i.length;p++){let m=JSON.parse(i[p].data);Ee(i[p].type,m,i,p,d)}return n.replayTarget=null,o.messages.style.display="none",o.messages.appendChild(l),o.messages.style.display="",i.length&&(n.lastEventSeq=i[i.length-1].seq,n.oldestLoadedSeq=i[0].seq),n.hasMoreHistory=a===!0,n.hasMoreHistory&&ht(),Ye(),Ie(i,r),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,ze()}}function Ie(e,t){if(t.thinking&&e.length){for(let s=e.length-1;s>=0;s--)if(e[s].type==="thinking"){let i=JSON.parse(e[s].data),r=o.messages.querySelectorAll(".thinking"),a=r[r.length-1];if(a){n.currentThinkingEl=a,n.currentThinkingText=i.text;let l=a.querySelector("summary");l&&(l.textContent="\u283F thinking...",l.classList.add("active"))}break}}if(t.assistant&&e.length){for(let s=e.length-1;s>=0;s--)if(e[s].type==="assistant_message"){let i=JSON.parse(e[s].data),r=o.messages.querySelectorAll(".msg.assistant"),a=r[r.length-1];a&&(n.currentAssistantEl=a,n.currentAssistantText=i.text);break}}}function Ye(){let e=o.messages.querySelector("[data-sync-boundary]");e&&e.removeAttribute("data-sync-boundary");let t=o.messages.lastElementChild;t&&t.setAttribute("data-sync-boundary","")}async function be(e){n.replayInProgress=!0,n.replayQueue=[];try{let t=`/api/v1/sessions/${e}/events?after=${n.lastEventSeq}`,s=await fetch(t);if(!s.ok)return!1;let i=await s.json(),{events:r,streaming:a}=Se(i),l=o.messages.querySelector("[data-sync-boundary]");if(l)for(;l.nextElementSibling;)l.nextElementSibling.remove();if(n.currentAssistantEl=null,n.currentAssistantText="",n.currentThinkingEl=null,n.currentThinkingText="",n.currentBashEl=null,r.length===0)return Ie(r,a),!0;let d=document.createDocumentFragment(),p=Te(r);n.replayTarget=d;for(let m=0;m<r.length;m++){let g=JSON.parse(r[m].data);Ee(r[m].type,g,r,m,p)}return n.replayTarget=null,o.messages.appendChild(d),n.lastEventSeq=r[r.length-1].seq,Ye(),Ie(r,a),!0}catch{return!1}finally{n.replayTarget=null,n.replayInProgress=!1,ze()}}var X=null;function ht(){ae();let e=document.createElement("div");e.id="history-sentinel",e.className="history-sentinel",e.textContent="\u2191 loading\u2026",o.messages.prepend(e),typeof IntersectionObserver=="function"&&(X=new IntersectionObserver(t=>{t[0]?.isIntersecting&&!n.loadingOlderEvents&&n.hasMoreHistory&&n.sessionId&&yt(n.sessionId)},{root:o.messages,rootMargin:"200px 0px 0px 0px"}),X.observe(e))}function ae(){X&&(X.disconnect(),X=null),document.getElementById("history-sentinel")?.remove()}Ae(ae);async function yt(e){if(n.loadingOlderEvents||!n.hasMoreHistory||n.oldestLoadedSeq<=0)return!1;n.loadingOlderEvents=!0;try{let t=await fetch(`/api/v1/sessions/${e}/events?limit=${Qe}&before=${n.oldestLoadedSeq}`);if(!t.ok||e!==n.sessionId)return!1;let s=await t.json(),{events:i,hasMore:r}=Se(s);if(i.length===0)return n.hasMoreHistory=!1,ae(),!0;let a=document.createDocumentFragment(),l=Te(i);n.replayTarget=a;for(let g=0;g<i.length;g++){let v=JSON.parse(i[g].data);Ee(i[g].type,v,i,g,l)}n.replayTarget=null;let d=o.messages,p=d.scrollHeight,m=document.getElementById("history-sentinel");return m?m.after(a):d.prepend(a),d.scrollTop+=d.scrollHeight-p,n.oldestLoadedSeq=i[0].seq,n.hasMoreHistory=r===!0,n.hasMoreHistory||ae(),!0}catch{return!1}finally{n.loadingOlderEvents=!1}}function Xe(){for(let[e,t]of n.unconfirmedPermissions){let s=document.querySelector(`.permission[data-request-id="${e}"]`);if(!s||!s.querySelector("button")){n.unconfirmedPermissions.delete(e);continue}t.denied?se(t.sessionId,e).catch(()=>{}):ne(t.sessionId,e,t.optionId).catch(()=>{});let i=s.dataset.title?`\u26BF ${u(s.dataset.title)}`:"\u26BF";s.innerHTML=`<span style="opacity:0.5">${i} \u2014 ${u(t.optionName)}</span>`,n.unconfirmedPermissions.delete(e)}}function Te(e){let t=new Set;for(let s of e)s.type==="permission_response"&&t.add(JSON.parse(s.data).requestId);return{toolCalls:new Map,permissions:new Map,resolvedPermissions:t,currentBashEl:null}}function Ee(e,t,s,i,r){switch(e){case"user_message":{let a=R("user",t.text);if(t.images)for(let l of t.images){let d=document.createElement("img");d.className="user-image",d.src=l.path,a.appendChild(d)}break}case"assistant_message":R("assistant",t.text);break;case"thinking":{let a=document.createElement("details");a.className="thinking",a.innerHTML=`<summary>\u283F thought</summary><div class="thinking-content">${u(t.text)}</div>`,E(a);break}case"tool_call":{let a=ye[t.kind]||we,l=document.createElement("div");l.className="tool-call",l.id=`tc-${t.id}`;let d=`<span class="icon">${a}</span> ${u(t.title)}`,p=t.rawInput;p&&p.command?d+=`<span class="tc-detail">$ ${u(p.command)}</span>`:p&&p.path&&(d+=`<span class="tc-detail">${u(p.path)}</span>`),l.innerHTML=d;let m=t.kind==="edit"?he(p):null;if(m){let v=document.createElement("details");v.innerHTML=`<summary>diff</summary><div class="diff-view">${m}</div>`,l.appendChild(v)}let g=l.querySelector(".tc-detail");g&&l.addEventListener("click",v=>{v.target.closest("details")||g.classList.toggle("expanded")}),E(l),r&&r.toolCalls.set(t.id,l);break}case"tool_call_update":{let a=r?r.toolCalls.get(t.id):Ve(`tc-${t.id}`);if(a){let l=t.status==="completed"?"\u2713":t.status==="failed"?"\u2717":"\u2026";a.className=`tool-call ${t.status}`;let d=a.querySelector(".icon");d&&(d.textContent=l)}(t.status==="completed"||t.status==="failed")&&n.pendingToolCallIds.delete(t.id);break}case"plan":{let a=document.createElement("div");a.className="plan",a.innerHTML='<div class="plan-title">\u2015 plan</div>'+(t.entries||[]).map(l=>`<div class="plan-entry">${ve[l.status]||"?"} ${u(l.content)}</div>`).join(""),E(a);break}case"permission_request":{let a=document.createElement("div");a.className="permission",a.dataset.requestId=t.requestId,a.dataset.title=t.title||"",a.innerHTML=`<span class="title" style="opacity:0.5">\u26BF ${u(t.title)}</span> `,!(r?r.resolvedPermissions.has(t.requestId):s&&s.slice(i+1).some(d=>d.type==="permission_response"&&JSON.parse(d.data).requestId===t.requestId))&&t.options&&(a.querySelector(".title").style.opacity="1",t.options.forEach(d=>{let p=document.createElement("button"),m=(d.kind||"").includes("allow");p.className=m?"allow":"deny",p.textContent=d.name,p.onclick=()=>{(d.kind||"").includes("reject")||(d.kind||"").includes("deny")?se(n.sessionId,t.requestId).catch(()=>{}):ne(n.sessionId,t.requestId,d.optionId).catch(()=>{}),a.innerHTML=`<span style="opacity:0.5">\u26BF ${u(t.title)} \u2014 ${u(d.name)}</span>`},a.appendChild(p)})),E(a),r&&r.permissions.set(t.requestId,a);break}case"permission_response":{let a=r?r.permissions.get(t.requestId):pt(`.permission[data-request-id="${t.requestId}"]`);if(a){let l=a.dataset.title?`\u26BF ${a.dataset.title}`:"\u26BF",d=t.optionName||(t.denied?"denied":"allowed");a.innerHTML=`<span style="opacity:0.5">${u(l)} \u2014 ${u(d)}</span>`}break}case"bash_command":{let a=Q(t.command,!1);r?r.currentBashEl=a:a.id="bash-replay-pending";break}case"bash_result":{let a=r?r.currentBashEl:Ve("bash-replay-pending");if(a){if(r||a.removeAttribute("id"),t.output){let l=a.querySelector(".bash-output");l&&(l.textContent=t.output,l.classList.add("has-content"))}Y(a,t.code,t.signal),r&&(r.currentBashEl=null)}break}case"prompt_done":n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,I(!1);break}}function ze(){let e=n.replayQueue;n.replayQueue=[];for(let t of e)wt(t)||C(t)}function wt(e){switch(e.type){case"tool_call":return!!document.getElementById(`tc-${e.id}`);case"permission_request":return!!document.querySelector(`.permission[data-request-id="${e.requestId}"]`);case"thought_chunk":return!!n.currentThinkingEl;case"message_chunk":return!!n.currentAssistantEl;default:return!1}}function C(e){if(n.replayInProgress){console.log("[handleEvent-DEBUG] QUEUED (replayInProgress):",e.type),n.replayQueue.push(e);return}if(!(e.sessionId&&e.type!=="session_created"&&e.type!=="session_deleted"&&(!n.sessionId||e.sessionId!==n.sessionId)))switch(e.type){case"connected":e.cancelTimeout!=null&&(n.cancelTimeout=e.cancelTimeout);break;case"session_created":if(!n.awaitingNewSession&&n.sessionId&&e.sessionId!==n.sessionId)break;if(n.awaitingNewSession=!1,n.sessionId=e.sessionId,n.sessionCwd=e.cwd||n.sessionCwd,n.sessionTitle=e.title||null,e.configOptions?.length&&pe(e.configOptions),De(n.sessionId),n.clientId&&V(n.clientId,!document.hidden,n.sessionId).catch(()=>{}),oe(n.sessionId,n.sessionTitle),B("connected","connected"),o.input.disabled=!1,o.sendBtn.disabled=!1,o.input.placeholder="",I(!!e.busyKind),n.newTurnStarted=!1,e.busyKind==="bash"){let t=document.getElementById("bash-replay-pending");t&&(t.removeAttribute("id"),t.querySelector(".bash-cmd")?.classList.add("running"),n.currentBashEl=t)}else n.currentBashEl=null;o.messages.children.length===0&&c(`Session created: ${n.sessionTitle||e.sessionId.slice(0,8)+"\u2026"}`),O();break;case"user_message":{if(n.sentMessageForSession===e.sessionId){n.sentMessageForSession=null;break}if(k(),H(),n.newTurnStarted=!0,n.turnEnded=!1,e.sessionId===n.sessionId){let t=R("user",e.text);if(e.images)for(let s of e.images){let i=document.createElement("img");i.className="user-image",i.src=s.path,t.appendChild(i)}}break}case"message_chunk":if(n.turnEnded)break;D(),k(),n.currentAssistantEl||(n.currentAssistantEl=R("assistant",""),n.currentAssistantText=""),n.currentAssistantText+=e.text,n.currentAssistantEl.innerHTML=me(n.currentAssistantText),S();break;case"thought_chunk":if(n.turnEnded)break;D(),n.currentThinkingEl||(n.currentThinkingEl=document.createElement("details"),n.currentThinkingEl.className="thinking",n.currentThinkingEl.innerHTML='<summary class="active">\u283F thinking...</summary><div class="thinking-content"></div>',n.currentThinkingText="",E(n.currentThinkingEl)),n.currentThinkingText+=e.text,n.currentThinkingEl.querySelector(".thinking-content").textContent=n.currentThinkingText,S();break;case"tool_call":{if(n.turnEnded)break;n.pendingToolCallIds.add(e.id),I(!0),D(),k(),H();let t=ye[e.kind]||we,s=document.createElement("div");s.className="tool-call",s.id=`tc-${e.id}`;let i=`<span class="icon">${t}</span> ${u(e.title)}`,r=e.rawInput;r&&r.command?i+=`<span class="tc-detail">$ ${u(r.command)}</span>`:r&&r.path&&(i+=`<span class="tc-detail">${u(r.path)}</span>`),s.innerHTML=i;let a=e.kind==="edit"?he(r):null;if(a){let d=document.createElement("details");d.innerHTML=`<summary>diff</summary><div class="diff-view">${a}</div>`,s.appendChild(d)}let l=s.querySelector(".tc-detail");l&&s.addEventListener("click",d=>{d.target.closest("details")||l.classList.toggle("expanded")}),E(s);break}case"tool_call_update":{let t=document.getElementById(`tc-${e.id}`);if((e.status==="completed"||e.status==="failed")&&n.pendingToolCallIds.delete(e.id),t){let s=e.status==="completed"?"\u2713":e.status==="failed"?"\u2717":"\u2026";t.className=`tool-call ${e.status}`;let i=t.querySelector(".icon");if(i&&(i.textContent=s),e.content&&e.content.length&&!t.querySelector("details")){let r=e.content.map(a=>a.type==="terminal"?`[terminal ${a.terminalId}]`:a.content?.text?a.content.text:Array.isArray(a.content)?a.content.map(l=>l.text||"").join(""):"").filter(Boolean).join(`
8
- `);if(r){let a=document.createElement("details");a.innerHTML=`<summary>output</summary><div class="tc-content">${u(r)}</div>`,t.appendChild(a)}}}re(),S();break}case"plan":{k(),H();let t=document.createElement("div");t.className="plan",t.innerHTML='<div class="plan-title">\u2015 plan</div>'+e.entries.map(s=>`<div class="plan-entry">${ve[s.status]||"?"} ${u(s.content)}</div>`).join(""),E(t);break}case"permission_request":{if(n.turnEnded||document.querySelector(`.permission[data-request-id="${e.requestId}"]`))break;n.pendingPermissionRequestIds.add(e.requestId),I(!0),k();let t=document.createElement("div");t.className="permission",t.dataset.requestId=e.requestId,t.dataset.title=e.title||"",t.innerHTML=`<span class="title">\u26BF ${u(e.title)}</span> `,e.options.forEach(s=>{let i=document.createElement("button"),r=(s.kind||"").includes("allow");i.className=r?"allow":"deny",i.textContent=s.name,i.onclick=()=>{let a=(s.kind||"").includes("reject")||(s.kind||"").includes("deny");a?se(n.sessionId,e.requestId).catch(()=>{}):ne(n.sessionId,e.requestId,s.optionId).catch(()=>{}),n.pendingPermissionRequestIds.delete(e.requestId),n.unconfirmedPermissions.set(e.requestId,{sessionId:n.sessionId,optionId:s.optionId,optionName:s.name,denied:a}),t.innerHTML=`<span style="opacity:0.5">\u26BF ${u(e.title)} \u2014 ${u(s.name)}</span>`,re()},t.appendChild(i)}),E(t);break}case"permission_resolved":{n.pendingPermissionRequestIds.delete(e.requestId),n.unconfirmedPermissions.delete(e.requestId);let t=document.querySelector(`.permission[data-request-id="${e.requestId}"]`);if(e.sessionId===n.sessionId&&t){let s=t.dataset.title?`\u26BF ${t.dataset.title}`:"\u26BF",i=e.optionName||(e.denied?"denied":"allowed");t.innerHTML=`<span style="opacity:0.5">${u(s)} \u2014 ${u(i)}</span>`}re();break}case"bash_command":{if(n.sentBashForSession===e.sessionId){n.sentBashForSession=null;break}e.sessionId===n.sessionId&&(Q(e.command,!0),I(!0));break}case"bash_output":{if(e.sessionId!==n.sessionId)break;if(n.currentBashEl){let t=n.currentBashEl.querySelector(".bash-output");if(e.stream==="stderr"){let s=document.createElement("span");s.className="stderr",s.textContent=e.text,t.appendChild(s)}else t.appendChild(document.createTextNode(e.text));t.classList.add("has-content"),t.scrollTop=t.scrollHeight,S()}break}case"bash_done":{if(e.sessionId!==n.sessionId)break;Y(n.currentBashEl,e.code,e.signal),e.error&&c(`err: ${e.error}`),I(!1);break}case"prompt_done":{if(J(),e.stopReason==="cancelled"&&n.newTurnStarted){n.newTurnStarted=!1,k(),H();break}n.newTurnStarted=!1,e.stopReason==="cancelled"?mt():gt(),n.turnEnded=!0,n.pendingPromptDone=!0,re();break}case"session_deleted":e.sessionId===n.sessionId&&(c("warn: This session has been deleted."),o.input.disabled=!0,o.sendBtn.disabled=!0,o.input.placeholder="Session deleted");break;case"session_expired":y(),c("warn: Previous session expired, created new one."),M();break;case"config_set":{Be(e.configId,e.value);let t=x(e.configId),s=t?.name||e.configId,i=t?.options.find(r=>r.value===e.value)?.name||e.value;c(`ok: ${s}: ${i}`),e.configId==="mode"&&_(),O();break}case"config_option_update":e.configOptions?.length&&pe(e.configOptions);break;case"session_title_updated":e.sessionId===n.sessionId&&(n.sessionTitle=e.title,oe(n.sessionId,n.sessionTitle));break;case"error":n.awaitingNewSession=!1,n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,D(),k(),H(),c(`err: ${e.message}`),I(!1);break}}async function vt(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await fetch("/api/v1/push/vapid-key");if(!t.ok)return;let{publicKey:s}=await t.json(),r=(await e.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:St(s)})).toJSON();await fetch("/api/v1/push/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:r.endpoint,keys:r.keys,clientId:n.clientId})})}catch(e){console.error("[push] subscribe failed:",e)}}async function It(){try{let e=await navigator.serviceWorker?.ready;if(!e)return;let t=await e.pushManager.getSubscription();if(!t)return;let s=t.endpoint;await t.unsubscribe(),await fetch("/api/v1/push/unsubscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({endpoint:s})})}catch(e){console.error("[push] unsubscribe failed:",e)}}function St(e){let t="=".repeat((4-e.length%4)%4),s=(e+t).replace(/-/g,"+").replace(/_/g,"/"),i=atob(s),r=new Uint8Array(i.length);for(let a=0;a<i.length;a++)r[a]=i.charCodeAt(a);return r}async function ke(){try{let e=await navigator.serviceWorker?.ready;return e?await e.pushManager.getSubscription()!==null:!1}catch{return!1}}async function xe(e){let t=e.split(/\s+/),s=t[0].toLowerCase(),i=t.slice(1).join(" ").trim();switch(s){case"/new":return y(),c("Creating new session\u2026"),M({cwd:i||n.sessionCwd}),!0;case"/pwd":return c(`\u{1F4C1} ${n.sessionCwd||"unknown"}`),!0;case"/rename":{if(!n.sessionId)return c("err: No active session"),!0;if(!i)return c(`Current: ${n.sessionTitle||"(untitled)"}`),c("Usage: /rename <new title>"),!0;try{await Ne(n.sessionId,i),c(`Renamed \u2192 ${i}`)}catch{c("err: Failed to rename session")}return!0}case"/sessions":return c("Removed. Use /switch to see all sessions."),!0;case"/exit":{if(!n.sessionId)return c("warn: No active session"),!0;let r=n.sessionId;try{let l=(await te()).find(d=>d.id!==r);if(n.busy&&G(),de(r).catch(()=>{}),$=null,l){n.sessionSwitchGen++;let d=n.sessionSwitchGen;y(),n.sessionId=null;let[p]=await Promise.all([P(l.id),N(l.id)]);if(d!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:p.id,cwd:p.cwd,title:p.title,configOptions:p.configOptions,busyKind:p.busyKind}),S(!0)}else y(),n.sessionId=null,c("Creating new session\u2026"),M({inheritFromSessionId:null})}catch{c("err: Failed to exit session")}return!0}case"/prune":{try{let l=(await(await fetch("/api/v1/sessions")).json()).filter(d=>d.id!==n.sessionId);if(l.length===0)return c("No other sessions to prune."),!0;for(let d of l)de(d.id).catch(()=>{});$=null,c(`Pruned ${l.length} session(s).`)}catch{c("err: Failed to prune sessions")}return!0}case"/switch":{if(!i)return c("Usage: /switch <title or id prefix>"),!0;try{let a=await(await fetch("/api/v1/sessions")).json(),l=i.toLowerCase(),d=a.find(g=>g.id.startsWith(i)||g.title&&g.title.toLowerCase().includes(l));if(!d)return c(`err: No session matching "${i}"`),!0;n.sessionSwitchGen++;let p=n.sessionSwitchGen;y(),n.sessionId=null;let[m]=await Promise.all([P(d.id),N(d.id)]);if(p!==n.sessionSwitchGen)return!0;C({type:"session_created",sessionId:m.id,cwd:m.cwd,title:m.title,configOptions:m.configOptions,busyKind:m.busyKind}),S(!0)}catch{y(),n.sessionId=null,c("err: Failed to switch session")}return!0}case"/cancel":return n.busy?(G(),c("^X")):c("Nothing to cancel."),!0;case"/help":case"?":c("? \u2014 Show help"),c("/help \u2014 Show help (alias)"),c("!<command> \u2014 Run bash command");for(let r of Ze){let a=r.args?`${r.cmd} ${r.args}`:r.cmd;c(`${a} \u2014 ${r.desc}`)}c("--- Shortcuts ---");for(let r of bt)c(`${r.key} \u2014 ${r.desc}`);return!0;case"/model":case"/mode":case"/think":{let a={"/model":"model","/mode":"mode","/think":"reasoning_effort"}[s],l=x(a);if(!i){let v=l?.options.find(ee=>ee.value===l.currentValue)?.name||l?.currentValue||"unknown";return c(`${l?.name||a}: ${v}`),c(`Type ${s} + space to pick from list`),!0}if(!l)return c(`err: ${s.slice(1)} is not available.`),!0;let d=i.trim(),p=v=>v.toLowerCase().replace(/[\s_]+/g,"-"),m=p(d),g=l.options.find(v=>p(v.value)===m||p(v.name)===m);if(!g){let v=l.options.filter(ee=>p(ee.value).includes(m)||p(ee.name).includes(m));if(v.length===1)g=v[0];else if(v.length>1)return c(`err: Ambiguous "${i}". Type ${s} + space to see options.`),!0}return g?(l.currentValue=g.value,_(),O(),c(`${l.name} \u2192 ${g.name}`),await K(n.sessionId,a,g.value).catch(()=>{}),!0):(c(`err: Unknown "${i}". Type ${s} + space to see options.`),!0)}case"/notify":{if(typeof Notification>"u")return c("err: notifications not supported in this browser"),!0;let r=i.toLowerCase();if(r==="on"){if(Notification.permission==="denied")return c("notify: blocked \u2014 allow in browser site settings to enable"),!0;if(Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted")return c("notify: blocked \u2014 allow in browser site settings to enable"),!0;let l=await ke();return await vt(),c(l?"notify: already enabled":"notify: enabled"),!0}if(r==="off")return await It(),c("notify: disabled"),!0;let a=Notification.permission;return a==="denied"?c("notify: blocked \u2014 allow in browser site settings to enable"):a==="granted"&&await ke()?c("notify: enabled"):c("notify: off \u2014 use /notify on to enable"),!0}default:return!1}}var Ze=[{cmd:"/cancel",args:"",desc:"Cancel current response"},{cmd:"/exit",args:"",desc:"Close current session"},{cmd:"/mode",args:"[name]",desc:"Pick or switch mode"},{cmd:"/model",args:"[name]",desc:"Pick or switch model"},{cmd:"/new",args:"[cwd]",desc:"New session"},{cmd:"/notify",args:"[on|off]",desc:"Toggle background notifications"},{cmd:"/prune",args:"",desc:"Delete all sessions except current"},{cmd:"/pwd",args:"",desc:"Show working directory"},{cmd:"/rename",args:"<new title>",desc:"Rename current session"},{cmd:"/switch",args:"<title|id>",desc:"Switch to session"},{cmd:"/think",args:"[level]",desc:"Pick or switch reasoning effort"}],bt=[{key:"Enter",desc:"Send message"},{key:"Shift+Enter",desc:"New line"},{key:"^X",desc:"Cancel current response"},{key:"^M",desc:"Cycle mode (Agent \u2192 Plan \u2192 Autopilot)"},{key:"^U",desc:"Upload image"}],b=-1,f=[],w="commands",z=null,$=null,W=null,Ce=!1;function Z(){let e=o.input.value;if(W!==null){if(e===W)return;W=null}let t=e.match(/^\/new /);if(t){let l=e.slice(t[0].length).toLowerCase();Et(l);return}let s=e.match(/^\/switch /);if(s){let l=e.slice(s[0].length).toLowerCase();Tt(l,"switch");return}let i=e.match(/^\/(model|mode|think) /);if(i){let d={model:"model",mode:"mode",think:"reasoning_effort"}[i[1]],p=e.slice(i[0].length).toLowerCase();kt(d,p);return}let r=e.match(/^\/notify /);if(r){let l=e.slice(r[0].length).toLowerCase();xt(l);return}if(!e.startsWith("/")||e.includes(" ")){h();return}w="commands";let a=e.toLowerCase();if(f=Ze.filter(l=>l.cmd.startsWith(a)),f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}async function Tt(e,t="switch"){if(!$)try{$=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{$=null},5e3)}catch{return}if(w=t,f=$.filter(i=>e?i.title&&i.title.toLowerCase().includes(e)||i.id.startsWith(e):!0),f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}async function Et(e){if(!$)try{$=await(await fetch("/api/v1/sessions")).json(),setTimeout(()=>{$=null},5e3)}catch{return}w="new";let t=new Map;for(let i of $){let r=t.get(i.cwd);(!r||(i.last_active_at||i.created_at)>r.time)&&t.set(i.cwd,{cwd:i.cwd,time:i.last_active_at||i.created_at})}let s=[...t.values()].sort((i,r)=>r.time.localeCompare(i.time));if(e&&(s=s.filter(i=>i.cwd.toLowerCase().includes(e))),f=s,f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}function kt(e,t){let s=x(e);if(!s){h();return}if(w="config",z=e,f=s.options.filter(i=>t?i.value.toLowerCase().includes(t)||i.name.toLowerCase().includes(t):!0),f.length===0){h();return}b=0,F(),o.slashMenu.classList.add("active")}var Ct=[{value:"on",name:"on",desc:"Enable background notifications"},{value:"off",name:"off",desc:"Disable background notifications"}];async function xt(e){if(w="notify",f=Ct.filter(i=>e?i.value.includes(e)||i.name.includes(e):!0),f.length===0){h();return}Ce=await ke();let t=Ce?"on":"off",s=f.findIndex(i=>i.value===t);b=s>=0?s:0,F(),o.slashMenu.classList.add("active")}function F(){if(w==="new"){let t=(n.sessionCwd||"").toLowerCase();o.slashMenu.innerHTML=f.map((s,i)=>{let r=s.cwd.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${r?' style="color:var(--green)"':""}>${u((r?"* ":" ")+s.cwd)}</span></div>`}).join("")}else if(w==="config"){let t=ie(z)?.toLowerCase()||"";o.slashMenu.innerHTML=f.map((s,i)=>{let r=s.value.toLowerCase()===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${r?' style="color:var(--green)"':""}>${u((r?"* ":" ")+s.name)}</span></div>`}).join("")}else if(w==="notify"){let t=Ce?"on":"off";o.slashMenu.innerHTML=f.map((s,i)=>{let r=s.value===t;return`<div class="slash-item${i===b?" selected":""}" data-idx="${i}"><span class="slash-cmd"${r?' style="color:var(--green)"':""}>${u((r?"* ":" ")+s.name)}</span><span class="slash-desc">${u(s.desc)}</span></div>`}).join("")}else w==="switch"?o.slashMenu.innerHTML=f.map((t,s)=>{let i=t.id===n.sessionId,r=i?"* ":" ",a=t.title||t.id.slice(0,8)+"\u2026",l=je(t.last_active_at||t.created_at);return`<div class="slash-item${s===b?" selected":""}" data-idx="${s}"><span class="slash-cmd"${i?' style="color:var(--green)"':""}>${u(r+a)}</span><span class="slash-desc">${u(t.cwd)} (${u(l)})</span></div>`}).join(""):o.slashMenu.innerHTML=f.map((t,s)=>{let i=t.args?`${t.cmd} ${t.args}`:t.cmd;return`<div class="slash-item${s===b?" selected":""}" data-idx="${s}"><span class="slash-cmd">${u(i)}</span><span class="slash-desc">${u(t.desc)}</span></div>`}).join("");let e=o.slashMenu.querySelector(".selected");e&&e.scrollIntoView({block:"nearest"})}function h(){o.slashMenu.classList.remove("active"),b=-1,f=[],w="commands",W=o.input.value}function Mt(e){if(!(e<0||e>=f.length)){if(w==="commands"){let t=f[e];o.input.value=t.cmd+(t.args?" ":""),h(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(W=null,Z())}else if(w==="config"){let t=f[e],s={model:"/model",mode:"/mode",reasoning_effort:"/think"}[z]||`/${z}`;o.input.value=`${s} ${t.name}`,h(),o.input.focus()}else if(w==="notify"){let t=f[e];o.input.value=`/notify ${t.value}`,h(),o.input.focus()}else if(w==="new"){let t=f[e];o.input.value=`/new ${t.cwd}`,h(),o.input.focus()}else if(w==="switch"){let t=f[e];o.input.value=`/switch ${t.title||t.id}`,h(),o.input.focus()}L()}}async function Lt(e){if(!(e<0||e>=f.length)){if(w==="new"){let t=f[e];o.input.value="",h(),y(),c("Creating new session\u2026"),M({cwd:t.cwd})}else if(w==="config"){let t=f[e],s=z,i=x(s);o.input.value="",h(),i&&(i.currentValue=t.value),_(),O(),c(`${i?.name||s} \u2192 ${t.name}`),await K(n.sessionId,s,t.value).catch(()=>{})}else if(w==="switch"){let t=f[e];o.input.value="",h(),y(),n.sessionId=null,c("Switching\u2026"),Promise.all([P(t.id),N(t.id)]).then(([s,i])=>{C({type:"session_created",sessionId:s.id,cwd:s.cwd,title:s.title,configOptions:s.configOptions,busyKind:s.busyKind}),i&&S(!0)}).catch(()=>{y(),n.sessionId=null,c("err: Failed to switch session")})}else if(w==="notify"){let t=f[e];o.input.value=`/notify ${t.value}`,h(),xe(o.input.value),o.input.value=""}else{let t=f[e];o.input.value=t.cmd+(t.args?" ":""),h(),o.input.focus(),["/new","/switch","/model","/mode","/think","/notify"].includes(t.cmd)&&(W=null,Z())}L()}}function et(e){return o.slashMenu.classList.contains("active")?e.key==="ArrowDown"?(b=(b+1)%f.length,F(),!0):e.key==="ArrowUp"?(b=(b-1+f.length)%f.length,F(),!0):e.key==="Tab"?(Mt(b),!0):!1:!1}o.slashMenu.addEventListener("mousedown",e=>{e.preventDefault();let t=e.target.closest(".slash-item");t&&Lt(Number(t.dataset.idx))});o.input.addEventListener("input",()=>{Z(),o.inputArea.classList.toggle("bash-mode",o.input.value.startsWith("!"))});function tt(e){return new Promise(t=>{let s=new FileReader;s.onload=()=>{let i=s.result.split(",")[1];t({data:i,mimeType:e.type,previewUrl:s.result})},s.readAsDataURL(e)})}function nt(e){n.pendingImages.push(e),le(),o.input.focus()}function le(){if(o.attachPreview.innerHTML="",n.pendingImages.length===0){o.attachPreview.classList.remove("active");return}o.attachPreview.classList.add("active"),n.pendingImages.forEach((e,t)=>{let s=document.createElement("span");s.className="attach-thumb",s.innerHTML=`<img src="${e.previewUrl}"><button class="remove">\xD7</button>`,s.querySelector(".remove").addEventListener("click",()=>{n.pendingImages.splice(t,1),le()}),o.attachPreview.appendChild(s)})}o.attachBtn.onclick=()=>o.fileInput.click();o.fileInput.onchange=async()=>{for(let e of o.fileInput.files)e.type.startsWith("image/")&&nt(await tt(e));o.fileInput.value=""};o.input.addEventListener("paste",async e=>{for(let t of e.clipboardData.items)t.type.startsWith("image/")&&(e.preventDefault(),nt(await tt(t.getAsFile())))});function Me(){return n.clientId!==null}n._onCancelTimeout=()=>c("warn: Agent not responding to cancel");function st(){let e=o.input.value.trim();if(!e&&n.pendingImages.length===0)return;if((e.startsWith("/")||e==="?"||e.startsWith("? "))&&n.pendingImages.length===0){o.input.value="",o.input.style.height="auto",L(),Le(),xe(e);return}if(e.startsWith("!")&&n.pendingImages.length===0){let i=e.slice(1).trim();if(!i)return;if(!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!Me()){c("warn: Not connected, please retry");return}o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),L(),Q(i,!0),n.sentBashForSession=n.sessionId,qe(n.sessionId,i).catch(()=>{}),I(!0);return}if(n.busy)return;if(o.input.value="",o.input.style.height="auto",o.inputArea.classList.remove("bash-mode"),L(),!n.sessionId){c("warn: Session not ready yet, please wait\u2026");return}if(!Me()){c("warn: Not connected, please retry");return}let t=R("user",e||"(image)");for(let i of n.pendingImages){let r=document.createElement("img");r.className="user-image",r.src=i.previewUrl,t.appendChild(r)}let s=n.pendingImages.slice();n.pendingImages.length=0,le(),s.length>0?Promise.all(s.map(i=>fetch(`/api/v1/sessions/${n.sessionId}/images`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({data:i.data,mimeType:i.mimeType})}).then(r=>r.json()).then(r=>({data:i.data,mimeType:i.mimeType,path:r.url})))).then(i=>{if(!Me()){t.remove(),c("warn: Not connected, please retry"),I(!1);return}ue(n.sessionId,e||"What is in this image?",i.map(r=>({data:r.data,mimeType:r.mimeType,path:r.path}))).catch(()=>{})}):ue(n.sessionId,e).catch(()=>{}),n.turnEnded=!1,n.sentMessageForSession=n.sessionId,I(!0),Ue()}function it(){G()&&c("^X")}function ot(){let e=o.input.value.trim();return e.startsWith("/")||e.startsWith("!")||e==="?"||e.startsWith("? ")}function Le(){n.busy&&(ot()?(o.sendBtn.textContent="\u21B5",o.sendBtn.title="Send (Enter)",o.sendBtn.classList.remove("cancel")):(o.sendBtn.textContent="^X",o.sendBtn.title="Cancel (Ctrl+X)",o.sendBtn.classList.add("cancel")))}o.sendBtn.onclick=()=>{n.busy&&!ot()?it():st()};o.input.addEventListener("keydown",e=>{if(et(e)){e.preventDefault();return}if(e.key==="Enter"&&!e.shiftKey){e.preventDefault(),h(),st();return}if(e.key==="u"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey){e.preventDefault(),o.fileInput.click();return}});document.addEventListener("keydown",e=>{if(e.key==="x"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&n.busy){e.preventDefault(),it();return}e.key==="Escape"&&o.slashMenu.classList.contains("active")&&(e.preventDefault(),h(),o.input.focus())});function rt(){let e=x("mode");if(!e||!e.options.length)return;let t=e.options.findIndex(i=>i.value===e.currentValue),s=e.options[(t+1)%e.options.length];e.currentValue=s.value,K(n.sessionId,"mode",s.value).catch(()=>{}),c(`Mode \u2192 ${s.name}`),_()}document.addEventListener("keydown",e=>{e.key==="m"&&(e.ctrlKey||e.metaKey)&&!e.shiftKey&&(e.preventDefault(),rt())});o.prompt.addEventListener("click",rt);o.newBtn.addEventListener("click",()=>{o.input.value="/new ",L(),Le(),Z(),o.input.focus()});o.input.addEventListener("input",()=>{L(),Le()});o.input.addEventListener("focus",L);o.input.addEventListener("input",()=>{o.input.style.height="auto",o.input.style.height=Math.min(o.input.scrollHeight,200)+"px"});async function $t(e){try{let t=await navigator.serviceWorker?.ready;if(!t)return;let s=await t.pushManager.getSubscription();if(!s)return;await fetch("/api/v1/push/register-client",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({clientId:e,endpoint:s.endpoint})})}catch{}}function Pe(){B("connecting","connecting");let e=new EventSource("/api/v1/events/stream");n.eventSource=e,e.onmessage=async t=>{let s=JSON.parse(t.data);if(s.type==="connected"){n.clientId=s.clientId,V(s.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),$t(s.clientId);return}C(s)},e.onerror=()=>{e.close(),Ht(),setTimeout(Pe,3e3)},Pt()}async function Pt(){B("connecting","session loading");let e=n.sessionSwitchGen,t=Re();if(t&&t===n.sessionId&&n.lastEventSeq>0){if(await $e(t,!0,e),e!==n.sessionSwitchGen)return;Xe(),S(!1);return}if(t){if(y(),await $e(t,!1,e),e!==n.sessionSwitchGen)return;S(!0);return}try{let s=await te();if(e!==n.sessionSwitchGen)return;if(s.length>0){if(y(),await $e(s[0].id,!1,e),e!==n.sessionSwitchGen)return;S(!0);return}}catch{}e===n.sessionSwitchGen&&M()}async function $e(e,t,s){if(t){try{let i=await P(e);if(s!==n.sessionSwitchGen)return;C({type:"session_created",sessionId:i.id,cwd:i.cwd,title:i.title,configOptions:i.configOptions,busyKind:i.busyKind})}catch{if(s!==n.sessionSwitchGen)return;y(),c("warn: Previous session expired, created new one."),M();return}if(s!==n.sessionSwitchGen)return;await be(e)}else{n.sessionId=null;let i=N(e),r;try{let[a,l]=await Promise.all([P(e),i]);if(s!==n.sessionSwitchGen)return;r=a,l||c("warn: Failed to load history.")}catch{if(s!==n.sessionSwitchGen)return;y(),c("warn: Previous session expired, created new one."),M();return}C({type:"session_created",sessionId:r.id,cwd:r.cwd,title:r.title,configOptions:r.configOptions,busyKind:r.busyKind})}}function Ht(){B("disconnected","disconnected"),n.eventSource=null,n.clientId=null,k(),H(),n.currentBashEl&&Y(n.currentBashEl,null,"disconnected"),n.pendingToolCallIds.clear(),n.pendingPermissionRequestIds.clear(),n.pendingPromptDone=!1,n.turnEnded=!1,J(),I(!1)}document.addEventListener("visibilitychange",()=>{n.clientId&&V(n.clientId,!document.hidden,n.sessionId??void 0).catch(()=>{}),!document.hidden&&n.sessionId&&n.lastEventSeq>0&&!n.replayInProgress&&be(n.sessionId).then(()=>S(!1))});Pe();"serviceWorker"in navigator&&(navigator.serviceWorker.register("/sw.js"),navigator.serviceWorker.addEventListener("message",e=>{if(e.data?.type==="navigate"&&e.data.sessionId){let t=e.data.sessionId;if(n.sessionId===t)return;n.sessionSwitchGen++;let s=n.sessionSwitchGen;history.replaceState(null,"",`#${t}`),y(),n.sessionId=null,c("Switching\u2026"),Promise.all([P(t),N(t)]).then(([i,r])=>{s===n.sessionSwitchGen&&(C({type:"session_created",sessionId:i.id,cwd:i.cwd,title:i.title,configOptions:i.configOptions,busyKind:i.busyKind}),r&&S(!0))}).catch(()=>{y(),n.sessionId=null,c("err: Failed to switch session")})}}));