@openeryc/pi-coding-agent 0.75.16 → 0.75.18

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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.75.18] - 2026-05-21
4
+
5
+ ## [0.75.17] - 2026-05-21
6
+
3
7
  ## [0.75.16] - 2026-05-21
4
8
 
5
9
  ## [0.75.15] - 2026-05-21
@@ -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;AA6H/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmF5E","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\">\n<title>pi</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font:13px/1.5 system-ui,monospace;background:#0d1117;color:#c9d1d9;height:100vh;display:flex;flex-direction:column}\n#header{background:#161b22;border-bottom:1px solid #30363d;padding:8px 16px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0}\n#header span{color:#8b949e;font-size:12px}\n#msgs{flex:1;overflow-y:auto;padding:16px}\n.msg{margin-bottom:12px;max-width:85%}\n.msg.user{background:#1f6feb22;border:1px solid #1f6feb44;border-radius:8px 8px 0 8px;padding:8px 12px;margin-left:auto}\n.msg.assistant{margin-right:auto}\n.msg.system{color:#8b949e;font-size:11px;margin:4px auto;text-align:center;max-width:100%}\n.tool{border:1px solid #30363d;border-radius:6px;margin:6px 0;overflow:hidden}\n.tool-header{padding:6px 10px;background:#161b22;cursor:pointer;font-size:12px;display:flex;justify-content:space-between}\n.tool-header:hover{background:#21262d}\n.tool-body{padding:6px 10px;background:#0d1117;font-size:12px;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;display:none}\n.tool.expanded .tool-body{display:block}\n.tool-arrow{color:#8b949e;transition:.2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-pending{border-color:#d2992266}\n.tool-error .tool-header{color:#f85149}\n.thinking{color:#8b949e;font-size:12px;border-left:2px solid #30363d;padding:4px 8px;margin:4px 0;font-style:italic}\n#input-area{border-top:1px solid #30363d;padding:12px 16px;background:#161b22;flex-shrink:0}\n#input-area form{display:flex;gap:8px}\n#prompt{flex:1;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:8px 12px;color:#c9d1d9;font:13px system-ui,monospace;outline:none}\n#prompt:focus{border-color:#1f6feb}\nbutton{background:#238636;color:#fff;border:none;border-radius:6px;padding:8px 16px;cursor:pointer;font:13px system-ui;font-weight:500}\nbutton:hover{background:#2ea043}\nbutton:disabled{opacity:.5;cursor:default}\n</style>\n</head>\n<body>\n<div id=\"header\"><b>pi</b><span id=\"stats\"></span></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\"><input id=\"prompt\" type=\"text\" placeholder=\"Type a message...\" autofocus><button id=\"send\">Send</button></form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),send=document.getElementById(\"send\"),stats=document.getElementById(\"stats\");\nlet busy=false,toolEls={},curAssistant=null,curTextIdx=null,curThinkIdx=null;\nfunction addMsg(cls,html){const d=document.createElement(\"div\");d.className=\"msg \"+cls;d.innerHTML=html;msgs.appendChild(d);msgs.scrollTop=msgs.scrollHeight;return d}\nfunction addSystem(text){addMsg(\"system\",text)}\nfunction fmtTokens(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}\nfunction handleData(e){\nswitch(e.type){\ncase\"agent_start\":curAssistant=null;curTextIdx=null;curThinkIdx=null;break;\ncase\"text_start\":curTextIdx=e.contentIndex;if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\");curTextIdx=null}break;\ncase\"text_delta\":if(!curAssistant)curAssistant=addMsg(\"assistant\",\"\");curAssistant.innerHTML+=esc(e.delta).replace(/\\\\n/g,\"<br>\");msgs.scrollTop=msgs.scrollHeight;break;\ncase\"thinking_start\":{const d=document.createElement(\"div\");d.className=\"thinking\";d.id=\"think\"+e.contentIndex;msgs.appendChild(d);break}\ncase\"thinking_delta\":{const d=document.getElementById(\"think\"+e.contentIndex);if(d)d.textContent+=e.delta;msgs.scrollTop=msgs.scrollHeight;break}\ncase\"toolcall_start\":break;\ncase\"toolcall_end\":break;\ncase\"tool_execution_start\":{const el=document.createElement(\"div\");el.className=\"tool tool-collapsed tool-pending\";el.id=\"t\"+e.toolCallId;const h=document.createElement(\"div\");h.className=\"tool-header\";h.innerHTML='<span>'+esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||JSON.stringify(e.args).slice(0,60))):'')+'</span><span class=\"tool-arrow\">&#9654;</span>';h.onclick=()=>el.classList.toggle(\"expanded\");el.appendChild(h);const b=document.createElement(\"div\");b.className=\"tool-body\";el.appendChild(b);msgs.appendChild(el);toolEls[e.toolCallId]=el;msgs.scrollTop=msgs.scrollHeight;break}\ncase\"tool_execution_update\":{const el=toolEls[e.toolCallId];if(el){const b=el.querySelector(\".tool-body\");if(b&&e.result){const texts=Array.isArray(e.result)?e.result:e.result.content||[];b.innerText=texts.filter(c=>c&&c.type===\"text\").map(c=>c.text).join(\"\");el.classList.add(\"expanded\")}}break}\ncase\"tool_execution_end\":{const el=toolEls[e.toolCallId];if(el){el.classList.remove(\"tool-pending\");if(e.isError)el.classList.add(\"tool-error\");const b=el.querySelector(\".tool-body\");if(b&&e.result){const texts=e.result.content||[];b.innerText=texts.filter(c=>c&&c.type===\"text\").map(c=>c.text).join(\"\")}el.classList.add(\"expanded\")}break}\ncase\"agent_end\":{let usage=\"\";if(e.usage){usage=\" — ↑\"+fmtTokens(e.usage.input)+\" ↓\"+fmtTokens(e.usage.output);if(e.usage.cost)usage+=\" $\"+e.usage.cost.total.toFixed(4)}addSystem(\"Done\"+usage);break}\ncase\"compaction\":addSystem(\"Compacting...\");break;\ncase\"error\":addSystem(\"Error: \"+esc(e.message));break;}};\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",esc(text));busy=true;send.disabled=true;\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\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{handleData(JSON.parse(line))}catch{}}}}\ncatch(err){addSystem(\"Error: \"+err.message)}finally{busy=false;send.disabled=false}};\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{stats.textContent=\"sessions: \"+s.sessions+\" | $\"+s.cost.toFixed(2)}).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\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(\" (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;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"]}
@@ -47,72 +47,188 @@ const HTML = `<!DOCTYPE html>
47
47
  <html lang="en">
48
48
  <head>
49
49
  <meta charset="UTF-8">
50
- <meta name="viewport" content="width=device-width,initial-scale=1">
50
+ <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
51
51
  <title>pi</title>
52
52
  <style>
53
- *{margin:0;padding:0;box-sizing:border-box}
54
- body{font:13px/1.5 system-ui,monospace;background:#0d1117;color:#c9d1d9;height:100vh;display:flex;flex-direction:column}
55
- #header{background:#161b22;border-bottom:1px solid #30363d;padding:8px 16px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0}
56
- #header span{color:#8b949e;font-size:12px}
57
- #msgs{flex:1;overflow-y:auto;padding:16px}
58
- .msg{margin-bottom:12px;max-width:85%}
59
- .msg.user{background:#1f6feb22;border:1px solid #1f6feb44;border-radius:8px 8px 0 8px;padding:8px 12px;margin-left:auto}
60
- .msg.assistant{margin-right:auto}
61
- .msg.system{color:#8b949e;font-size:11px;margin:4px auto;text-align:center;max-width:100%}
62
- .tool{border:1px solid #30363d;border-radius:6px;margin:6px 0;overflow:hidden}
63
- .tool-header{padding:6px 10px;background:#161b22;cursor:pointer;font-size:12px;display:flex;justify-content:space-between}
64
- .tool-header:hover{background:#21262d}
65
- .tool-body{padding:6px 10px;background:#0d1117;font-size:12px;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;display:none}
66
- .tool.expanded .tool-body{display:block}
67
- .tool-arrow{color:#8b949e;transition:.2s}
53
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
54
+ html{font-size:14px;-webkit-text-size-adjust:100%}
55
+ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}
56
+ #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}
57
+ #header-left{display:flex;align-items:center;gap:10px}
58
+ #header h1{font-size:14px;font-weight:600;color:#e6edf3}
59
+ #session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
60
+ #header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}
61
+ #msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}
62
+ #msgs::-webkit-scrollbar{width:6px}
63
+ #msgs::-webkit-scrollbar-track{background:transparent}
64
+ #msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}
65
+ .msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}
66
+ .msg-group.user{max-width:82%;align-self:flex-end}
67
+ @keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
68
+ .msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}
69
+ .msg-group.user .msg-label{color:#7ec9ff;text-align:right}
70
+ .msg-group.assistant .msg-label{color:#7d8590}
71
+ .msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}
72
+ .msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}
73
+ .msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}
74
+ .msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}
75
+ .msg p{margin:0 0 6px}
76
+ .msg p:last-child{margin-bottom:0}
77
+ .msg strong{color:#f2a65a;font-weight:600}
78
+ .msg em{color:#c9d1d9;font-style:italic}
79
+ .msg code{font-family:"JetBrains Mono","Fira Code",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}
80
+ .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}
81
+ .msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}
82
+ .msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}
83
+ .msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}
84
+ .msg h2{font-size:15px}
85
+ .msg h3{font-size:13.5px;color:#c9d1d9}
86
+ .msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}
87
+ .msg li{margin:2px 0}
88
+ .msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}
89
+ .tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}
90
+ .tool:hover{border-color:#30363d}
91
+ .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}
92
+ .tool-header:hover{background:#161b22}
93
+ .tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}
94
+ .tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}
95
+ .tool-icon.done{background:#3fb950}
96
+ .tool-icon.error{background:#f85149}
97
+ @keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
98
+ .tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
99
+ .tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}
68
100
  .tool.expanded .tool-arrow{transform:rotate(90deg)}
69
- .tool-pending{border-color:#d2992266}
70
- .tool-error .tool-header{color:#f85149}
71
- .thinking{color:#8b949e;font-size:12px;border-left:2px solid #30363d;padding:4px 8px;margin:4px 0;font-style:italic}
72
- #input-area{border-top:1px solid #30363d;padding:12px 16px;background:#161b22;flex-shrink:0}
73
- #input-area form{display:flex;gap:8px}
74
- #prompt{flex:1;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:8px 12px;color:#c9d1d9;font:13px system-ui,monospace;outline:none}
75
- #prompt:focus{border-color:#1f6feb}
76
- button{background:#238636;color:#fff;border:none;border-radius:6px;padding:8px 16px;cursor:pointer;font:13px system-ui;font-weight:500}
77
- button:hover{background:#2ea043}
78
- button:disabled{opacity:.5;cursor:default}
101
+ .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}
102
+ .tool-body::-webkit-scrollbar{width:4px;height:4px}
103
+ .tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}
104
+ .tool.expanded .tool-body{display:block}
105
+ .tool.mini .tool-body{max-height:12vh}
106
+ .thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}
107
+ #input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}
108
+ #input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}
109
+ #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}
110
+ #prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}
111
+ #prompt::placeholder{color:#484f58}
112
+ button{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}
113
+ button:hover{background:#2b83ff}
114
+ button:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}
115
+ .btn-mini{font-size:11px;padding:4px 10px;background:#21262d}
116
+ .btn-mini:hover{background:#30363d}
117
+ .spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}
118
+ .spinner.active{display:inline-block}
119
+ @keyframes spin{to{transform:rotate(360deg)}}
120
+ @media(max-width:600px){
121
+ html{font-size:13px}
122
+ #msgs{padding:10px;gap:10px}
123
+ .msg-group{max-width:94%}
124
+ .msg-group.user{max-width:92%}
125
+ #input-area{padding:8px 10px}
126
+ #prompt{padding:7px 10px}
127
+ button{padding:7px 12px}
128
+ }
79
129
  </style>
80
130
  </head>
81
131
  <body>
82
- <div id="header"><b>pi</b><span id="stats"></span></div>
132
+ <div id="header">
133
+ <div id="header-left"><h1>pi</h1><span id="session-info"></span></div>
134
+ <div id="header-stats"><span id="stat-sessions"></span><span id="stat-cost"></span></div>
135
+ </div>
83
136
  <div id="msgs"></div>
84
137
  <div id="input-area">
85
- <form id="f" autocomplete="off"><input id="prompt" type="text" placeholder="Type a message..." autofocus><button id="send">Send</button></form>
138
+ <form id="f" autocomplete="off">
139
+ <input id="prompt" type="text" placeholder="Message pi..." autofocus autocomplete="off">
140
+ <button id="send">Send</button>
141
+ <span class="spinner" id="spinner"></span>
142
+ </form>
86
143
  </div>
87
144
  <script>
88
- const msgs=document.getElementById("msgs"),f=document.getElementById("f"),prompt=document.getElementById("prompt"),send=document.getElementById("send"),stats=document.getElementById("stats");
89
- let busy=false,toolEls={},curAssistant=null,curTextIdx=null,curThinkIdx=null;
90
- function addMsg(cls,html){const d=document.createElement("div");d.className="msg "+cls;d.innerHTML=html;msgs.appendChild(d);msgs.scrollTop=msgs.scrollHeight;return d}
91
- function addSystem(text){addMsg("system",text)}
92
- function fmtTokens(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+"k";return Math.round(n/1e3)+"k"}
145
+ const msgs=document.getElementById("msgs"),f=document.getElementById("f"),prompt=document.getElementById("prompt"),
146
+ send=document.getElementById("send"),spinner=document.getElementById("spinner"),
147
+ statSessions=document.getElementById("stat-sessions"),statCost=document.getElementById("stat-cost"),
148
+ sessionInfo=document.getElementById("session-info");
149
+ let busy=false,toolEls={},curAssistant=null;
150
+
151
+ function scrollDown(){msgs.scrollTop=msgs.scrollHeight}
93
152
  function esc(s){if(!s)return"";return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
94
- function handleData(e){
95
- switch(e.type){
96
- case"agent_start":curAssistant=null;curTextIdx=null;curThinkIdx=null;break;
97
- case"text_start":curTextIdx=e.contentIndex;if(!curAssistant){curAssistant=addMsg("assistant","");curTextIdx=null}break;
98
- case"text_delta":if(!curAssistant)curAssistant=addMsg("assistant","");curAssistant.innerHTML+=esc(e.delta).replace(/\\n/g,"<br>");msgs.scrollTop=msgs.scrollHeight;break;
99
- case"thinking_start":{const d=document.createElement("div");d.className="thinking";d.id="think"+e.contentIndex;msgs.appendChild(d);break}
100
- case"thinking_delta":{const d=document.getElementById("think"+e.contentIndex);if(d)d.textContent+=e.delta;msgs.scrollTop=msgs.scrollHeight;break}
101
- case"toolcall_start":break;
102
- case"toolcall_end":break;
103
- case"tool_execution_start":{const el=document.createElement("div");el.className="tool tool-collapsed tool-pending";el.id="t"+e.toolCallId;const h=document.createElement("div");h.className="tool-header";h.innerHTML='<span>'+esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||JSON.stringify(e.args).slice(0,60))):'')+'</span><span class="tool-arrow">&#9654;</span>';h.onclick=()=>el.classList.toggle("expanded");el.appendChild(h);const b=document.createElement("div");b.className="tool-body";el.appendChild(b);msgs.appendChild(el);toolEls[e.toolCallId]=el;msgs.scrollTop=msgs.scrollHeight;break}
104
- case"tool_execution_update":{const el=toolEls[e.toolCallId];if(el){const b=el.querySelector(".tool-body");if(b&&e.result){const texts=Array.isArray(e.result)?e.result:e.result.content||[];b.innerText=texts.filter(c=>c&&c.type==="text").map(c=>c.text).join("");el.classList.add("expanded")}}break}
105
- case"tool_execution_end":{const el=toolEls[e.toolCallId];if(el){el.classList.remove("tool-pending");if(e.isError)el.classList.add("tool-error");const b=el.querySelector(".tool-body");if(b&&e.result){const texts=e.result.content||[];b.innerText=texts.filter(c=>c&&c.type==="text").map(c=>c.text).join("")}el.classList.add("expanded")}break}
106
- case"agent_end":{let usage="";if(e.usage){usage=" — ↑"+fmtTokens(e.usage.input)+" ↓"+fmtTokens(e.usage.output);if(e.usage.cost)usage+=" $"+e.usage.cost.total.toFixed(4)}addSystem("Done"+usage);break}
107
- case"compaction":addSystem("Compacting...");break;
108
- case"error":addSystem("Error: "+esc(e.message));break;}};
109
- f.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value="";addMsg("user",esc(text));busy=true;send.disabled=true;
153
+ function fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+"k";return Math.round(n/1e3)+"k"}
154
+
155
+ function md(text){
156
+ let s=esc(text);
157
+ const blocks=[];
158
+ s=s.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\\n\`\`\`/g,(_,lang,code)=>{blocks.push('<pre><code>'+code+'</code></pre>');return'\\x00B'+(blocks.length-1)+'\\x00B'});
159
+ s=s.replace(/\`([^\`]+)\`/g,'<code>$1</code>');
160
+ s=s.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
161
+ s=s.replace(/\\*(.+?)\\*/g,'<em>$1</em>');
162
+ s=s.replace(/^### (.+$)/gm,'<h3>$1</h3>');
163
+ s=s.replace(/^## (.+$)/gm,'<h2>$1</h2>');
164
+ s=s.replace(/^# (.+$)/gm,'<h1>$1</h1>');
165
+ s=s.replace(/^- (.+$)/gm,'<li>$1</li>');
166
+ s=s.replace(/^> (.+$)/gm,'<blockquote>$1</blockquote>');
167
+ s=s.replace(/((?:<li>.*<\\/li>\\n?)+)/g,'<ul>$1</ul>');
168
+ s=s.replace(/^(---+|\\*\\*\\*+|___+)$/gm,'<hr>');
169
+ s=s.replace(/\\x00B(\\d+)\\x00B/g,(_,i)=>blocks[parseInt(i)]);
170
+ s=s.replace(/\\n\\n/g,'<br><br>');
171
+ s=s.replace(/\\n/g,'<br>');
172
+ return s
173
+ }
174
+
175
+ function addMsg(role,text){
176
+ const g=document.createElement("div");g.className="msg-group "+role;
177
+ if(role==="user"){g.innerHTML='<div class="msg-label">You</div><div class="msg user">'+esc(text)+'</div>'}
178
+ else if(role==="assistant"){g.innerHTML='<div class="msg-label">pi</div><div class="msg assistant"></div>'}
179
+ else{g.innerHTML='<div class="msg system">'+text+'</div>'}
180
+ msgs.appendChild(g);scrollDown();return g}
181
+
182
+ function getAssistantDiv(){if(!curAssistant){curAssistant=addMsg("assistant","")}return curAssistant.querySelector(".msg")}
183
+
184
+ function startTool(e){
185
+ const el=document.createElement("div");el.className="tool";el.id="t"+e.toolCallId;
186
+ const title=esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||"").slice(0,80)):'');
187
+ el.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>';
188
+ el.querySelector(".tool-header").onclick=()=>el.classList.toggle("expanded");
189
+ msgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}
190
+
191
+ function updateTool(id,result,isError){
192
+ const el=toolEls[id];if(!el)return;
193
+ const icon=el.querySelector(".tool-icon"),body=el.querySelector(".tool-body");
194
+ if(isError){icon.className="tool-icon error"}
195
+ const texts=result&&result.content?result.content.filter(c=>c&&c.type==="text").map(c=>c.text):[];
196
+ if(texts.length){body.textContent=texts.join("\\n");el.classList.add("expanded","mini")}}
197
+
198
+ function finishTool(id,isError){
199
+ const el=toolEls[id];if(!el)return;
200
+ const icon=el.querySelector(".tool-icon");icon.className="tool-icon "+(isError?"error":"done");
201
+ const body=el.querySelector(".tool-body");if(isError&&!body.textContent.trim())body.textContent="(no output)"}
202
+
203
+ function handle(d){
204
+ switch(d.type){
205
+ case"agent_start":curAssistant=null;break;
206
+ case"text_delta":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}
207
+ case"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}
208
+ case"tool_execution_start":startTool(d);break;
209
+ case"tool_execution_update":updateTool(d.toolCallId,d.result,false);break;
210
+ case"tool_execution_end":finishTool(d.toolCallId,d.isError);if(d.result)updateTool(d.toolCallId,d.result,d.isError);break;
211
+ case"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}
212
+ case"compaction":addMsg("system","Compacting...");break;
213
+ case"error":addMsg("system","Error: "+esc(d.message));break;
214
+ case"text_start":case"thinking_start":case"text_end":case"thinking_end":case"toolcall_start":case"toolcall_end":case"toolcall_delta":break}}
215
+
216
+ f.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";
110
217
  try{const r=await fetch("/api/prompt",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({text})});
218
+ if(!r.ok){addMsg("system","Error: "+r.status);return}
111
219
  const reader=r.body.getReader(),decoder=new TextDecoder();let buf="";
112
220
  while(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.decode(value,{stream:true});const lines=buf.split("\\n");buf=lines.pop()||"";
113
- for(const line of lines){if(!line.trim())continue;try{handleData(JSON.parse(line))}catch{}}}}
114
- catch(err){addSystem("Error: "+err.message)}finally{busy=false;send.disabled=false}};
115
- fetch("/api/stats").then(r=>r.json()).then(s=>{stats.textContent="sessions: "+s.sessions+" | $"+s.cost.toFixed(2)}).catch(()=>{});
221
+ for(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}
222
+ catch(err){addMsg("system","Error: "+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className="spinner"}};
223
+
224
+ fetch("/api/stats").then(r=>r.json()).then(s=>{
225
+ statSessions.textContent=s.sessions+" sessions";
226
+ statCost.textContent="$"+s.cost.toFixed(2);
227
+ }).catch(()=>{});
228
+
229
+ fetch("/api/session-info").then(r=>r.json()).then(s=>{
230
+ sessionInfo.textContent=s.id?("session: "+s.id.slice(0,8)+(s.name?" | "+s.name:"")):"";
231
+ }).catch(()=>{});
116
232
  </script>
117
233
  </body>
118
234
  </html>`;
