@openeryc/pi-coding-agent 0.75.22 → 0.75.23

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.23] - 2026-05-22
4
+
3
5
  ## [0.75.22] - 2026-05-22
4
6
 
5
7
  ## [0.75.21] - 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;AAiP/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgG5E","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;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</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=\"spinner\" id=\"spinner\"></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(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){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(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">&#9654;</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" &middot; \"+fmt(d.usage.input)+\" in &middot \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" &middot; $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+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){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).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\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 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;AAwP/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;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</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=\"spinner\" id=\"spinner\"></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(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){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(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">&#9654;</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" &middot; \"+fmt(d.usage.input)+\" in &middot \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" &middot; $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+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){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{\nfor(const e of events){\nif(e.type===\"user\"){addMsg(\"user\",e.text)}\nelse{handle(e)}\n}\n}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).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"]}
@@ -221,6 +221,13 @@ while(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.dec
221
221
  for(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}
222
222
  catch(err){addMsg("system","Error: "+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className="spinner"}};
223
223
 
224
+ fetch("/api/messages").then(r=>r.json()).then(events=>{
225
+ for(const e of events){
226
+ if(e.type==="user"){addMsg("user",e.text)}
227
+ else{handle(e)}
228
+ }
229
+ }).catch(()=>{});
230
+
224
231
  fetch("/api/stats").then(r=>r.json()).then(s=>{
225
232
  statSessions.textContent=s.sessions+" sessions";
226
233
  statCost.textContent="$"+s.cost.toFixed(2);
@@ -287,6 +294,53 @@ export async function runWebMode(runtime) {
287
294
  }));
288
295
  return;
289
296
  }
297
+ if (req.method === "GET" && url === "/api/messages") {
298
+ const entries = session.sessionManager.getEntries();
299
+ const events = [];
300
+ for (const entry of entries) {
301
+ if (entry.type !== "message")
302
+ continue;
303
+ const msg = entry.message;
304
+ if (!msg)
305
+ continue;
306
+ if (msg.role === "user") {
307
+ const content = typeof msg.content === "string" ? msg.content : "";
308
+ events.push({ type: "user", text: content });
309
+ }
310
+ else if (msg.role === "assistant") {
311
+ const content = Array.isArray(msg.content) ? msg.content : [];
312
+ for (const block of content) {
313
+ if (block.type === "text" && block.text) {
314
+ events.push({ type: "text_delta", delta: block.text });
315
+ }
316
+ if (block.type === "toolCall" && block.toolCall) {
317
+ const tc = block.toolCall;
318
+ events.push({
319
+ type: "tool_execution_start",
320
+ toolCallId: tc.toolCallId,
321
+ toolName: tc.toolName,
322
+ args: tc.input,
323
+ });
324
+ }
325
+ }
326
+ }
327
+ else if (msg.role === "toolResult") {
328
+ const content = msg
329
+ .content;
330
+ events.push({
331
+ type: "tool_execution_end",
332
+ toolCallId: msg.toolCallId,
333
+ result: { content: content ?? [] },
334
+ isError: false,
335
+ });
336
+ }
337
+ }
338
+ const usage = getSessionTokenCount(session);
339
+ events.push({ type: "agent_end", usage });
340
+ res.writeHead(200, { "content-type": "application/json" });
341
+ res.end(JSON.stringify(events));
342
+ return;
343
+ }
290
344
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
291
345
  res.end(HTML);
292
346
  });
@@ -318,6 +372,25 @@ export async function runWebMode(runtime) {
318
372
  await new Promise(() => { });
319
373
  server.close();
320
374
  }
