@openeryc/pi-coding-agent 0.75.17 → 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 +2 -0
- package/dist/modes/web/web-mode.d.ts.map +1 -1
- package/dist/modes/web/web-mode.js +93 -43
- package/dist/modes/web/web-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -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;AAyM/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,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:10px 20px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:12px}\n#header h1{font-size:15px;font-weight:600;color:#e6edf3;letter-spacing:-.3px}\n#header-stats{display:flex;gap:16px;font-size:12px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:16px}\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:4px;animation:fadeIn .15s ease}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}\n.msg{max-width:82%;padding:10px 14px;border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word}\n.msg.user{background:#1b4a8b;color:#e6edf3;align-self:flex-end;border-bottom-right-radius:4px}\n.msg.user .msg-label{color:#7ec9ff;text-align:right}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;align-self:flex-start;border-bottom-left-radius:4px;border:1px solid #21262d}\n.msg.assistant .msg-label{color:#7d8590}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#161b22;padding:1px 5px;border-radius:3px;border:1px solid #30363d}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.45}\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:8px 12px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:8px;height:8px;border-radius:50%;flex-shrink:0;opacity:.6}\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:12.5px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:10px;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:10px 12px;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}\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:6px 12px;margin:0 0 4px;border-left:2px solid #30363d;font-size:12px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:14px 20px;flex-shrink:0}\n#input-area form{display:flex;gap:10px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:10px 14px;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:10px 18px;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.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:12px}\n.msg{max-width:92%}\n#input-area{padding:10px 14px}\n#prompt{padding:8px 12px}\nbutton{padding:8px 14px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\"><h1>pi</h1><div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"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\");\nlet busy=false,toolEls={},curAssistant=null,curToolSeq=0;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\nfunction renderText(text){\nreturn esc(text).replace(/\\\\n\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`\\\\n/g,(_,lang,code)=>'</p><pre>'+esc(code)+'</pre><p>')\n.replace(/\\\\n\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>'</p><pre>'+esc(code)+'</pre><p>')\n.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>').replace(/\\\\n/g,'<br>')\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group\";\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;\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\")).slice(0,80):'')+'</span><span class=\"tool-arrow\">▶</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}\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\");\nif(isError&&el.querySelector(\".tool-body\").textContent===\"\"){el.querySelector(\".tool-body\").textContent=\"(no output)\"}\n}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=renderText(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=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+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</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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\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\">▶</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=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+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"]}
|
|
@@ -53,65 +53,86 @@ const HTML = `<!DOCTYPE html>
|
|
|
53
53
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
54
54
|
html{font-size:14px;-webkit-text-size-adjust:100%}
|
|
55
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:
|
|
57
|
-
#header
|
|
58
|
-
#header-
|
|
59
|
-
#
|
|
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}
|
|
60
62
|
#msgs::-webkit-scrollbar{width:6px}
|
|
61
63
|
#msgs::-webkit-scrollbar-track{background:transparent}
|
|
62
64
|
#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}
|
|
63
|
-
.msg-group{display:flex;flex-direction:column;gap:
|
|
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}
|
|
64
67
|
@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
|
|
65
|
-
.msg-label{font-size:
|
|
66
|
-
.msg
|
|
67
|
-
.msg.
|
|
68
|
-
.msg.
|
|
69
|
-
.msg.
|
|
70
|
-
.msg.assistant
|
|
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}
|
|
71
74
|
.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}
|
|
72
|
-
.msg
|
|
73
|
-
.msg
|
|
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}
|
|
74
89
|
.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}
|
|
75
90
|
.tool:hover{border-color:#30363d}
|
|
76
|
-
.tool-header{display:flex;align-items:center;justify-content:space-between;padding:
|
|
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}
|
|
77
92
|
.tool-header:hover{background:#161b22}
|
|
78
|
-
.tool-icon{width:
|
|
93
|
+
.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
|
79
94
|
.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}
|
|
80
95
|
.tool-icon.done{background:#3fb950}
|
|
81
96
|
.tool-icon.error{background:#f85149}
|
|
82
97
|
@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}
|
|
83
|
-
.tool-title{flex:1;font-size:
|
|
84
|
-
.tool-arrow{color:#6e7681;font-size:
|
|
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}
|
|
85
100
|
.tool.expanded .tool-arrow{transform:rotate(90deg)}
|
|
86
|
-
.tool-body{font-family:"JetBrains Mono","Fira Code",monospace;font-size:12px;line-height:1.55;padding:10px
|
|
87
|
-
.tool-body::-webkit-scrollbar{width:4px}
|
|
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}
|
|
88
103
|
.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}
|
|
89
104
|
.tool.expanded .tool-body{display:block}
|
|
90
105
|
.tool.mini .tool-body{max-height:12vh}
|
|
91
|
-
.thinking{padding:
|
|
92
|
-
#input-area{background:#0d121a;border-top:1px solid #21262d;padding:
|
|
93
|
-
#input-area form{display:flex;gap:
|
|
94
|
-
#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:
|
|
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}
|
|
95
110
|
#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}
|
|
96
111
|
#prompt::placeholder{color:#484f58}
|
|
97
|
-
button{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:
|
|
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}
|
|
98
113
|
button:hover{background:#2b83ff}
|
|
99
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}
|
|
100
117
|
.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}
|
|
101
118
|
.spinner.active{display:inline-block}
|
|
102
119
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
103
120
|
@media(max-width:600px){
|
|
104
121
|
html{font-size:13px}
|
|
105
|
-
#msgs{padding:
|
|
106
|
-
.msg{max-width:
|
|
107
|
-
|
|
108
|
-
#
|
|
109
|
-
|
|
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}
|
|
110
128
|
}
|
|
111
129
|
</style>
|
|
112
130
|
</head>
|
|
113
131
|
<body>
|
|
114
|
-
<div id="header"
|
|
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>
|
|
115
136
|
<div id="msgs"></div>
|
|
116
137
|
<div id="input-area">
|
|
117
138
|
<form id="f" autocomplete="off">
|
|
@@ -123,20 +144,36 @@ button{padding:8px 14px}
|
|
|
123
144
|
<script>
|
|
124
145
|
const msgs=document.getElementById("msgs"),f=document.getElementById("f"),prompt=document.getElementById("prompt"),
|
|
125
146
|
send=document.getElementById("send"),spinner=document.getElementById("spinner"),
|
|
126
|
-
statSessions=document.getElementById("stat-sessions"),statCost=document.getElementById("stat-cost")
|
|
127
|
-
|
|
147
|
+
statSessions=document.getElementById("stat-sessions"),statCost=document.getElementById("stat-cost"),
|
|
148
|
+
sessionInfo=document.getElementById("session-info");
|
|
149
|
+
let busy=false,toolEls={},curAssistant=null;
|
|
128
150
|
|
|
129
151
|
function scrollDown(){msgs.scrollTop=msgs.scrollHeight}
|
|
130
152
|
function esc(s){if(!s)return"";return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}
|
|
131
153
|
function fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+"k";return Math.round(n/1e3)+"k"}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
173
|
}
|
|
137
174
|
|
|
138
175
|
function addMsg(role,text){
|
|
139
|
-
const g=document.createElement("div");g.className="msg-group";
|
|
176
|
+
const g=document.createElement("div");g.className="msg-group "+role;
|
|
140
177
|
if(role==="user"){g.innerHTML='<div class="msg-label">You</div><div class="msg user">'+esc(text)+'</div>'}
|
|
141
178
|
else if(role==="assistant"){g.innerHTML='<div class="msg-label">pi</div><div class="msg assistant"></div>'}
|
|
142
179
|
else{g.innerHTML='<div class="msg system">'+text+'</div>'}
|
|
@@ -146,7 +183,8 @@ function getAssistantDiv(){if(!curAssistant){curAssistant=addMsg("assistant","")
|
|
|
146
183
|
|
|
147
184
|
function startTool(e){
|
|
148
185
|
const el=document.createElement("div");el.className="tool";el.id="t"+e.toolCallId;
|
|
149
|
-
|
|
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">▶</span></div><div class="tool-body"></div>';
|
|
150
188
|
el.querySelector(".tool-header").onclick=()=>el.classList.toggle("expanded");
|
|
151
189
|
msgs.appendChild(el);toolEls[e.toolCallId]=el;scrollDown()}
|
|
152
190
|
|
|
@@ -155,19 +193,17 @@ const el=toolEls[id];if(!el)return;
|
|
|
155
193
|
const icon=el.querySelector(".tool-icon"),body=el.querySelector(".tool-body");
|
|
156
194
|
if(isError){icon.className="tool-icon error"}
|
|
157
195
|
const texts=result&&result.content?result.content.filter(c=>c&&c.type==="text").map(c=>c.text):[];
|
|
158
|
-
if(texts.length){body.textContent=texts.join("\\n");el.classList.add("expanded","mini")}
|
|
159
|
-
}
|
|
196
|
+
if(texts.length){body.textContent=texts.join("\\n");el.classList.add("expanded","mini")}}
|
|
160
197
|
|
|
161
198
|
function finishTool(id,isError){
|
|
162
199
|
const el=toolEls[id];if(!el)return;
|
|
163
200
|
const icon=el.querySelector(".tool-icon");icon.className="tool-icon "+(isError?"error":"done");
|
|
164
|
-
|
|
165
|
-
}
|
|
201
|
+
const body=el.querySelector(".tool-body");if(isError&&!body.textContent.trim())body.textContent="(no output)"}
|
|
166
202
|
|
|
167
203
|
function handle(d){
|
|
168
204
|
switch(d.type){
|
|
169
205
|
case"agent_start":curAssistant=null;break;
|
|
170
|
-
case"text_delta":{const ad=getAssistantDiv();ad.innerHTML+=
|
|
206
|
+
case"text_delta":{const ad=getAssistantDiv();ad.innerHTML+=md(d.delta);scrollDown();break}
|
|
171
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}
|
|
172
208
|
case"tool_execution_start":startTool(d);break;
|
|
173
209
|
case"tool_execution_update":updateTool(d.toolCallId,d.result,false);break;
|
|
@@ -189,6 +225,10 @@ fetch("/api/stats").then(r=>r.json()).then(s=>{
|
|
|
189
225
|
statSessions.textContent=s.sessions+" sessions";
|
|
190
226
|
statCost.textContent="$"+s.cost.toFixed(2);
|
|
191
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(()=>{});
|
|
192
232
|
</script>
|
|
193
233
|
</body>
|
|
194
234
|
</html>`;
|
|
@@ -238,6 +278,15 @@ export async function runWebMode(runtime) {
|
|
|
238
278
|
res.end(JSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }));
|
|
239
279
|
return;
|
|
240
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
|
+
}
|
|
241
290
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
242
291
|
res.end(HTML);
|
|
243
292
|
});
|
|
@@ -256,6 +305,7 @@ export async function runWebMode(runtime) {
|
|
|
256
305
|
if (lan !== "127.0.0.1") {
|
|
257
306
|
console.log(` LAN: http://${lan}:${addr.port}`);
|
|
258
307
|
}
|
|
308
|
+
console.log(` session: ${session.sessionId.slice(0, 8)}`);
|
|
259
309
|
console.log(" (listening on all interfaces)");
|
|
260
310
|
if (PASSWORD) {
|
|
261
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,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:10px 20px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:12px}\n#header h1{font-size:15px;font-weight:600;color:#e6edf3;letter-spacing:-.3px}\n#header-stats{display:flex;gap:16px;font-size:12px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:16px}\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:4px;animation:fadeIn .15s ease}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px}\n.msg{max-width:82%;padding:10px 14px;border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word}\n.msg.user{background:#1b4a8b;color:#e6edf3;align-self:flex-end;border-bottom-right-radius:4px}\n.msg.user .msg-label{color:#7ec9ff;text-align:right}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;align-self:flex-start;border-bottom-left-radius:4px;border:1px solid #21262d}\n.msg.assistant .msg-label{color:#7d8590}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#161b22;padding:1px 5px;border-radius:3px;border:1px solid #30363d}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.45}\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:8px 12px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:8px;height:8px;border-radius:50%;flex-shrink:0;opacity:.6}\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:12.5px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:10px;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:10px 12px;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}\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:6px 12px;margin:0 0 4px;border-left:2px solid #30363d;font-size:12px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:14px 20px;flex-shrink:0}\n#input-area form{display:flex;gap:10px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:10px 14px;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:10px 18px;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.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:12px}\n.msg{max-width:92%}\n#input-area{padding:10px 14px}\n#prompt{padding:8px 12px}\nbutton{padding:8px 14px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\"><h1>pi</h1><div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div></div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"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\");\nlet busy=false,toolEls={},curAssistant=null,curToolSeq=0;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\nfunction fmt(n){if(n<1e3)return n;if(n<1e4)return(n/1e3).toFixed(1)+\"k\";return Math.round(n/1e3)+\"k\"}\nfunction renderText(text){\nreturn esc(text).replace(/\\\\n\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`\\\\n/g,(_,lang,code)=>'</p><pre>'+esc(code)+'</pre><p>')\n.replace(/\\\\n\\`\\`\\`(\\\\w*)\\\\n([\\\\s\\\\S]*?)\\\\n\\`\\`\\`/g,(_,lang,code)=>'</p><pre>'+esc(code)+'</pre><p>')\n.replace(/\\`([^\\`]+)\\`/g,'<code>$1</code>').replace(/\\\\n/g,'<br>')\n}\n\nfunction addMsg(role,text){\nconst g=document.createElement(\"div\");g.className=\"msg-group\";\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;\nel.innerHTML='<div class=\"tool-header\"><span class=\"tool-icon pending\"></span><span class=\"tool-title\">'+esc(e.toolName)+(e.args?' '+esc(String(e.args.command||e.args.path||e.args.file_path||\"\")).slice(0,80):'')+'</span><span class=\"tool-arrow\">▶</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}\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\");\nif(isError&&el.querySelector(\".tool-body\").textContent===\"\"){el.querySelector(\".tool-body\").textContent=\"(no output)\"}\n}\n\nfunction handle(d){\nswitch(d.type){\ncase\"agent_start\":curAssistant=null;break;\ncase\"text_delta\":{const ad=getAssistantDiv();ad.innerHTML+=renderText(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=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+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</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,\"&\").replace(/</g,\"<\").replace(/>/g,\">\")}\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\">▶</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=\" · \"+fmt(d.usage.input)+\" in · \"+fmt(d.usage.output)+\" out\";if(d.usage.cost)s+=\" · $\"+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.
|
|
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.
|
|
9
|
+
"version": "0.75.18",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-sandbox",
|
|
3
|
-
"version": "1.5.
|
|
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.
|
|
9
|
+
"version": "1.5.18",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sandbox-runtime": "^0.0.26"
|
|
12
12
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "0.75.
|
|
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.
|
|
9
|
+
"version": "0.75.18",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeryc/pi-coding-agent",
|
|
3
|
-
"version": "0.75.
|
|
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.
|
|
9
|
+
"version": "0.75.18",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@openeryc/pi-agent-core": "^0.75.
|
|
13
|
-
"@openeryc/pi-ai": "^0.75.
|
|
14
|
-
"@openeryc/pi-tui": "^0.75.
|
|
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.
|
|
703
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.
|
|
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.
|
|
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.
|
|
717
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.
|
|
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.
|
|
739
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.
|
|
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.
|
|
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.
|
|
43
|
-
"@openeryc/pi-ai": "^0.75.
|
|
44
|
-
"@openeryc/pi-tui": "^0.75.
|
|
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",
|