@@ -162,6 +278,15 @@ export async function runWebMode(runtime) {
162
278
  res.end(JSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }));
163
279
  return;
164
280
  }
281
+ if (req.method === "GET" && url === "/api/session-info") {
282
+ res.writeHead(200, { "content-type": "application/json" });
283
+ res.end(JSON.stringify({
284
+ id: session.sessionId,
285
+ name: session.sessionManager.getSessionName(),
286
+ file: session.sessionFile,
287
+ }));
288
+ return;
289
+ }
165
290
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
166
291
  res.end(HTML);
167
292
  });
@@ -180,6 +305,7 @@ export async function runWebMode(runtime) {
180
305
  if (lan !== "127.0.0.1") {
181
306
  console.log(` LAN: http://${lan}:${addr.port}`);
182
307
  }
308
+ console.log(` session: ${session.sessionId.slice(0, 8)}`);
183
309
  console.log(" (listening on all interfaces)");
184
310
  if (PASSWORD) {
185
311
  console.log(` auth: Basic (user "pi", password from PI_WEB_PASSWORD)`);
@@ -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\">\n<title>pi</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font:13px/1.5 system-ui,monospace;background:#0d1117;color:#c9d1d9;height:100vh;display:flex;flex-direction:column}\n#header{background:#161b22;border-bottom:1px solid #30363d;padding:8px 16px;display:flex;justify-content:space-between;align-items:center;flex-shrink:0}\n#header span{color:#8b949e;font-size:12px}\n#msgs{flex:1;overflow-y:auto;padding:16px}\n.msg{margin-bottom:12px;max-width:85%}\n.msg.user{background:#1f6feb22;border:1px solid #1f6feb44;border-radius:8px 8px 0 8px;padding:8px 12px;margin-left:auto}\n.msg.assistant{margin-right:auto}\n.msg.system{color:#8b949e;font-size:11px;margin:4px auto;text-align:center;max-width:100%}\n.tool{border:1px solid #30363d;border-radius:6px;margin:6px 0;overflow:hidden}\n.tool-header{padding:6px 10px;background:#161b22;cursor:pointer;font-size:12px;display:flex;justify-content:space-between}\n.tool-header:hover{background:#21262d}\n.tool-body{padding:6px 10px;background:#0d1117;font-size:12px;white-space:pre-wrap;word-break:break-all;max-height:200px;overflow-y:auto;display:none}\n.tool.expanded .tool-body{display:block}\n.tool-arrow{color:#8b949e;transition:.2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-pending{border-color:#d2992266}\n.tool-error .tool-header{color:#f85149}\n.thinking{color:#8b949e;font-size:12px;border-left:2px solid #30363d;padding:4px 8px;margin:4px 0;font-style:italic}\n#input-area{border-top:1px solid #30363d;padding:12px 16px;background:#161b22;flex-shrink:0}\n#input-area form{display:flex;gap:8px}\n#prompt{flex:1;background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:8px 12px;color:#c9d1d9;font:13px system-ui,monospace;outline:none}\n#prompt:focus{border-color:#1f6feb}\nbutton{background:#238636;color:#fff;border:none;border-radius:6px;padding:8px 16px;cursor:pointer;font:13px system-ui;font-weight:500}\nbutton:hover{background:#2ea043}\nbutton:disabled{opacity:.5;cursor:default}\n</style>\n</head>\n<body>\n<div id=\"header\"><b>pi</b><span id=\"stats\"></span></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\"><input id=\"prompt\" type=\"text\" placeholder=\"Type a message...\" autofocus><button id=\"send\">Send</button></form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),send=document.getElementById(\"send\"),stats=document.getElementById(\"stats\");\nlet busy=false,toolEls={},curAssistant=null,curTextIdx=null,curThinkIdx=null;\nfunction addMsg(cls,html){const d=document.createElement(\"div\");d.className=\"msg \"+cls;d.innerHTML=html;msgs.appendChild(d);msgs.scrollTop=msgs.scrollHeight;return d}\nfunction addSystem(text){addMsg(\"system\",text)}\nfunction fmtTokens(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}\nfunction handleData(e){\nswitch(e.type){\ncase\"agent_start\":curAssistant=null;curTextIdx=null;curThinkIdx=null;break;\ncase\"text_start\":curTextIdx=e.contentIndex;if(!curAssistant){curAssistant=addMsg(\"assistant\",\"\");curTextIdx=null}break;\ncase\"text_delta\":if(!curAssistant)curAssistant=addMsg(\"assistant\",\"\");curAssistant.innerHTML+=esc(e.delta).replace(/\\\\n/g,\"<br>\");msgs.scrollTop=msgs.scrollHeight;break;\ncase\"thinking_start\":{const d=document.createElement(\"div\");d.className=\"thinking\";d.id=\"think\"+e.contentIndex;msgs.appendChild(d);break}\ncase\"thinking_delta\":{const d=document.getElementById(\"think\"+e.contentIndex);if(d)d.textContent+=e.delta;msgs.scrollTop=msgs.scrollHeight;break}\ncase\"toolcall_start\":break;\ncase\"toolcall_end\":break;\ncase\"tool_execution_start\":{const el=document.createElement(\"div\");el.className=\"tool tool-collapsed tool-pending\";el.id=\"t\"+e.toolCallId;const h=document.createElement(\"div\");h.className=\"tool-header\";h.innerHTML='<span>'+esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||JSON.stringify(e.args).slice(0,60))):'')+'</span><span class=\"tool-arrow\">&#9654;</span>';h.onclick=()=>el.classList.toggle(\"expanded\");el.appendChild(h);const b=document.createElement(\"div\");b.className=\"tool-body\";el.appendChild(b);msgs.appendChild(el);toolEls[e.toolCallId]=el;msgs.scrollTop=msgs.scrollHeight;break}\ncase\"tool_execution_update\":{const el=toolEls[e.toolCallId];if(el){const b=el.querySelector(\".tool-body\");if(b&&e.result){const texts=Array.isArray(e.result)?e.result:e.result.content||[];b.innerText=texts.filter(c=>c&&c.type===\"text\").map(c=>c.text).join(\"\");el.classList.add(\"expanded\")}}break}\ncase\"tool_execution_end\":{const el=toolEls[e.toolCallId];if(el){el.classList.remove(\"tool-pending\");if(e.isError)el.classList.add(\"tool-error\");const b=el.querySelector(\".tool-body\");if(b&&e.result){const texts=e.result.content||[];b.innerText=texts.filter(c=>c&&c.type===\"text\").map(c=>c.text).join(\"\")}el.classList.add(\"expanded\")}break}\ncase\"agent_end\":{let usage=\"\";if(e.usage){usage=\" — ↑\"+fmtTokens(e.usage.input)+\" ↓\"+fmtTokens(e.usage.output);if(e.usage.cost)usage+=\" $\"+e.usage.cost.total.toFixed(4)}addSystem(\"Done\"+usage);break}\ncase\"compaction\":addSystem(\"Compacting...\");break;\ncase\"error\":addSystem(\"Error: \"+esc(e.message));break;}};\nf.onsubmit=async e=>{e.preventDefault();if(busy)return;const text=prompt.value.trim();if(!text)return;prompt.value=\"\";addMsg(\"user\",esc(text));busy=true;send.disabled=true;\ntry{const r=await fetch(\"/api/prompt\",{method:\"POST\",headers:{\"content-type\":\"application/json\"},body:JSON.stringify({text})});\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{handleData(JSON.parse(line))}catch{}}}}\ncatch(err){addSystem(\"Error: \"+err.message)}finally{busy=false;send.disabled=false}};\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{stats.textContent=\"sessions: \"+s.sessions+\" | $\"+s.cost.toFixed(2)}).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\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(\" (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":"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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,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.75.16",
3
+ "version": "0.75.18",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.75.16",
9
+ "version": "0.75.18",
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.16",
4
+ "version": "0.75.18",
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.16",
4
+ "version": "0.75.18",
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.16",
3
+ "version": "1.5.18",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.5.16",
9
+ "version": "1.5.18",
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.16",
4
+ "version": "1.5.18",
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.16",
3
+ "version": "0.75.18",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.75.16",
9
+ "version": "0.75.18",
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.16",
4
+ "version": "0.75.18",
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.16",
3
+ "version": "0.75.18",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openeryc/pi-coding-agent",
9
- "version": "0.75.16",
9
+ "version": "0.75.18",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@openeryc/pi-agent-core": "^0.75.16",
13
- "@openeryc/pi-ai": "^0.75.16",
14
- "@openeryc/pi-tui": "^0.75.16",
12
+ "@openeryc/pi-agent-core": "^0.75.18",
13
+ "@openeryc/pi-ai": "^0.75.18",
14
+ "@openeryc/pi-tui": "^0.75.18",
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.16",
703
- "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.16.tgz",
702
+ "version": "0.75.18",
703
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.18.tgz",
704
704
  "license": "MIT",
705
705
  "dependencies": {
706
- "@openeryc/pi-ai": "^0.75.16",
706
+ "@openeryc/pi-ai": "^0.75.18",
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.16",
717
- "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.16.tgz",
716
+ "version": "0.75.18",
717
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.18.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.16",
739
- "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.16.tgz",
738
+ "version": "0.75.18",
739
+ "resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.18.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.16",
3
+ "version": "0.75.18",
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.16",
43
- "@openeryc/pi-ai": "^0.75.16",
44
- "@openeryc/pi-tui": "^0.75.16",
42
+ "@openeryc/pi-agent-core": "^0.75.18",
43
+ "@openeryc/pi-ai": "^0.75.18",
44
+ "@openeryc/pi-tui": "^0.75.18",
45
45
  "@silvia-odwyer/photon-node": "0.3.4",
46
46
  "chalk": "5.6.2",
47
47
  "cross-spawn": "7.0.6",