375
+ function getSessionTokenCount(session) {
376
+ const entries = session.sessionManager.getEntries();
377
+ let input = 0;
378
+ let output = 0;
379
+ let costTotal = 0;
380
+ let hasUsage = false;
381
+ for (const entry of entries) {
382
+ if (entry.type !== "message")
383
+ continue;
384
+ const msg = entry.message;
385
+ if (msg?.role === "assistant" && msg.usage) {
386
+ input += msg.usage.input;
387
+ output += msg.usage.output;
388
+ costTotal += msg.usage.cost.total;
389
+ hasUsage = true;
390
+ }
391
+ }
392
+ return hasUsage ? { input, output, cost: { total: costTotal } } : undefined;
393
+ }
321
394
  function toSerializableEvents(event) {
322
395
  switch (event.type) {
323
396
  case "agent_start":
@@ -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;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</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=\"spinner\" id=\"spinner\"></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(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){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(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">&#9654;</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" &middot; \"+fmt(d.usage.input)+\" in &middot \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" &middot; $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+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){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).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\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 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":"AAAA,OAAO,EAAE,YAAY,EAA6C,MAAM,WAAW,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAI5C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAE7C,SAAS,SAAS,CAAC,GAAoB,EAAE,GAAmB,EAAW;IACtE,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IACvC,IAAI,IAAI,EAAE,CAAC;QACV,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,MAAM,KAAK,OAAO,IAAI,WAAW,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACrE,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACxC,IAAI,QAAQ,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;QACxC,CAAC;IACF,CAAC;IAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QAClB,kBAAkB,EAAE,mCAAmC;QACvD,cAAc,EAAE,YAAY;KAC5B,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACxB,OAAO,KAAK,CAAC;AAAA,CACb;AAED,SAAS,UAAU,GAAW;IAC7B,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC9C,OAAO,IAAI,CAAC,OAAO,CAAC;YACrB,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,WAAW,CAAC;AAAA,CACnB;AAED,SAAS,OAAO,GAAW;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAC7B,IAAI,GAAG,EAAE,CAAC;QACT,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK;YAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,CAAC,CAAC;AAAA,CACT;AAED,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAmML,CAAC;AAET,SAAS,SAAS,CAAC,GAAmB,EAAE,IAA6B,EAAQ;IAC5E,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACvC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAA4B,EAAiB;IAC7E,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAChC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAE,EAAE,CAAC;QAChF,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;YAAE,OAAO;QAEjC,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;QAE3B,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;YACpD,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACtD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;YAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACX,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;gBACnD,OAAO;YACR,CAAC;YAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,2BAA2B,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC;YAEjG,MAAM,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,KAAwB,EAAE,EAAE,CAAC;gBACnE,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;gBAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;oBAC1B,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;gBACrB,CAAC;YAAA,CACD,CAAC,CAAC;YAEH,IAAI,CAAC;gBACJ,MAAM,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,SAAS,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzD,CAAC;oBAAS,CAAC;gBACV,WAAW,EAAE,CAAC;gBACd,GAAG,CAAC,GAAG,EAAE,CAAC;YACX,CAAC;YACD,OAAO;QACR,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,YAAY,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;YAC5C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CACN,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CACxG,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,mBAAmB,EAAE,CAAC;YACzD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CACN,IAAI,CAAC,SAAS,CAAC;gBACd,EAAE,EAAE,OAAO,CAAC,SAAS;gBACrB,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,cAAc,EAAE;gBAC7C,IAAI,EAAE,OAAO,CAAC,WAAW;aACzB,CAAC,CACF,CAAC;YACF,OAAO;QACR,CAAC;QAED,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,eAAe,EAAE,CAAC;YACrD,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;YACpD,MAAM,MAAM,GAA8B,EAAE,CAAC;YAE7C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;oBAAE,SAAS;gBACvC,MAAM,GAAG,GAAI,KAA0E,CAAC,OAAO,CAAC;gBAChG,IAAI,CAAC,GAAG;oBAAE,SAAS;gBAEnB,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACzB,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;oBACnE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC9C,CAAC;qBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBACrC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC9D,KAAK,MAAM,KAAK,IAAI,OAAqE,EAAE,CAAC;wBAC3F,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;4BACzC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;wBACxD,CAAC;wBACD,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;4BACjD,MAAM,EAAE,GAAG,KAAK,CAAC,QAAuE,CAAC;4BACzF,MAAM,CAAC,IAAI,CAAC;gCACX,IAAI,EAAE,sBAAsB;gCAC5B,UAAU,EAAE,EAAE,CAAC,UAAU;gCACzB,QAAQ,EAAE,EAAE,CAAC,QAAQ;gCACrB,IAAI,EAAE,EAAE,CAAC,KAAK;6BACd,CAAC,CAAC;wBACJ,CAAC;oBACF,CAAC;gBACF,CAAC;qBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBACtC,MAAM,OAAO,GAAI,GAAiF;yBAChG,OAAO,CAAC;oBACV,MAAM,CAAC,IAAI,CAAC;wBACX,IAAI,EAAE,oBAAoB;wBAC1B,UAAU,EAAG,GAA+B,CAAC,UAAU;wBACvD,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE;wBAClC,OAAO,EAAE,KAAK;qBACd,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;YAED,MAAM,KAAK,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;YAE1C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YAChC,OAAO;QACR,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAAA,CACd,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAAA,CAC3B,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IAC9B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC5C,OAAO;IACR,CAAC;IAED,MAAM,WAAW,GAAG,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAC;IACpD,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,OAAO,CAAC,GAAG,CAAC,mBAAmB,WAAW,EAAE,CAAC,CAAC;IAC9C,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,wBAAwB,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,iBAAiB,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC/C,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,gEAAgE,CAAC,CAAC;IAC/E,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,CAAC,GAAG,EAAE,CAAC;IAEd,qBAAqB;IACrB,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;IAE5B,MAAM,CAAC,KAAK,EAAE,CAAC;AAAA,CACf;AAED,SAAS,oBAAoB,CAAC,OAAuC,EAAuC;IAC3G,MAAM,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,UAAU,EAAE,CAAC;IACpD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,SAAS;QACvC,MAAM,GAAG,GACR,KACA,CAAC,OAAO,CAAC;QACV,IAAI,GAAG,EAAE,IAAI,KAAK,WAAW,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC5C,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC;YACzB,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;YAC3B,SAAS,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAClC,QAAQ,GAAG,IAAI,CAAC;QACjB,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CAC5E;AAED,SAAS,oBAAoB,CAAC,KAAwB,EAA6B;IAClF,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,aAAa;YACjB,OAAO,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;QAClC,KAAK,WAAW,EAAE,CAAC;YAClB,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAClF,MAAM,KAAK,GAAG,aAAa,IAAI,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;YAC1F,OAAO,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,gBAAgB,EAAE,CAAC;YACvB,MAAM,GAAG,GAA4B,KAAK,CAAC,qBAAqB,CAAC;YACjE,MAAM,UAAU,GAAG,8BAA8B,CAAC,GAAG,CAAC,CAAC;YACvD,OAAO,UAAU,CAAC;QACnB,CAAC;QACD,KAAK,sBAAsB;YAC1B,OAAO;gBACN;oBACC,IAAI,EAAE,sBAAsB;oBAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,IAAI,EAAE,KAAK,CAAC,IAAI;iBAChB;aACD,CAAC;QACH,KAAK,uBAAuB;YAC3B,OAAO;gBACN;oBACC,IAAI,EAAE,uBAAuB;oBAC7B,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,MAAM,EAAE,KAAK,CAAC,aAAa;iBAC3B;aACD,CAAC;QACH,KAAK,oBAAoB;YACxB,OAAO;gBACN;oBACC,IAAI,EAAE,oBAAoB;oBAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,OAAO,EAAE,KAAK,CAAC,OAAO;iBACtB;aACD,CAAC;QACH,KAAK,kBAAkB,CAAC;QACxB,KAAK,gBAAgB;YACpB,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QACjC;YACC,OAAO,EAAE,CAAC;IACZ,CAAC;AAAA,CACD;AAED,SAAS,8BAA8B,CAAC,KAA8B,EAA6B;IAClG,MAAM,IAAI,GAAG,KAAK,CAAC,IAAc,CAAC;IAClC,QAAQ,IAAI,EAAE,CAAC;QACd,KAAK,OAAO;YACX,OAAO,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;QAClC,KAAK,YAAY;YAChB,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QACnE,KAAK,YAAY;YAChB,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QACvF,KAAK,UAAU;YACd,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QACzF,KAAK,gBAAgB;YACpB,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QACvE,KAAK,gBAAgB;YACpB,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QAC3F,KAAK,cAAc;YAClB,OAAO,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QAC7F,KAAK,gBAAgB;YACpB,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QACvE,KAAK,gBAAgB;YACpB,OAAO,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QAC3F,KAAK,cAAc;YAClB,OAAO,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;QAC/F,KAAK,MAAM;YACV,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACjD,KAAK,OAAO;YACX,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QAClD;YACC,OAAO,EAAE,CAAC;IACZ,CAAC;AAAA,CACD","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;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</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=\"spinner\" id=\"spinner\"></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(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){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(text){\nlet s=esc(text);\nconst blocks=[];\ns=s.replace(/\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\\\x00B'+(blocks.length-1)+'\\\\x00B'});\ns=s.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>');\ns=s.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g,'<strong>$1</strong>');\ns=s.replace(/\\\\*(.+?)\\\\*/g,'<em>$1</em>');\ns=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');\ns=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');\ns=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');\ns=s.replace(/^- (.+$)/gm,'<li>$1</li>');\ns=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');\ns=s.replace(/((?:<li>.*<\\\\/li>\\\\n?)+)/g,'<ul>$1</ul>');\ns=s.replace(/^(---+|\\\\*\\\\*\\\\*+|___+)$/gm,'<hr>');\ns=s.replace(/\\\\x00B(\\\\d+)\\\\x00B/g,(_,i)=>blocks[parseInt(i)]);\ns=s.replace(/\\\\n\\\\n/g,'<br><br>');\ns=s.replace(/\\\\n/g,'<br>');\nreturn s\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group \"+role;\nif(role===\"user\"){g.innerHTML='<div class=\"msg-label\">You</div><div class=\"msg user\">'+esc(text)+'</div>'}\nelse if(role===\"assistant\"){g.innerHTML='<div class=\"msg-label\">pi</div><div class=\"msg assistant\"></div>'}\nelse{g.innerHTML='<div class=\"msg system\">'+text+'</div>'}\nmsgs.appendChild(g);scrollDown();return g}\n\nfunction getAssistantDiv(){if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\")}return curAssistant.querySelector(\".msg\")}\n\nfunction startTool(e){\nconst el=document.createElement(\"div\");el.className=\"tool\";el.id=\"t\"+e.toolCallId;\nconst title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\").slice(0,80)):'');\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+title+'</span><span class=\"tool-arrow\">&#9654;</span></div><div class=\"tool-body\"></div>';\nel.querySelector(\".tool-header\").onclick=()=>el.classList.toggle(\"expanded\");\nmsgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}\n\nfunction updateTool(id,result,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\"),body=el.querySelector(\".tool-body\");\nif(isError){icon.className=\"tool-icon error\"}\nconst texts=result&&result.content?result.content.filter(c=>c&&c.type===\"text\").map(c=>c.text):[];\nif(texts.length){body.textContent=texts.join(\"\\\\n\");el.classList.add(\"expanded\",\"mini\")}}\n\nfunction finishTool(id,isError){\nconst el=toolEls[id];if(!el)return;\nconst icon=el.querySelector(\".tool-icon\");icon.className=\"tool-icon \"+(isError?\"error\":\"done\");\nconst body=el.querySelector(\".tool-body\");if(isError&&!body.textContent.trim())body.textContent=\"(no output)\"}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}\ncase\"thinking_delta\":{let th=document.getElementById(\"think\"+d.contentIndex);if(!th){th=document.createElement(\"div\");th.className=\"thinking\";th.id=\"think\"+d.contentIndex;msgs.appendChild(th)}th.textContent+=d.delta;scrollDown();break}\ncase\"tool_execution_start\":startTool(d);break;\ncase\"tool_execution_update\":updateTool(d.toolCallId,d.result,false);break;\ncase\"tool_execution_end\":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;\ncase\"agent_end\":{let s=\"\";if(d.usage){s=\" &middot; \"+fmt(d.usage.input)+\" in &middot \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" &middot; $\"+d.usage.cost.total.toFixed(4)}addMsg(\"system\",\"Done\"+s);break}\ncase\"compaction\":addMsg(\"system\",\"Compacting...\");break;\ncase\"error\":addMsg(\"system\",\"Error: \"+esc(d.message));break;\ncase\"text_start\":case\"thinking_start\":case\"text_end\":case\"thinking_end\":case\"toolcall_start\":case\"toolcall_end\":case\"toolcall_delta\":break}}\n\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",text);busy=true;send.disabled=true;spinner.className=\"spinner active\";\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\nif(!r.ok){addMsg(\"system\",\"Error: \"+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){addMsg(\"system\",\"Error: \"+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className=\"spinner\"}};\n\nfetch(\"/api/messages\").then(r=>r.json()).then(events=>{\nfor(const e of events){\nif(e.type===\"user\"){addMsg(\"user\",e.text)}\nelse{handle(e)}\n}\n}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).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.22",
3
+ "version": "0.75.23",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.75.22",
9
+ "version": "0.75.23",
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.22",
4
+ "version": "0.75.23",
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.22",
4
+ "version": "0.75.23",
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.22",
3
+ "version": "1.5.23",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.5.22",
9
+ "version": "1.5.23",
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.22",
4
+ "version": "1.5.23",
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.22",
3
+ "version": "0.75.23",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.75.22",
9
+ "version": "0.75.23",
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.22",
4
+ "version": "0.75.23",
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.22",
3
+ "version": "0.75.23",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openeryc/pi-coding-agent",
9
- "version": "0.75.22",
9
+ "version": "0.75.23",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@openeryc/pi-agent-core": "^0.75.22",
13
- "@openeryc/pi-ai": "^0.75.22",
14
- "@openeryc/pi-tui": "^0.75.22",
12
+ "@openeryc/pi-agent-core": "^0.75.23",
13
+ "@openeryc/pi-ai": "^0.75.23",
14
+ "@openeryc/pi-tui": "^0.75.23",
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.22",
703
- "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.22.tgz",
702
+ "version": "0.75.23",
703
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.23.tgz",
704
704
  "license": "MIT",
705
705
  "dependencies": {
706
- "@openeryc/pi-ai": "^0.75.22",
706
+ "@openeryc/pi-ai": "^0.75.23",
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.22",
717
- "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.22.tgz",
716
+ "version": "0.75.23",
717
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.23.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.22",
739
- "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.22.tgz",
738
+ "version": "0.75.23",
739
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.23.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.22",
3
+ "version": "0.75.23",
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.22",
43
- "@openeryc/pi-ai": "^0.75.22",
44
- "@openeryc/pi-tui": "^0.75.22",
42
+ "@openeryc/pi-agent-core": "^0.75.23",
43
+ "@openeryc/pi-ai": "^0.75.23",
44
+ "@openeryc/pi-tui": "^0.75.23",
45
45
  "@silvia-odwyer/photon-node": "0.3.4",
46
46
  "chalk": "5.6.2",
47
47
  "cross-spawn": "7.0.6",