@openeryc/pi-coding-agent 0.75.24 → 0.75.25

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/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.75.25] - 2026-05-22
4
+
3
5
  ## [0.75.24] - 2026-05-22
4
6
 
5
7
  ## [0.75.23] - 2026-05-22
@@ -1 +1 @@
1
- {"version":3,"file":"web-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AA2O/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgJ5E","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;background:#0d1117;color:#e6edf3;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:44px;background:#161b22;border-bottom:1px solid #21262d;flex-shrink:0}\n#header h1{font-size:14px;font-weight:600;color:#f0f6fc}\n#header sub{font-size:10px;color:#8b949e;margin-left:6px}\n#header-right{display:flex;gap:14px;font-size:11px;color:#8b949e}\n#msgs{flex:1;overflow-y:auto;padding:20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:18px}\n#msgs::-webkit-scrollbar{width:5px}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-row{animation:fadeIn .15s ease}\n@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}\n.msg-row.user{align-self:flex-end;max-width:80%}\n.msg-row.assistant{align-self:flex-start;max-width:86%}\n.msg-row.system{align-self:center;max-width:90%}\n.msg-avatar{width:26px;height:26px;border-radius:6px;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;margin-bottom:4px}\n.msg-avatar.user{background:#1f6feb33;color:#58a6ff;margin-left:auto}\n.msg-avatar.assistant{background:#30363d;color:#8b949e}\n.msg-bubble{border-radius:8px;font-size:13.5px;line-height:1.55;padding:10px 14px;overflow-wrap:break-word}\n.msg-bubble.user{background:#1f6feb;color:#fff;border-bottom-right-radius:3px}\n.msg-bubble.assistant{background:#161b22;color:#c9d1d9;border:1px solid #30363d;border-bottom-left-radius:3px}\n.msg-bubble.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;padding:4px 0;border:none}\n.msg-bubble code{font-family:\"JetBrains Mono\",monospace;font-size:12px;background:#0d1117;border:1px solid #30363d;padding:2px 6px;border-radius:4px;color:#d2a8ff}\n.msg-bubble pre{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.45;font-family:\"JetBrains Mono\",monospace;color:#c9d1d9}\n.msg-bubble pre code{background:transparent;border:none;padding:0;color:inherit}\n.msg-bubble strong{color:#e6edf3;font-weight:600}\n.msg-bubble em{font-style:italic}\n.msg-bubble h1,.msg-bubble h2,.msg-bubble h3{margin:6px 0 3px;font-weight:600}\n.msg-bubble h1{font-size:17px;border-bottom:1px solid #21262d;padding-bottom:3px}\n.msg-bubble h2{font-size:14px}\n.msg-bubble h3{font-size:13px;color:#8b949e}\n.msg-bubble ul,.msg-bubble ol{padding-left:20px;margin:4px 0}\n.msg-bubble li{margin:2px 0}\n.msg-bubble hr{border:none;border-top:1px solid #21262d;margin:8px 0}\n.msg-bubble blockquote{border-left:3px solid #30363d;padding:4px 10px;color:#8b949e;margin:6px 0}\n.tool-wrapper{margin:4px 0}\n.tool{background:#161b22;border:1px solid #21262d;border-radius:6px;overflow:hidden;transition:box-shadow .15s, border-color .15s}\n.tool:hover{border-color:#30363d}\n.tool.active{box-shadow:0 0 0 1px rgba(210,153,34,.25)}\n.tool.error{border-color:#490202;background:#161b22}\n.tool-bar{display:flex;align-items:center;padding:7px 10px;cursor:pointer;gap:8px;font-size:12px;user-select:none}\n.tool-bar:hover{background:#1c2128}\n.tool-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:#30363d}\n.tool-dot.running{background:#d29922;animation:pulse 1.2s ease-in-out infinite}\n.tool-dot.ok{background:#3fb950}\n.tool-dot.err{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.3;transform:scale(.9)}50%{opacity:1;transform:scale(1.2)}}\n.tool-name{font-weight:600;color:#e6edf3;white-space:nowrap}\n.tool-args{color:#8b949e;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-chev{color:#484f58;font-size:9px;transition:.15s}\n.tool.open .tool-chev{transform:rotate(90deg)}\n.tool-content{display:none;padding:8px 12px;border-top:1px solid #21262d;font-family:\"JetBrains Mono\",monospace;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:36vh;overflow-y:auto;color:#c9d1d9;background:#0d1117}\n.tool.open .tool-content{display:block}\n.tool-content::-webkit-scrollbar{width:4px;height:4px}\n.tool-content::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.thinking{padding:5px 12px;border-left:2px solid #21262d;font-size:12px;color:#6e7681;font-style:italic;overflow:hidden;text-overflow:ellipsis}\n#input-area{background:#161b22;border-top:1px solid #21262d;padding:10px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#e6edf3;font-size:13px;font-family:inherit;outline:none;transition:border-color .15s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#238636;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;font-weight:600;cursor:pointer;transition:background .1s;flex-shrink:0}\nbutton:hover{background:#2ea043}\nbutton:disabled{background:#21262d;color:#484f58;cursor:not-allowed}\n.spin{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spin.on{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:640px){\n#msgs{padding:12px;gap:12px}\n.msg-row.user{max-width:92%}.msg-row.assistant{max-width:95%}\n#input-area{padding:8px 12px}\n#prompt{padding:7px 10px}button{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\"><h1>pi<sub id=\"session-info\"></sub></h1><div id=\"header-right\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spin\" id=\"spin\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spin=document.getElementById(\"spin\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,tools={},curAsst=null;\n\nfunction scroll(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return(s+\"\").replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction md(s){\nlet t=esc(s);\nconst blks=[];\nt=t.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blks.length-1)+'\\\\x00B'});\nt=t.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\nt=t.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\nt=t.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\nt=t.replace(/^### (.+$)/gm,'<h3>$1</h3>');\nt=t.replace(/^## (.+$)/gm,'<h2>$1</h2>');\nt=t.replace(/^# (.+$)/gm,'<h1>$1</h1>');\nt=t.replace(/^- (.+$)/gm,'<li>$1</li>');\nt=t.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\nt=t.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\nt=t.replace(/(?:^---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\nt=t.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blks[parseInt(i)]);\nt=t.replace(/\\\\n\\\\n/g,'<br><br>');\nt=t.replace(/\\\\n/g,'<br>');\nreturn t\n}\n\nfunction row(role){const r=document.createElement(\"div\");r.className=\"msg-row \"+role;msgs.appendChild(r);return r}\nfunction bubble(row,inner){const b=document.createElement(\"div\");b.className=\"msg-bubble \"+row.classList[1];b.innerHTML=inner;row.appendChild(b);return b}\nfunction avatar(row,lbl){if(row.classList.contains(\"system\"))return;const a=document.createElement(\"div\");a.className=\"msg-avatar \"+row.classList[1];a.textContent=lbl;row.appendChild(a)}\nfunction addUser(text){const r=row(\"user\");avatar(r,\"Y\");bubble(r,esc(text));scroll();return r}\nfunction addAsst(){curAsst=row(\"assistant\");avatar(curAsst,\"P\");const b=bubble(curAsst,\"\");scroll();return b}\nfunction getAsst(){if(!curAsst)return addAsst();return curAsst.querySelector(\".msg-bubble\")}\nfunction addSys(text){const r=row(\"system\");bubble(r,text);scroll()}\n\nfunction renderTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool-wrapper\";\nconst argText=e.args?(\" \"+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,60))):\"\";\nel.innerHTML='<div class=\"tool active\" id=\"t'+e.toolCallId+'\"><div class=\"tool-bar\"><span class=\"tool-dot running\"></span><span class=\"tool-name\">'+esc(e.toolName)+'</span><span class=\"tool-args\">'+argText+'</span><span class=\"tool-chev\">&#9654;</span></div><div class=\"tool-content\"></div></div>';\nel.querySelector(\".tool-bar\").onclick=()=>{el.querySelector(\".tool\").classList.toggle(\"open\")};\nmsgs.appendChild(el);tools[e.toolCallId]=el;scroll()}\n\nfunction updateTool(id,result,isError){\nconst t=tools[id];if(!t)return;\nconst ct=t.querySelector(\".tool-content\");\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){ct.textContent=texts.join(\"\\\\n\");t.querySelector(\".tool\").classList.add(\"open\")}\nif(isError){t.querySelector(\".tool-dot\").className=\"tool-dot err\";t.querySelector(\".tool\").classList.add(\"error\");t.querySelector(\".tool\").classList.remove(\"active\")}\n}\n\nfunction doneTool(id,isError){\nconst t=tools[id];if(!t)return;\nconst dot=t.querySelector(\".tool-dot\");dot.className=\"tool-dot \"+(isError?\"err\":\"ok\");\nt.querySelector(\".tool\").classList.remove(\"active\");\nif(isError){t.querySelector(\".tool\").classList.add(\"error\")}\nif(isError&&!t.querySelector(\".tool-content\").textContent.trim())t.querySelector(\".tool-content\").textContent=\"(no output)\"\n}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAsst=null;break;\ncase\"text_delta\":{const ad=getAsst();ad.innerHTML+=md(d.delta);scroll();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"th\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"th\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scroll();break}\ncase\"tool_execution_start\":renderTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":doneTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"Done\";if(d.usage){s+=\" &middot; \"+fmt(d.usage.input)+\" in / \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" &middot; $\"+d.usage.cost.total.toFixed(4)}addSys(s);break}\ncase\"compaction\":addSys(\"Compacting...\");break;\ncase\"error\":addSys(\"Error: \"+esc(d.message));break;\ndefault:break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addUser(text);busy=true;send.disabled=true;spin.className=\"spin on\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addSys(\"Error: HTTP \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addSys(\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spin.className=\"spin\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{for(const e of events){if(e.type===\"user\")addUser(e.text);else handle(e)}}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{statSessions.textContent=s.sessions+\" sessions\";statCost.textContent=\"$\"+s.cost.toFixed(2)}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{sessionInfo.textContent=s.id?s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\"):\"\"}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
1
+ {"version":3,"file":"web-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAyO/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgJ5E","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:15px}\nbody{font-family:\"IBM Plex Mono\",\"JetBrains Mono\",\"Fira Code\",\"SF Mono\",monospace;background:#1a1815;color:#d4b872;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#topbar{display:flex;align-items:center;justify-content:space-between;padding:6px 16px;background:#141310;border-bottom:2px solid #3a3528;flex-shrink:0}\n#topbar h1{font-size:13px;font-weight:700;color:#e8cf8a;letter-spacing:1px}\n#topbar h1::before{content:\"[ \";color:#5c5240}#topbar h1::after{content:\" ]\";color:#5c5240}\n#topbar aside{font-size:10px;color:#5c5240}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:12px;background:linear-gradient(180deg,#1a1815 0%,#171512 100%)}\n#msgs::-webkit-scrollbar{width:5px}\n#msgs::-webkit-scrollbar-thumb{background:#3a3528;border-radius:0}\n.row{animation:fadeIn .12s ease}\n.row.user{display:flex;flex-direction:column;align-items:flex-end;max-width:82%;align-self:flex-end}\n.row.ast{display:flex;flex-direction:column;align-items:flex-start;max-width:88%;align-self:flex-start;width:100%}\n.row.sys{display:flex;justify-content:center;max-width:100%;align-self:center}\n@keyframes fadeIn{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}\n.rolename{font-size:10px;text-transform:uppercase;letter-spacing:1px;margin-bottom:2px;font-weight:700}\n.row.user .rolename{color:#9e8a5e}\n.row.ast .rolename{color:#5c5240}\n.bubble{border:1px solid #3a3528;padding:10px 14px;font-size:13px;line-height:1.65;overflow-wrap:break-word;word-break:break-word}\n.bubble.user{background:#2a251c;color:#e8cf8a;max-width:100%}\n.bubble.ast{background:#141310;color:#c4a860;max-width:100%}\n.bubble.sys{background:none;border:none;color:#5c5240;font-size:11px;text-align:center;padding:2px 0}\n.bubble code{background:#111;border:1px solid #3a3528;padding:1px 5px;color:#d4b872;font-size:12px}\n.bubble pre{background:#111;border:1px solid #3a3528;padding:10px 12px;overflow-x:auto;font-size:12px;line-height:1.5;color:#c4a860;margin:6px 0}\n.bubble pre code{background:none;border:none;padding:0;font-size:inherit;color:inherit}\n.bubble strong{color:#e8cf8a;font-weight:700}\n.bubble em{color:#c4a860;font-style:italic}\n.bubble h1,.bubble h2,.bubble h3{font-weight:700;color:#e8cf8a;margin:8px 0 3px}\n.bubble h1{font-size:15px;border-bottom:1px solid #3a3528;padding-bottom:3px}\n.bubble h2{font-size:14px}\n.bubble h3{font-size:13px;color:#c4a860}\n.bubble ul,.bubble ol{padding-left:22px;margin:4px 0}\n.bubble li{margin:2px 0}\n.bubble hr{border:none;border-top:1px solid #3a3528;margin:8px 0}\n.bubble blockquote{border-left:3px solid #3a3528;padding:4px 10px;color:#8e8048;margin:4px 0}\n.tool{border:1px solid #3a3528;margin:2px 0}\n.tool.running{border-color:#906820}\n.tool.error{border-color:#6b2020}\n.tool-bar{display:flex;align-items:center;padding:6px 10px;cursor:pointer;gap:8px;font-size:12px;user-select:none;background:#141310}\n.tool-bar:hover{background:#1a1815}\n.tool-dot{width:7px;height:7px;flex-shrink:0;background:#3a3528}\n.tool-dot.running{background:#c48c22;animation:blink 1s step-end infinite}\n.tool-dot.ok{background:#689040}\n.tool-dot.err{background:#b84040}\n@keyframes blink{50%{opacity:.2}}\n.tool-kind{font-weight:700;color:#c4a860}\n.tool-args{color:#5c5240;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-chev{color:#5c5240;font-size:10px;transition:.12s}\n.tool.open .tool-chev{transform:rotate(90deg)}\n.tool-content{display:none;padding:8px 12px;border-top:1px solid #3a3528;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:32vh;overflow-y:auto;color:#8e8048;background:#111}\n.tool.open .tool-content{display:block}\n.tool-content::-webkit-scrollbar{width:4px;height:4px}\n.tool-content::-webkit-scrollbar-thumb{background:#3a3528}\n#input-area{border-top:2px solid #3a3528;background:#141310;padding:10px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#1a1815;border:1px solid #3a3528;padding:8px 12px;color:#d4b872;font-size:13px;font-family:inherit;outline:none;transition:border .15s}\n#prompt:focus{border-color:#c48c22;box-shadow:0 0 6px rgba(196,140,34,.15)}\n#prompt::placeholder{color:#3a3528}\nbutton{background:#3a3528;color:#c4a860;border:1px solid #5c5240;padding:8px 18px;font-size:12px;font-family:inherit;font-weight:700;cursor:pointer;transition:.1s;flex-shrink:0;text-transform:uppercase;letter-spacing:1px}\nbutton:hover{background:#5c5240;color:#e8cf8a;border-color:#8e8048}\nbutton:disabled{opacity:.3;cursor:not-allowed}\n.spin{display:none;width:14px;height:14px;border:2px solid #3a3528;border-top-color:#c48c22;border-radius:50%;animation:spinner .5s linear infinite}\n.spin.on{display:inline-block}\n@keyframes spinner{to{transform:rotate(360deg)}}\n@media(max-width:600px){\n#msgs{padding:10px;gap:8px}\n.row.user{max-width:94%}.row.ast{max-width:96%}\n#input-area{padding:8px 10px}\n}\n</style>\n</head>\n<body>\n<div id=\"topbar\"><h1>pi</h1><aside><span id=\"sid\"></span> &nbsp; <span id=\"stat-sessions\"></span> &nbsp; <span id=\"stat-cost\"></span></aside></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\">_\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spin\" id=\"spin\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spin\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsid=document.getElementById(\"sid\");\nlet busy=false,tools={},curAsst=null;\n\nfunction scroll(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(s==null)return\"\";return(\"\"+s).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}\nfunction fmt(n){if(!n)return\"0\";if(n<1e3)return\"\"+n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction argText(args){if(!args)return\"\";const v=args.command||args.path||args.file_path;return v?\" \"+esc(v).slice(0,80):\"\"}\n\nfunction md(s){\nlet t=esc(s||\"\");\nconst blocks=[];\nt=t.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\nt=t.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\nt=t.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\nt=t.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\nt=t.replace(/^### (.+)$/gm,'<h3>$1</h3>');\nt=t.replace(/^## (.+)$/gm,'<h2>$1</h2>');\nt=t.replace(/^# (.+)$/gm,'<h1>$1</h1>');\nt=t.replace(/^- (.+)$/gm,'<li>$1</li>');\nt=t.replace(/^> (.+)$/gm,'<blockquote>$1</blockquote>');\nt=t.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\nt=t.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\nt=t.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\nt=t.replace(/\\\\n\\\\n/g,'<br><br>');\nt=t.replace(/\\\\n/g,'<br>');\nreturn t\n}\n\nfunction row(role){const d=document.createElement(\"div\");d.className=\"row \"+role;msgs.appendChild(d);return d}\nfunction rol(role,lbl){const d=row(role);const n=document.createElement(\"div\");n.className=\"rolename\";n.textContent=lbl;d.appendChild(n);return d}\nfunction bubble(parent,html){const b=document.createElement(\"div\");b.className=\"bubble \"+parent.classList[1];if(html!==undefined)b.innerHTML=html;parent.appendChild(b);return b}\n\nfunction addUser(txt){const r=rol(\"user\",\"YOU\");bubble(r,esc(txt));scroll()}\nfunction addAst(){const r=rol(\"ast\",\"PI\");const b=bubble(r);scroll();curAsst={row:r,bubble:b};return b}\nfunction addSys(txt){const r=row(\"sys\");bubble(r,txt);scroll()}\nfunction getAsstB(){if(!curAsst)return addAst();return curAsst.bubble}\n\nfunction renderTool(e){\nconst r=row(\"ast\");const w=document.createElement(\"div\");w.className=\"tool-wrapper\";\nw.innerHTML='<div class=\"tool running\" id=\"t'+e.toolCallId+'\"><div class=\"tool-bar\"><span class=\"tool-dot running\"></span><span class=\"tool-kind\">'+esc(e.toolName)+'</span><span class=\"tool-args\">'+argText(e.args)+'</span><span class=\"tool-chev\">&#9654;</span></div><div class=\"tool-content\"></div></div>';\nw.querySelector(\".tool-bar\").onclick=()=>w.querySelector(\".tool\").classList.toggle(\"open\");\nr.appendChild(w);msgs.appendChild(r);tools[e.toolCallId]={el:w.querySelector(\".tool\"),row:r};scroll()}\n\nfunction updateTool(id,result,isError){\nconst t=tools[id];if(!t)return;\nconst ct=t.el.querySelector(\".tool-content\");\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){ct.textContent=texts.join(\"\\\\n\");t.el.classList.add(\"open\")}\nif(isError){t.el.querySelector(\".tool-dot\").className=\"tool-dot err\";t.el.classList.add(\"error\");t.el.classList.remove(\"running\")}\n}\n\nfunction doneTool(id,isError){\nconst t=tools[id];if(!t)return;\nt.el.querySelector(\".tool-dot\").className=\"tool-dot \"+(isError?\"err\":\"ok\");\nt.el.classList.remove(\"running\");\nif(isError){t.el.classList.add(\"error\")}\nif(isError&&!t.el.querySelector(\".tool-content\").textContent.trim())t.el.querySelector(\".tool-content\").textContent=\"(no output)\"\n}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAsst=null;break;\ncase\"text_delta\":{const b=getAsstB();b.innerHTML+=md(d.delta);scroll();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"th\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"row ast\";const b=document.createElement(\"div\");b.style.cssText=\"font-size:11px;color:#5c5240;font-style:italic;padding:4px 10px;border-left:2px solid #3a3528\";b.id=\"th\"+d.contentIndex;th.appendChild(b);msgs.appendChild(th)}th.firstChild.textContent+=d.delta;scroll();break}\ncase\"tool_execution_start\":renderTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":doneTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"DONE\";if(d.usage){s+=\" \"+fmt(d.usage.input)+\" IN / \"+fmt(d.usage.output)+\" OUT\";if(d.usage.cost)s+=\" $\"+d.usage.cost.total.toFixed(4)}addSys(s);break}\ncase\"compaction\":addSys(\"--- COMPACTING ---\");break;\ncase\"error\":addSys(\"ERR: \"+esc(d.message||\"\"));break;\ndefault:break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addUser(text);busy=true;send.disabled=true;spinner.className=\"spin on\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addSys(\"HTTP \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addSys(\"ERR: \"+esc(err.message||\"\"))}finally{busy=false;send.disabled=false;spinner.className=\"spin\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{for(const e of events){if(e.type===\"user\")addUser(e.text);else handle(e)}}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{statSessions.textContent=s.sessions+\" sessions\";statCost.textContent=s.cost.toFixed(2)}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{sid.textContent=s.id?s.id.slice(0,8)+(s.name?\" \"+s.name:\"\"):\"\"}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
@@ -51,178 +51,176 @@ const HTML = `<!DOCTYPE html>
51
51
  <title>pi</title>
52
52
  <style>
53
53
  *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
54
- html{font-size:14px}
55
- body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#0d1117;color:#e6edf3;display:flex;flex-direction:column;height:100dvh;overflow:hidden}
56
- #header{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:44px;background:#161b22;border-bottom:1px solid #21262d;flex-shrink:0}
57
- #header h1{font-size:14px;font-weight:600;color:#f0f6fc}
58
- #header sub{font-size:10px;color:#8b949e;margin-left:6px}
59
- #header-right{display:flex;gap:14px;font-size:11px;color:#8b949e}
60
- #msgs{flex:1;overflow-y:auto;padding:20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:18px}
54
+ html{font-size:15px}
55
+ body{font-family:"IBM Plex Mono","JetBrains Mono","Fira Code","SF Mono",monospace;background:#1a1815;color:#d4b872;display:flex;flex-direction:column;height:100dvh;overflow:hidden}
56
+ #topbar{display:flex;align-items:center;justify-content:space-between;padding:6px 16px;background:#141310;border-bottom:2px solid #3a3528;flex-shrink:0}
57
+ #topbar h1{font-size:13px;font-weight:700;color:#e8cf8a;letter-spacing:1px}
58
+ #topbar h1::before{content:"[ ";color:#5c5240}#topbar h1::after{content:" ]";color:#5c5240}
59
+ #topbar aside{font-size:10px;color:#5c5240}
60
+ #msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:12px;background:linear-gradient(180deg,#1a1815 0%,#171512 100%)}
61
61
  #msgs::-webkit-scrollbar{width:5px}
62
- #msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}
63
- .msg-row{animation:fadeIn .15s ease}
64
- @keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
65
- .msg-row.user{align-self:flex-end;max-width:80%}
66
- .msg-row.assistant{align-self:flex-start;max-width:86%}
67
- .msg-row.system{align-self:center;max-width:90%}
68
- .msg-avatar{width:26px;height:26px;border-radius:6px;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;margin-bottom:4px}
69
- .msg-avatar.user{background:#1f6feb33;color:#58a6ff;margin-left:auto}
70
- .msg-avatar.assistant{background:#30363d;color:#8b949e}
71
- .msg-bubble{border-radius:8px;font-size:13.5px;line-height:1.55;padding:10px 14px;overflow-wrap:break-word}
72
- .msg-bubble.user{background:#1f6feb;color:#fff;border-bottom-right-radius:3px}
73
- .msg-bubble.assistant{background:#161b22;color:#c9d1d9;border:1px solid #30363d;border-bottom-left-radius:3px}
74
- .msg-bubble.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;padding:4px 0;border:none}
75
- .msg-bubble code{font-family:"JetBrains Mono",monospace;font-size:12px;background:#0d1117;border:1px solid #30363d;padding:2px 6px;border-radius:4px;color:#d2a8ff}
76
- .msg-bubble pre{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.45;font-family:"JetBrains Mono",monospace;color:#c9d1d9}
77
- .msg-bubble pre code{background:transparent;border:none;padding:0;color:inherit}
78
- .msg-bubble strong{color:#e6edf3;font-weight:600}
79
- .msg-bubble em{font-style:italic}
80
- .msg-bubble h1,.msg-bubble h2,.msg-bubble h3{margin:6px 0 3px;font-weight:600}
81
- .msg-bubble h1{font-size:17px;border-bottom:1px solid #21262d;padding-bottom:3px}
82
- .msg-bubble h2{font-size:14px}
83
- .msg-bubble h3{font-size:13px;color:#8b949e}
84
- .msg-bubble ul,.msg-bubble ol{padding-left:20px;margin:4px 0}
85
- .msg-bubble li{margin:2px 0}
86
- .msg-bubble hr{border:none;border-top:1px solid #21262d;margin:8px 0}
87
- .msg-bubble blockquote{border-left:3px solid #30363d;padding:4px 10px;color:#8b949e;margin:6px 0}
88
- .tool-wrapper{margin:4px 0}
89
- .tool{background:#161b22;border:1px solid #21262d;border-radius:6px;overflow:hidden;transition:box-shadow .15s, border-color .15s}
90
- .tool:hover{border-color:#30363d}
91
- .tool.active{box-shadow:0 0 0 1px rgba(210,153,34,.25)}
92
- .tool.error{border-color:#490202;background:#161b22}
93
- .tool-bar{display:flex;align-items:center;padding:7px 10px;cursor:pointer;gap:8px;font-size:12px;user-select:none}
94
- .tool-bar:hover{background:#1c2128}
95
- .tool-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:#30363d}
96
- .tool-dot.running{background:#d29922;animation:pulse 1.2s ease-in-out infinite}
97
- .tool-dot.ok{background:#3fb950}
98
- .tool-dot.err{background:#f85149}
99
- @keyframes pulse{0%,100%{opacity:.3;transform:scale(.9)}50%{opacity:1;transform:scale(1.2)}}
100
- .tool-name{font-weight:600;color:#e6edf3;white-space:nowrap}
101
- .tool-args{color:#8b949e;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
102
- .tool-chev{color:#484f58;font-size:9px;transition:.15s}
62
+ #msgs::-webkit-scrollbar-thumb{background:#3a3528;border-radius:0}
63
+ .row{animation:fadeIn .12s ease}
64
+ .row.user{display:flex;flex-direction:column;align-items:flex-end;max-width:82%;align-self:flex-end}
65
+ .row.ast{display:flex;flex-direction:column;align-items:flex-start;max-width:88%;align-self:flex-start;width:100%}
66
+ .row.sys{display:flex;justify-content:center;max-width:100%;align-self:center}
67
+ @keyframes fadeIn{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}
68
+ .rolename{font-size:10px;text-transform:uppercase;letter-spacing:1px;margin-bottom:2px;font-weight:700}
69
+ .row.user .rolename{color:#9e8a5e}
70
+ .row.ast .rolename{color:#5c5240}
71
+ .bubble{border:1px solid #3a3528;padding:10px 14px;font-size:13px;line-height:1.65;overflow-wrap:break-word;word-break:break-word}
72
+ .bubble.user{background:#2a251c;color:#e8cf8a;max-width:100%}
73
+ .bubble.ast{background:#141310;color:#c4a860;max-width:100%}
74
+ .bubble.sys{background:none;border:none;color:#5c5240;font-size:11px;text-align:center;padding:2px 0}
75
+ .bubble code{background:#111;border:1px solid #3a3528;padding:1px 5px;color:#d4b872;font-size:12px}
76
+ .bubble pre{background:#111;border:1px solid #3a3528;padding:10px 12px;overflow-x:auto;font-size:12px;line-height:1.5;color:#c4a860;margin:6px 0}
77
+ .bubble pre code{background:none;border:none;padding:0;font-size:inherit;color:inherit}
78
+ .bubble strong{color:#e8cf8a;font-weight:700}
79
+ .bubble em{color:#c4a860;font-style:italic}
80
+ .bubble h1,.bubble h2,.bubble h3{font-weight:700;color:#e8cf8a;margin:8px 0 3px}
81
+ .bubble h1{font-size:15px;border-bottom:1px solid #3a3528;padding-bottom:3px}
82
+ .bubble h2{font-size:14px}
83
+ .bubble h3{font-size:13px;color:#c4a860}
84
+ .bubble ul,.bubble ol{padding-left:22px;margin:4px 0}
85
+ .bubble li{margin:2px 0}
86
+ .bubble hr{border:none;border-top:1px solid #3a3528;margin:8px 0}
87
+ .bubble blockquote{border-left:3px solid #3a3528;padding:4px 10px;color:#8e8048;margin:4px 0}
88
+ .tool{border:1px solid #3a3528;margin:2px 0}
89
+ .tool.running{border-color:#906820}
90
+ .tool.error{border-color:#6b2020}
91
+ .tool-bar{display:flex;align-items:center;padding:6px 10px;cursor:pointer;gap:8px;font-size:12px;user-select:none;background:#141310}
92
+ .tool-bar:hover{background:#1a1815}
93
+ .tool-dot{width:7px;height:7px;flex-shrink:0;background:#3a3528}
94
+ .tool-dot.running{background:#c48c22;animation:blink 1s step-end infinite}
95
+ .tool-dot.ok{background:#689040}
96
+ .tool-dot.err{background:#b84040}
97
+ @keyframes blink{50%{opacity:.2}}
98
+ .tool-kind{font-weight:700;color:#c4a860}
99
+ .tool-args{color:#5c5240;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
100
+ .tool-chev{color:#5c5240;font-size:10px;transition:.12s}
103
101
  .tool.open .tool-chev{transform:rotate(90deg)}
104
- .tool-content{display:none;padding:8px 12px;border-top:1px solid #21262d;font-family:"JetBrains Mono",monospace;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:36vh;overflow-y:auto;color:#c9d1d9;background:#0d1117}
102
+ .tool-content{display:none;padding:8px 12px;border-top:1px solid #3a3528;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:32vh;overflow-y:auto;color:#8e8048;background:#111}
105
103
  .tool.open .tool-content{display:block}
106
104
  .tool-content::-webkit-scrollbar{width:4px;height:4px}
107
- .tool-content::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}
108
- .thinking{padding:5px 12px;border-left:2px solid #21262d;font-size:12px;color:#6e7681;font-style:italic;overflow:hidden;text-overflow:ellipsis}
109
- #input-area{background:#161b22;border-top:1px solid #21262d;padding:10px 16px;flex-shrink:0}
105
+ .tool-content::-webkit-scrollbar-thumb{background:#3a3528}
106
+ #input-area{border-top:2px solid #3a3528;background:#141310;padding:10px 16px;flex-shrink:0}
110
107
  #input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}
111
- #prompt{flex:1;background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#e6edf3;font-size:13px;font-family:inherit;outline:none;transition:border-color .15s}
112
- #prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}
113
- #prompt::placeholder{color:#484f58}
114
- button{background:#238636;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;font-weight:600;cursor:pointer;transition:background .1s;flex-shrink:0}
115
- button:hover{background:#2ea043}
116
- button:disabled{background:#21262d;color:#484f58;cursor:not-allowed}
117
- .spin{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}
108
+ #prompt{flex:1;background:#1a1815;border:1px solid #3a3528;padding:8px 12px;color:#d4b872;font-size:13px;font-family:inherit;outline:none;transition:border .15s}
109
+ #prompt:focus{border-color:#c48c22;box-shadow:0 0 6px rgba(196,140,34,.15)}
110
+ #prompt::placeholder{color:#3a3528}
111
+ button{background:#3a3528;color:#c4a860;border:1px solid #5c5240;padding:8px 18px;font-size:12px;font-family:inherit;font-weight:700;cursor:pointer;transition:.1s;flex-shrink:0;text-transform:uppercase;letter-spacing:1px}
112
+ button:hover{background:#5c5240;color:#e8cf8a;border-color:#8e8048}
113
+ button:disabled{opacity:.3;cursor:not-allowed}
114
+ .spin{display:none;width:14px;height:14px;border:2px solid #3a3528;border-top-color:#c48c22;border-radius:50%;animation:spinner .5s linear infinite}
118
115
  .spin.on{display:inline-block}
119
- @keyframes spin{to{transform:rotate(360deg)}}
120
- @media(max-width:640px){
121
- #msgs{padding:12px;gap:12px}
122
- .msg-row.user{max-width:92%}.msg-row.assistant{max-width:95%}
123
- #input-area{padding:8px 12px}
124
- #prompt{padding:7px 10px}button{padding:7px 12px}
116
+ @keyframes spinner{to{transform:rotate(360deg)}}
117
+ @media(max-width:600px){
118
+ #msgs{padding:10px;gap:8px}
119
+ .row.user{max-width:94%}.row.ast{max-width:96%}
120
+ #input-area{padding:8px 10px}
125
121
  }
126
122
  </style>
127
123
  </head>
128
124
  <body>
129
- <div id="header"><h1>pi<sub id="session-info"></sub></h1><div id="header-right"><span id="stat-sessions"></span><span id="stat-cost"></span></div></div>
125
+ <div id="topbar"><h1>pi</h1><aside><span id="sid"></span> &nbsp; <span id="stat-sessions"></span> &nbsp; <span id="stat-cost"></span></aside></div>
130
126
  <div id="msgs"></div>
131
127
  <div id="input-area">
132
128
  <form id="f" autocomplete="off">
133
- <input id="prompt" type="text" placeholder="Message pi..." autofocus autocomplete="off">
129
+ <input id="prompt" type="text" placeholder=">_" autofocus autocomplete="off">
134
130
  <button id="send">Send</button>
135
131
  <span class="spin" id="spin"></span>
136
132
  </form>
137
133
  </div>
138
134
  <script>
139
135
  const msgs=document.getElementById("msgs"),f=document.getElementById("f"),prompt=document.getElementById("prompt"),
140
- send=document.getElementById("send"),spin=document.getElementById("spin"),
136
+ send=document.getElementById("send"),spinner=document.getElementById("spin"),
141
137
  statSessions=document.getElementById("stat-sessions"),statCost=document.getElementById("stat-cost"),
142
- sessionInfo=document.getElementById("session-info");
138
+ sid=document.getElementById("sid");
143
139
  let busy=false,tools={},curAsst=null;
144
140
 
145
141
  function scroll(){msgs.scrollTop=msgs.scrollHeight}
146
- function esc(s){if(!s)return"";return(s+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
147
- function fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+"k";return Math.round(n/1e3)+"k"}
142
+ function esc(s){if(s==null)return"";return(""+s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}
143
+ function fmt(n){if(!n)return"0";if(n<1e3)return""+n;if(n<1e4)return(n/1e3).toFixed(1)+"k";return Math.round(n/1e3)+"k"}
144
+
145
+ function argText(args){if(!args)return"";const v=args.command||args.path||args.file_path;return v?" "+esc(v).slice(0,80):""}
148
146
 
149
147
  function md(s){
150
- let t=esc(s);
151
- const blks=[];
152
- t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\\n\`\`\`/g,(_,lang,code)=>{blks.push('<pre><code>'+code+'</code></pre>');return'\\x00B'+(blks.length-1)+'\\x00B'});
148
+ let t=esc(s||"");
149
+ const blocks=[];
150
+ t=t.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\\n\`\`\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\x00B'+(blocks.length-1)+'\\x00B'});
153
151
  t=t.replace(/\`([^\`]+)\`/g,'<code>$1</code>');
154
152
  t=t.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
155
153
  t=t.replace(/\\*(.+?)\\*/g,'<em>$1</em>');
156
- t=t.replace(/^### (.+$)/gm,'<h3>$1</h3>');
157
- t=t.replace(/^## (.+$)/gm,'<h2>$1</h2>');
158
- t=t.replace(/^# (.+$)/gm,'<h1>$1</h1>');
159
- t=t.replace(/^- (.+$)/gm,'<li>$1</li>');
160
- t=t.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');
154
+ t=t.replace(/^### (.+)$/gm,'<h3>$1</h3>');
155
+ t=t.replace(/^## (.+)$/gm,'<h2>$1</h2>');
156
+ t=t.replace(/^# (.+)$/gm,'<h1>$1</h1>');
157
+ t=t.replace(/^- (.+)$/gm,'<li>$1</li>');
158
+ t=t.replace(/^> (.+)$/gm,'<blockquote>$1</blockquote>');
161
159
  t=t.replace(/((?:<li>.*<\\/li>\\n?)+)/g,'<ul>$1</ul>');
162
- t=t.replace(/(?:^---+|\\*\\*\\*+|___+)$/gm,'<hr>');
163
- t=t.replace(/\\x00B(\\d+)\\x00B/g,(_,i)=>blks[parseInt(i)]);
160
+ t=t.replace(/^(---+|\\*\\*\\*+|___+)$/gm,'<hr>');
161
+ t=t.replace(/\\x00B(\\d+)\\x00B/g,(_,i)=>blocks[parseInt(i)]);
164
162
  t=t.replace(/\\n\\n/g,'<br><br>');
165
163
  t=t.replace(/\\n/g,'<br>');
166
164
  return t
167
165
  }
168
166
 
169
- function row(role){const r=document.createElement("div");r.className="msg-row "+role;msgs.appendChild(r);return r}
170
- function bubble(row,inner){const b=document.createElement("div");b.className="msg-bubble "+row.classList[1];b.innerHTML=inner;row.appendChild(b);return b}
171
- function avatar(row,lbl){if(row.classList.contains("system"))return;const a=document.createElement("div");a.className="msg-avatar "+row.classList[1];a.textContent=lbl;row.appendChild(a)}
172
- function addUser(text){const r=row("user");avatar(r,"Y");bubble(r,esc(text));scroll();return r}
173
- function addAsst(){curAsst=row("assistant");avatar(curAsst,"P");const b=bubble(curAsst,"");scroll();return b}
174
- function getAsst(){if(!curAsst)return addAsst();return curAsst.querySelector(".msg-bubble")}
175
- function addSys(text){const r=row("system");bubble(r,text);scroll()}
167
+ function row(role){const d=document.createElement("div");d.className="row "+role;msgs.appendChild(d);return d}
168
+ function rol(role,lbl){const d=row(role);const n=document.createElement("div");n.className="rolename";n.textContent=lbl;d.appendChild(n);return d}
169
+ function bubble(parent,html){const b=document.createElement("div");b.className="bubble "+parent.classList[1];if(html!==undefined)b.innerHTML=html;parent.appendChild(b);return b}
170
+
171
+ function addUser(txt){const r=rol("user","YOU");bubble(r,esc(txt));scroll()}
172
+ function addAst(){const r=rol("ast","PI");const b=bubble(r);scroll();curAsst={row:r,bubble:b};return b}
173
+ function addSys(txt){const r=row("sys");bubble(r,txt);scroll()}
174
+ function getAsstB(){if(!curAsst)return addAst();return curAsst.bubble}
176
175
 
177
176
  function renderTool(e){
178
- const el=document.createElement("div");el.className="tool-wrapper";
179
- const argText=e.args?(" "+esc(String(e.args.command||e.args.path||e.args.file_path||"").slice(0,60))):"";
180
- el.innerHTML='<div class="tool active" id="t'+e.toolCallId+'"><div class="tool-bar"><span class="tool-dot running"></span><span class="tool-name">'+esc(e.toolName)+'</span><span class="tool-args">'+argText+'</span><span class="tool-chev">&#9654;</span></div><div class="tool-content"></div></div>';
181
- el.querySelector(".tool-bar").onclick=()=>{el.querySelector(".tool").classList.toggle("open")};
182
- msgs.appendChild(el);tools[e.toolCallId]=el;scroll()}
177
+ const r=row("ast");const w=document.createElement("div");w.className="tool-wrapper";
178
+ w.innerHTML='<div class="tool running" id="t'+e.toolCallId+'"><div class="tool-bar"><span class="tool-dot running"></span><span class="tool-kind">'+esc(e.toolName)+'</span><span class="tool-args">'+argText(e.args)+'</span><span class="tool-chev">&#9654;</span></div><div class="tool-content"></div></div>';
179
+ w.querySelector(".tool-bar").onclick=()=>w.querySelector(".tool").classList.toggle("open");
180
+ r.appendChild(w);msgs.appendChild(r);tools[e.toolCallId]={el:w.querySelector(".tool"),row:r};scroll()}
183
181
 
184
182
  function updateTool(id,result,isError){
185
183
  const t=tools[id];if(!t)return;
186
- const ct=t.querySelector(".tool-content");
184
+ const ct=t.el.querySelector(".tool-content");
187
185
  const texts=result&&result.content?result.content.filter(c=>c&&c.type==="text").map(c=>c.text):[];
188
- if(texts.length){ct.textContent=texts.join("\\n");t.querySelector(".tool").classList.add("open")}
189
- if(isError){t.querySelector(".tool-dot").className="tool-dot err";t.querySelector(".tool").classList.add("error");t.querySelector(".tool").classList.remove("active")}
186
+ if(texts.length){ct.textContent=texts.join("\\n");t.el.classList.add("open")}
187
+ if(isError){t.el.querySelector(".tool-dot").className="tool-dot err";t.el.classList.add("error");t.el.classList.remove("running")}
190
188
  }
191
189
 
192
190
  function doneTool(id,isError){
193
191
  const t=tools[id];if(!t)return;
194
- const dot=t.querySelector(".tool-dot");dot.className="tool-dot "+(isError?"err":"ok");
195
- t.querySelector(".tool").classList.remove("active");
196
- if(isError){t.querySelector(".tool").classList.add("error")}
197
- if(isError&&!t.querySelector(".tool-content").textContent.trim())t.querySelector(".tool-content").textContent="(no output)"
192
+ t.el.querySelector(".tool-dot").className="tool-dot "+(isError?"err":"ok");
193
+ t.el.classList.remove("running");
194
+ if(isError){t.el.classList.add("error")}
195
+ if(isError&&!t.el.querySelector(".tool-content").textContent.trim())t.el.querySelector(".tool-content").textContent="(no output)"
198
196
  }
199
197
 
200
198
  function handle(d){
201
199
  switch(d.type){
202
200
  case"agent_start":curAsst=null;break;
203
- case"text_delta":{const ad=getAsst();ad.innerHTML+=md(d.delta);scroll();break}
204
- case"thinking_delta":{let th=document.getElementById("th"+d.contentIndex);if(!th){th=document.createElement("div");th.className="thinking";th.id="th"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scroll();break}
201
+ case"text_delta":{const b=getAsstB();b.innerHTML+=md(d.delta);scroll();break}
202
+ case"thinking_delta":{let th=document.getElementById("th"+d.contentIndex);if(!th){th=document.createElement("div");th.className="row ast";const b=document.createElement("div");b.style.cssText="font-size:11px;color:#5c5240;font-style:italic;padding:4px 10px;border-left:2px solid #3a3528";b.id="th"+d.contentIndex;th.appendChild(b);msgs.appendChild(th)}th.firstChild.textContent+=d.delta;scroll();break}
205
203
  case"tool_execution_start":renderTool(d);break;
206
204
  case"tool_execution_update":updateTool(d.toolCallId,d.result,false);break;
207
205
  case"tool_execution_end":doneTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;
208
- case"agent_end":{let s="Done";if(d.usage){s+=" &middot; "+fmt(d.usage.input)+" in / "+fmt(d.usage.output)+" out";if(d.usage.cost)s+=" &middot; $"+d.usage.cost.total.toFixed(4)}addSys(s);break}
209
- case"compaction":addSys("Compacting...");break;
210
- case"error":addSys("Error: "+esc(d.message));break;
206
+ case"agent_end":{let s="DONE";if(d.usage){s+=" "+fmt(d.usage.input)+" IN / "+fmt(d.usage.output)+" OUT";if(d.usage.cost)s+=" $"+d.usage.cost.total.toFixed(4)}addSys(s);break}
207
+ case"compaction":addSys("--- COMPACTING ---");break;
208
+ case"error":addSys("ERR: "+esc(d.message||""));break;
211
209
  default:break}}
212
210
 
213
- f.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value="";addUser(text);busy=true;send.disabled=true;spin.className="spin on";
211
+ f.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value="";addUser(text);busy=true;send.disabled=true;spinner.className="spin on";
214
212
  try{const r=await fetch("/api/prompt",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({text})});
215
- if(!r.ok){addSys("Error: HTTP "+r.status);return}
213
+ if(!r.ok){addSys("HTTP "+r.status);return}
216
214
  const reader=r.body.getReader(),decoder=new TextDecoder();let buf="";
217
215
  while(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split("\\n");buf=lines.pop()||"";
218
216
  for(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}
219
- catch(err){addSys("Error: "+esc(err.message))}finally{busy=false;send.disabled=false;spin.className="spin"}};
217
+ catch(err){addSys("ERR: "+esc(err.message||""))}finally{busy=false;send.disabled=false;spinner.className="spin"}};
220
218
 
221
219
  fetch("/api/messages").then(r=>r.json()).then(events=>{for(const e of events){if(e.type==="user")addUser(e.text);else handle(e)}}).catch(()=>{});
222
220
 
223
- fetch("/api/stats").then(r=>r.json()).then(s=>{statSessions.textContent=s.sessions+" sessions";statCost.textContent="$"+s.cost.toFixed(2)}).catch(()=>{});
221
+ fetch("/api/stats").then(r=>r.json()).then(s=>{statSessions.textContent=s.sessions+" sessions";statCost.textContent=s.cost.toFixed(2)}).catch(()=>{});
224
222
 
225
- fetch("/api/session-info").then(r=>r.json()).then(s=>{sessionInfo.textContent=s.id?s.id.slice(0,8)+(s.name?" | "+s.name:""):""}).catch(()=>{});
223
+ fetch("/api/session-info").then(r=>r.json()).then(s=>{sid.textContent=s.id?s.id.slice(0,8)+(s.name?" "+s.name:""):""}).catch(()=>{});
226
224
  </script>
227
225
  </body>
228
226
  </html>`;
@@ -1 +1 @@
1
- {"version":3,"file":"web-mode.js","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;background:#0d1117;color:#e6edf3;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:0 16px;height:44px;background:#161b22;border-bottom:1px solid #21262d;flex-shrink:0}\n#header h1{font-size:14px;font-weight:600;color:#f0f6fc}\n#header sub{font-size:10px;color:#8b949e;margin-left:6px}\n#header-right{display:flex;gap:14px;font-size:11px;color:#8b949e}\n#msgs{flex:1;overflow-y:auto;padding:20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:18px}\n#msgs::-webkit-scrollbar{width:5px}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-row{animation:fadeIn .15s ease}\n@keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}\n.msg-row.user{align-self:flex-end;max-width:80%}\n.msg-row.assistant{align-self:flex-start;max-width:86%}\n.msg-row.system{align-self:center;max-width:90%}\n.msg-avatar{width:26px;height:26px;border-radius:6px;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;margin-bottom:4px}\n.msg-avatar.user{background:#1f6feb33;color:#58a6ff;margin-left:auto}\n.msg-avatar.assistant{background:#30363d;color:#8b949e}\n.msg-bubble{border-radius:8px;font-size:13.5px;line-height:1.55;padding:10px 14px;overflow-wrap:break-word}\n.msg-bubble.user{background:#1f6feb;color:#fff;border-bottom-right-radius:3px}\n.msg-bubble.assistant{background:#161b22;color:#c9d1d9;border:1px solid #30363d;border-bottom-left-radius:3px}\n.msg-bubble.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;padding:4px 0;border:none}\n.msg-bubble code{font-family:\"JetBrains Mono\",monospace;font-size:12px;background:#0d1117;border:1px solid #30363d;padding:2px 6px;border-radius:4px;color:#d2a8ff}\n.msg-bubble pre{background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.45;font-family:\"JetBrains Mono\",monospace;color:#c9d1d9}\n.msg-bubble pre code{background:transparent;border:none;padding:0;color:inherit}\n.msg-bubble strong{color:#e6edf3;font-weight:600}\n.msg-bubble em{font-style:italic}\n.msg-bubble h1,.msg-bubble h2,.msg-bubble h3{margin:6px 0 3px;font-weight:600}\n.msg-bubble h1{font-size:17px;border-bottom:1px solid #21262d;padding-bottom:3px}\n.msg-bubble h2{font-size:14px}\n.msg-bubble h3{font-size:13px;color:#8b949e}\n.msg-bubble ul,.msg-bubble ol{padding-left:20px;margin:4px 0}\n.msg-bubble li{margin:2px 0}\n.msg-bubble hr{border:none;border-top:1px solid #21262d;margin:8px 0}\n.msg-bubble blockquote{border-left:3px solid #30363d;padding:4px 10px;color:#8b949e;margin:6px 0}\n.tool-wrapper{margin:4px 0}\n.tool{background:#161b22;border:1px solid #21262d;border-radius:6px;overflow:hidden;transition:box-shadow .15s, border-color .15s}\n.tool:hover{border-color:#30363d}\n.tool.active{box-shadow:0 0 0 1px rgba(210,153,34,.25)}\n.tool.error{border-color:#490202;background:#161b22}\n.tool-bar{display:flex;align-items:center;padding:7px 10px;cursor:pointer;gap:8px;font-size:12px;user-select:none}\n.tool-bar:hover{background:#1c2128}\n.tool-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:#30363d}\n.tool-dot.running{background:#d29922;animation:pulse 1.2s ease-in-out infinite}\n.tool-dot.ok{background:#3fb950}\n.tool-dot.err{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.3;transform:scale(.9)}50%{opacity:1;transform:scale(1.2)}}\n.tool-name{font-weight:600;color:#e6edf3;white-space:nowrap}\n.tool-args{color:#8b949e;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-chev{color:#484f58;font-size:9px;transition:.15s}\n.tool.open .tool-chev{transform:rotate(90deg)}\n.tool-content{display:none;padding:8px 12px;border-top:1px solid #21262d;font-family:\"JetBrains Mono\",monospace;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:36vh;overflow-y:auto;color:#c9d1d9;background:#0d1117}\n.tool.open .tool-content{display:block}\n.tool-content::-webkit-scrollbar{width:4px;height:4px}\n.tool-content::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.thinking{padding:5px 12px;border-left:2px solid #21262d;font-size:12px;color:#6e7681;font-style:italic;overflow:hidden;text-overflow:ellipsis}\n#input-area{background:#161b22;border-top:1px solid #21262d;padding:10px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#0d1117;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#e6edf3;font-size:13px;font-family:inherit;outline:none;transition:border-color .15s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#238636;color:#fff;border:none;border-radius:8px;padding:8px 16px;font-size:13px;font-weight:600;cursor:pointer;transition:background .1s;flex-shrink:0}\nbutton:hover{background:#2ea043}\nbutton:disabled{background:#21262d;color:#484f58;cursor:not-allowed}\n.spin{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spin.on{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:640px){\n#msgs{padding:12px;gap:12px}\n.msg-row.user{max-width:92%}.msg-row.assistant{max-width:95%}\n#input-area{padding:8px 12px}\n#prompt{padding:7px 10px}button{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\"><h1>pi<sub id=\"session-info\"></sub></h1><div id=\"header-right\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spin\" id=\"spin\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spin=document.getElementById(\"spin\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,tools={},curAsst=null;\n\nfunction scroll(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return(s+\"\").replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction md(s){\nlet t=esc(s);\nconst blks=[];\nt=t.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blks.length-1)+'\\\\x00B'});\nt=t.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\nt=t.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\nt=t.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\nt=t.replace(/^### (.+$)/gm,'<h3>$1</h3>');\nt=t.replace(/^## (.+$)/gm,'<h2>$1</h2>');\nt=t.replace(/^# (.+$)/gm,'<h1>$1</h1>');\nt=t.replace(/^- (.+$)/gm,'<li>$1</li>');\nt=t.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\nt=t.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\nt=t.replace(/(?:^---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\nt=t.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blks[parseInt(i)]);\nt=t.replace(/\\\\n\\\\n/g,'<br><br>');\nt=t.replace(/\\\\n/g,'<br>');\nreturn t\n}\n\nfunction row(role){const r=document.createElement(\"div\");r.className=\"msg-row \"+role;msgs.appendChild(r);return r}\nfunction bubble(row,inner){const b=document.createElement(\"div\");b.className=\"msg-bubble \"+row.classList[1];b.innerHTML=inner;row.appendChild(b);return b}\nfunction avatar(row,lbl){if(row.classList.contains(\"system\"))return;const a=document.createElement(\"div\");a.className=\"msg-avatar \"+row.classList[1];a.textContent=lbl;row.appendChild(a)}\nfunction addUser(text){const r=row(\"user\");avatar(r,\"Y\");bubble(r,esc(text));scroll();return r}\nfunction addAsst(){curAsst=row(\"assistant\");avatar(curAsst,\"P\");const b=bubble(curAsst,\"\");scroll();return b}\nfunction getAsst(){if(!curAsst)return addAsst();return curAsst.querySelector(\".msg-bubble\")}\nfunction addSys(text){const r=row(\"system\");bubble(r,text);scroll()}\n\nfunction renderTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool-wrapper\";\nconst argText=e.args?(\" \"+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,60))):\"\";\nel.innerHTML='<div class=\"tool active\" id=\"t'+e.toolCallId+'\"><div class=\"tool-bar\"><span class=\"tool-dot running\"></span><span class=\"tool-name\">'+esc(e.toolName)+'</span><span class=\"tool-args\">'+argText+'</span><span class=\"tool-chev\">&#9654;</span></div><div class=\"tool-content\"></div></div>';\nel.querySelector(\".tool-bar\").onclick=()=>{el.querySelector(\".tool\").classList.toggle(\"open\")};\nmsgs.appendChild(el);tools[e.toolCallId]=el;scroll()}\n\nfunction updateTool(id,result,isError){\nconst t=tools[id];if(!t)return;\nconst ct=t.querySelector(\".tool-content\");\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){ct.textContent=texts.join(\"\\\\n\");t.querySelector(\".tool\").classList.add(\"open\")}\nif(isError){t.querySelector(\".tool-dot\").className=\"tool-dot err\";t.querySelector(\".tool\").classList.add(\"error\");t.querySelector(\".tool\").classList.remove(\"active\")}\n}\n\nfunction doneTool(id,isError){\nconst t=tools[id];if(!t)return;\nconst dot=t.querySelector(\".tool-dot\");dot.className=\"tool-dot \"+(isError?\"err\":\"ok\");\nt.querySelector(\".tool\").classList.remove(\"active\");\nif(isError){t.querySelector(\".tool\").classList.add(\"error\")}\nif(isError&&!t.querySelector(\".tool-content\").textContent.trim())t.querySelector(\".tool-content\").textContent=\"(no output)\"\n}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAsst=null;break;\ncase\"text_delta\":{const ad=getAsst();ad.innerHTML+=md(d.delta);scroll();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"th\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"th\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scroll();break}\ncase\"tool_execution_start\":renderTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":doneTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"Done\";if(d.usage){s+=\" &middot; \"+fmt(d.usage.input)+\" in / \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" &middot; $\"+d.usage.cost.total.toFixed(4)}addSys(s);break}\ncase\"compaction\":addSys(\"Compacting...\");break;\ncase\"error\":addSys(\"Error: \"+esc(d.message));break;\ndefault:break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addUser(text);busy=true;send.disabled=true;spin.className=\"spin on\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addSys(\"Error: HTTP \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addSys(\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spin.className=\"spin\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{for(const e of events){if(e.type===\"user\")addUser(e.text);else handle(e)}}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{statSessions.textContent=s.sessions+\" sessions\";statCost.textContent=\"$\"+s.cost.toFixed(2)}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{sessionInfo.textContent=s.id?s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\"):\"\"}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
1
+ {"version":3,"file":"web-mode.js","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:15px}\nbody{font-family:\"IBM Plex Mono\",\"JetBrains Mono\",\"Fira Code\",\"SF Mono\",monospace;background:#1a1815;color:#d4b872;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#topbar{display:flex;align-items:center;justify-content:space-between;padding:6px 16px;background:#141310;border-bottom:2px solid #3a3528;flex-shrink:0}\n#topbar h1{font-size:13px;font-weight:700;color:#e8cf8a;letter-spacing:1px}\n#topbar h1::before{content:\"[ \";color:#5c5240}#topbar h1::after{content:\" ]\";color:#5c5240}\n#topbar aside{font-size:10px;color:#5c5240}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:12px;background:linear-gradient(180deg,#1a1815 0%,#171512 100%)}\n#msgs::-webkit-scrollbar{width:5px}\n#msgs::-webkit-scrollbar-thumb{background:#3a3528;border-radius:0}\n.row{animation:fadeIn .12s ease}\n.row.user{display:flex;flex-direction:column;align-items:flex-end;max-width:82%;align-self:flex-end}\n.row.ast{display:flex;flex-direction:column;align-items:flex-start;max-width:88%;align-self:flex-start;width:100%}\n.row.sys{display:flex;justify-content:center;max-width:100%;align-self:center}\n@keyframes fadeIn{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}\n.rolename{font-size:10px;text-transform:uppercase;letter-spacing:1px;margin-bottom:2px;font-weight:700}\n.row.user .rolename{color:#9e8a5e}\n.row.ast .rolename{color:#5c5240}\n.bubble{border:1px solid #3a3528;padding:10px 14px;font-size:13px;line-height:1.65;overflow-wrap:break-word;word-break:break-word}\n.bubble.user{background:#2a251c;color:#e8cf8a;max-width:100%}\n.bubble.ast{background:#141310;color:#c4a860;max-width:100%}\n.bubble.sys{background:none;border:none;color:#5c5240;font-size:11px;text-align:center;padding:2px 0}\n.bubble code{background:#111;border:1px solid #3a3528;padding:1px 5px;color:#d4b872;font-size:12px}\n.bubble pre{background:#111;border:1px solid #3a3528;padding:10px 12px;overflow-x:auto;font-size:12px;line-height:1.5;color:#c4a860;margin:6px 0}\n.bubble pre code{background:none;border:none;padding:0;font-size:inherit;color:inherit}\n.bubble strong{color:#e8cf8a;font-weight:700}\n.bubble em{color:#c4a860;font-style:italic}\n.bubble h1,.bubble h2,.bubble h3{font-weight:700;color:#e8cf8a;margin:8px 0 3px}\n.bubble h1{font-size:15px;border-bottom:1px solid #3a3528;padding-bottom:3px}\n.bubble h2{font-size:14px}\n.bubble h3{font-size:13px;color:#c4a860}\n.bubble ul,.bubble ol{padding-left:22px;margin:4px 0}\n.bubble li{margin:2px 0}\n.bubble hr{border:none;border-top:1px solid #3a3528;margin:8px 0}\n.bubble blockquote{border-left:3px solid #3a3528;padding:4px 10px;color:#8e8048;margin:4px 0}\n.tool{border:1px solid #3a3528;margin:2px 0}\n.tool.running{border-color:#906820}\n.tool.error{border-color:#6b2020}\n.tool-bar{display:flex;align-items:center;padding:6px 10px;cursor:pointer;gap:8px;font-size:12px;user-select:none;background:#141310}\n.tool-bar:hover{background:#1a1815}\n.tool-dot{width:7px;height:7px;flex-shrink:0;background:#3a3528}\n.tool-dot.running{background:#c48c22;animation:blink 1s step-end infinite}\n.tool-dot.ok{background:#689040}\n.tool-dot.err{background:#b84040}\n@keyframes blink{50%{opacity:.2}}\n.tool-kind{font-weight:700;color:#c4a860}\n.tool-args{color:#5c5240;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-chev{color:#5c5240;font-size:10px;transition:.12s}\n.tool.open .tool-chev{transform:rotate(90deg)}\n.tool-content{display:none;padding:8px 12px;border-top:1px solid #3a3528;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:32vh;overflow-y:auto;color:#8e8048;background:#111}\n.tool.open .tool-content{display:block}\n.tool-content::-webkit-scrollbar{width:4px;height:4px}\n.tool-content::-webkit-scrollbar-thumb{background:#3a3528}\n#input-area{border-top:2px solid #3a3528;background:#141310;padding:10px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#1a1815;border:1px solid #3a3528;padding:8px 12px;color:#d4b872;font-size:13px;font-family:inherit;outline:none;transition:border .15s}\n#prompt:focus{border-color:#c48c22;box-shadow:0 0 6px rgba(196,140,34,.15)}\n#prompt::placeholder{color:#3a3528}\nbutton{background:#3a3528;color:#c4a860;border:1px solid #5c5240;padding:8px 18px;font-size:12px;font-family:inherit;font-weight:700;cursor:pointer;transition:.1s;flex-shrink:0;text-transform:uppercase;letter-spacing:1px}\nbutton:hover{background:#5c5240;color:#e8cf8a;border-color:#8e8048}\nbutton:disabled{opacity:.3;cursor:not-allowed}\n.spin{display:none;width:14px;height:14px;border:2px solid #3a3528;border-top-color:#c48c22;border-radius:50%;animation:spinner .5s linear infinite}\n.spin.on{display:inline-block}\n@keyframes spinner{to{transform:rotate(360deg)}}\n@media(max-width:600px){\n#msgs{padding:10px;gap:8px}\n.row.user{max-width:94%}.row.ast{max-width:96%}\n#input-area{padding:8px 10px}\n}\n</style>\n</head>\n<body>\n<div id=\"topbar\"><h1>pi</h1><aside><span id=\"sid\"></span> &nbsp; <span id=\"stat-sessions\"></span> &nbsp; <span id=\"stat-cost\"></span></aside></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\">_\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spin\" id=\"spin\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spin\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsid=document.getElementById(\"sid\");\nlet busy=false,tools={},curAsst=null;\n\nfunction scroll(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(s==null)return\"\";return(\"\"+s).replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/\"/g,\"&quot;\")}\nfunction fmt(n){if(!n)return\"0\";if(n<1e3)return\"\"+n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\n\nfunction argText(args){if(!args)return\"\";const v=args.command||args.path||args.file_path;return v?\" \"+esc(v).slice(0,80):\"\"}\n\nfunction md(s){\nlet t=esc(s||\"\");\nconst blocks=[];\nt=t.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\nt=t.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\nt=t.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\nt=t.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\nt=t.replace(/^### (.+)$/gm,'<h3>$1</h3>');\nt=t.replace(/^## (.+)$/gm,'<h2>$1</h2>');\nt=t.replace(/^# (.+)$/gm,'<h1>$1</h1>');\nt=t.replace(/^- (.+)$/gm,'<li>$1</li>');\nt=t.replace(/^> (.+)$/gm,'<blockquote>$1</blockquote>');\nt=t.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\nt=t.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\nt=t.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\nt=t.replace(/\\\\n\\\\n/g,'<br><br>');\nt=t.replace(/\\\\n/g,'<br>');\nreturn t\n}\n\nfunction row(role){const d=document.createElement(\"div\");d.className=\"row \"+role;msgs.appendChild(d);return d}\nfunction rol(role,lbl){const d=row(role);const n=document.createElement(\"div\");n.className=\"rolename\";n.textContent=lbl;d.appendChild(n);return d}\nfunction bubble(parent,html){const b=document.createElement(\"div\");b.className=\"bubble \"+parent.classList[1];if(html!==undefined)b.innerHTML=html;parent.appendChild(b);return b}\n\nfunction addUser(txt){const r=rol(\"user\",\"YOU\");bubble(r,esc(txt));scroll()}\nfunction addAst(){const r=rol(\"ast\",\"PI\");const b=bubble(r);scroll();curAsst={row:r,bubble:b};return b}\nfunction addSys(txt){const r=row(\"sys\");bubble(r,txt);scroll()}\nfunction getAsstB(){if(!curAsst)return addAst();return curAsst.bubble}\n\nfunction renderTool(e){\nconst r=row(\"ast\");const w=document.createElement(\"div\");w.className=\"tool-wrapper\";\nw.innerHTML='<div class=\"tool running\" id=\"t'+e.toolCallId+'\"><div class=\"tool-bar\"><span class=\"tool-dot running\"></span><span class=\"tool-kind\">'+esc(e.toolName)+'</span><span class=\"tool-args\">'+argText(e.args)+'</span><span class=\"tool-chev\">&#9654;</span></div><div class=\"tool-content\"></div></div>';\nw.querySelector(\".tool-bar\").onclick=()=>w.querySelector(\".tool\").classList.toggle(\"open\");\nr.appendChild(w);msgs.appendChild(r);tools[e.toolCallId]={el:w.querySelector(\".tool\"),row:r};scroll()}\n\nfunction updateTool(id,result,isError){\nconst t=tools[id];if(!t)return;\nconst ct=t.el.querySelector(\".tool-content\");\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){ct.textContent=texts.join(\"\\\\n\");t.el.classList.add(\"open\")}\nif(isError){t.el.querySelector(\".tool-dot\").className=\"tool-dot err\";t.el.classList.add(\"error\");t.el.classList.remove(\"running\")}\n}\n\nfunction doneTool(id,isError){\nconst t=tools[id];if(!t)return;\nt.el.querySelector(\".tool-dot\").className=\"tool-dot \"+(isError?\"err\":\"ok\");\nt.el.classList.remove(\"running\");\nif(isError){t.el.classList.add(\"error\")}\nif(isError&&!t.el.querySelector(\".tool-content\").textContent.trim())t.el.querySelector(\".tool-content\").textContent=\"(no output)\"\n}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAsst=null;break;\ncase\"text_delta\":{const b=getAsstB();b.innerHTML+=md(d.delta);scroll();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"th\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"row ast\";const b=document.createElement(\"div\");b.style.cssText=\"font-size:11px;color:#5c5240;font-style:italic;padding:4px 10px;border-left:2px solid #3a3528\";b.id=\"th\"+d.contentIndex;th.appendChild(b);msgs.appendChild(th)}th.firstChild.textContent+=d.delta;scroll();break}\ncase\"tool_execution_start\":renderTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":doneTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"DONE\";if(d.usage){s+=\" \"+fmt(d.usage.input)+\" IN / \"+fmt(d.usage.output)+\" OUT\";if(d.usage.cost)s+=\" $\"+d.usage.cost.total.toFixed(4)}addSys(s);break}\ncase\"compaction\":addSys(\"--- COMPACTING ---\");break;\ncase\"error\":addSys(\"ERR: \"+esc(d.message||\"\"));break;\ndefault:break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addUser(text);busy=true;send.disabled=true;spinner.className=\"spin on\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addSys(\"HTTP \"+r.status);return}\nconst reader=r.body.getReader(),decoder=new TextDecoder();let buf=\"\";\nwhile(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split(\"\\\\n\");buf=lines.pop()||\"\";\nfor(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}\ncatch(err){addSys(\"ERR: \"+esc(err.message||\"\"))}finally{busy=false;send.disabled=false;spinner.className=\"spin\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{for(const e of events){if(e.type===\"user\")addUser(e.text);else handle(e)}}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{statSessions.textContent=s.sessions+\" sessions\";statCost.textContent=s.cost.toFixed(2)}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{sid.textContent=s.id?s.id.slice(0,8)+(s.name?\" \"+s.name:\"\"):\"\"}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.75.24",
3
+ "version": "0.75.25",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.75.24",
9
+ "version": "0.75.25",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.75.24",
4
+ "version": "0.75.25",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.75.24",
4
+ "version": "0.75.25",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "1.5.24",
3
+ "version": "1.5.25",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.5.24",
9
+ "version": "1.5.25",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.5.24",
4
+ "version": "1.5.25",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.75.24",
3
+ "version": "0.75.25",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.75.24",
9
+ "version": "0.75.25",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.75.24",
4
+ "version": "0.75.25",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@openeryc/pi-coding-agent",
3
- "version": "0.75.24",
3
+ "version": "0.75.25",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openeryc/pi-coding-agent",
9
- "version": "0.75.24",
9
+ "version": "0.75.25",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@openeryc/pi-agent-core": "^0.75.24",
13
- "@openeryc/pi-ai": "^0.75.24",
14
- "@openeryc/pi-tui": "^0.75.24",
12
+ "@openeryc/pi-agent-core": "^0.75.25",
13
+ "@openeryc/pi-ai": "^0.75.25",
14
+ "@openeryc/pi-tui": "^0.75.25",
15
15
  "@silvia-odwyer/photon-node": "0.3.4",
16
16
  "chalk": "5.6.2",
17
17
  "cross-spawn": "7.0.6",
@@ -699,11 +699,11 @@
699
699
  ]
700
700
  },
701
701
  "node_modules/@openeryc/pi-agent-core": {
702
- "version": "0.75.24",
703
- "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.24.tgz",
702
+ "version": "0.75.25",
703
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.25.tgz",
704
704
  "license": "MIT",
705
705
  "dependencies": {
706
- "@openeryc/pi-ai": "^0.75.24",
706
+ "@openeryc/pi-ai": "^0.75.25",
707
707
  "ignore": "7.0.5",
708
708
  "typebox": "1.1.38",
709
709
  "yaml": "2.9.0"
@@ -713,8 +713,8 @@
713
713
  }
714
714
  },
715
715
  "node_modules/@openeryc/pi-ai": {
716
- "version": "0.75.24",
717
- "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.24.tgz",
716
+ "version": "0.75.25",
717
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.25.tgz",
718
718
  "license": "MIT",
719
719
  "dependencies": {
720
720
  "@anthropic-ai/sdk": "0.91.1",
@@ -735,8 +735,8 @@
735
735
  }
736
736
  },
737
737
  "node_modules/@openeryc/pi-tui": {
738
- "version": "0.75.24",
739
- "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.24.tgz",
738
+ "version": "0.75.25",
739
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.25.tgz",
740
740
  "license": "MIT",
741
741
  "dependencies": {
742
742
  "get-east-asian-width": "1.6.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openeryc/pi-coding-agent",
3
- "version": "0.75.24",
3
+ "version": "0.75.25",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -39,9 +39,9 @@
39
39
  "prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
40
40
  },
41
41
  "dependencies": {
42
- "@openeryc/pi-agent-core": "^0.75.24",
43
- "@openeryc/pi-ai": "^0.75.24",
44
- "@openeryc/pi-tui": "^0.75.24",
42
+ "@openeryc/pi-agent-core": "^0.75.25",
43
+ "@openeryc/pi-ai": "^0.75.25",
44
+ "@openeryc/pi-tui": "^0.75.25",
45
45
  "@silvia-odwyer/photon-node": "0.3.4",
46
46
  "chalk": "5.6.2",
47
47
  "cross-spawn": "7.0.6",