@openeryc/pi-coding-agent 0.75.22 → 0.75.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/dist/modes/web/web-mode.d.ts.map +1 -1
- package/dist/modes/web/web-mode.js +73 -0
- 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;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"]}
|
|
1
|
+
{"version":3,"file":"web-mode.d.ts","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAwP/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgJ5E","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spinner\" id=\"spinner\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").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/messages\").then(r=>r.json()).then(events=>{\nfor(const e of events){\nif(e.type===\"user\"){addMsg(\"user\",e.text)}\nelse{handle(e)}\n}\n}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
|
|
@@ -221,6 +221,13 @@ while(true){const{done,value}=await reader.read();if(done)break;buf+=decoder.dec
|
|
|
221
221
|
for(const line of lines){if(!line.trim())continue;try{handle(JSON.parse(line))}catch{}}}}
|
|
222
222
|
catch(err){addMsg("system","Error: "+esc(err.message))}finally{busy=false;send.disabled=false;spinner.className="spinner"}};
|
|
223
223
|
|
|
224
|
+
fetch("/api/messages").then(r=>r.json()).then(events=>{
|
|
225
|
+
for(const e of events){
|
|
226
|
+
if(e.type==="user"){addMsg("user",e.text)}
|
|
227
|
+
else{handle(e)}
|
|
228
|
+
}
|
|
229
|
+
}).catch(()=>{});
|
|
230
|
+
|
|
224
231
|
fetch("/api/stats").then(r=>r.json()).then(s=>{
|
|
225
232
|
statSessions.textContent=s.sessions+" sessions";
|
|
226
233
|
statCost.textContent="$"+s.cost.toFixed(2);
|
|
@@ -287,6 +294,53 @@ export async function runWebMode(runtime) {
|
|
|
287
294
|
}));
|
|
288
295
|
return;
|
|
289
296
|
}
|
|
297
|
+
if (req.method === "GET" && url === "/api/messages") {
|
|
298
|
+
const entries = session.sessionManager.getEntries();
|
|
299
|
+
const events = [];
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
if (entry.type !== "message")
|
|
302
|
+
continue;
|
|
303
|
+
const msg = entry.message;
|
|
304
|
+
if (!msg)
|
|
305
|
+
continue;
|
|
306
|
+
if (msg.role === "user") {
|
|
307
|
+
const content = typeof msg.content === "string" ? msg.content : "";
|
|
308
|
+
events.push({ type: "user", text: content });
|
|
309
|
+
}
|
|
310
|
+
else if (msg.role === "assistant") {
|
|
311
|
+
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
312
|
+
for (const block of content) {
|
|
313
|
+
if (block.type === "text" && block.text) {
|
|
314
|
+
events.push({ type: "text_delta", delta: block.text });
|
|
315
|
+
}
|
|
316
|
+
if (block.type === "toolCall" && block.toolCall) {
|
|
317
|
+
const tc = block.toolCall;
|
|
318
|
+
events.push({
|
|
319
|
+
type: "tool_execution_start",
|
|
320
|
+
toolCallId: tc.toolCallId,
|
|
321
|
+
toolName: tc.toolName,
|
|
322
|
+
args: tc.input,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else if (msg.role === "toolResult") {
|
|
328
|
+
const content = msg
|
|
329
|
+
.content;
|
|
330
|
+
events.push({
|
|
331
|
+
type: "tool_execution_end",
|
|
332
|
+
toolCallId: msg.toolCallId,
|
|
333
|
+
result: { content: content ?? [] },
|
|
334
|
+
isError: false,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const usage = getSessionTokenCount(session);
|
|
339
|
+
events.push({ type: "agent_end", usage });
|
|
340
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
341
|
+
res.end(JSON.stringify(events));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
290
344
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
291
345
|
res.end(HTML);
|
|
292
346
|
});
|
|
@@ -318,6 +372,25 @@ export async function runWebMode(runtime) {
|
|
|
318
372
|
await new Promise(() => { });
|
|
319
373
|
server.close();
|
|
320
374
|
}
|
|
375
|
+
function getSessionTokenCount(session) {
|
|
376
|
+
const entries = session.sessionManager.getEntries();
|
|
377
|
+
let input = 0;
|
|
378
|
+
let output = 0;
|
|
379
|
+
let costTotal = 0;
|
|
380
|
+
let hasUsage = false;
|
|
381
|
+
for (const entry of entries) {
|
|
382
|
+
if (entry.type !== "message")
|
|
383
|
+
continue;
|
|
384
|
+
const msg = entry.message;
|
|
385
|
+
if (msg?.role === "assistant" && msg.usage) {
|
|
386
|
+
input += msg.usage.input;
|
|
387
|
+
output += msg.usage.output;
|
|
388
|
+
costTotal += msg.usage.cost.total;
|
|
389
|
+
hasUsage = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return hasUsage ? { input, output, cost: { total: costTotal } } : undefined;
|
|
393
|
+
}
|
|
321
394
|
function toSerializableEvents(event) {
|
|
322
395
|
switch (event.type) {
|
|
323
396
|
case "agent_start":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-mode.js","sourceRoot":"","sources":["../../../src/modes/web/web-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { networkInterfaces } from \"node:os\";\nimport type { AgentSessionEvent } from \"../../core/agent-session.ts\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.ts\";\n\nconst PASSWORD = process.env.PI_WEB_PASSWORD;\n\nfunction checkAuth(req: IncomingMessage, res: ServerResponse): boolean {\n\tif (!PASSWORD) return true;\n\n\tconst auth = req.headers.authorization;\n\tif (auth) {\n\t\tconst [scheme, credentials] = auth.split(\" \");\n\t\tif (scheme === \"Basic\" && credentials) {\n\t\t\tconst decoded = Buffer.from(credentials, \"base64\").toString(\"utf-8\");\n\t\t\tconst [, password] = decoded.split(\":\");\n\t\t\tif (password === PASSWORD) return true;\n\t\t}\n\t}\n\n\tres.writeHead(401, {\n\t\t\"www-authenticate\": 'Basic realm=\"pi\", charset=\"UTF-8\"',\n\t\t\"content-type\": \"text/plain\",\n\t});\n\tres.end(\"Unauthorized\");\n\treturn false;\n}\n\nfunction getLocalIP(): string {\n\tconst interfaces = networkInterfaces();\n\tfor (const iface of Object.values(interfaces)) {\n\t\tif (!iface) continue;\n\t\tfor (const addr of iface) {\n\t\t\tif (addr.family === \"IPv4\" && !addr.internal) {\n\t\t\t\treturn addr.address;\n\t\t\t}\n\t\t}\n\t}\n\treturn \"127.0.0.1\";\n}\n\nfunction getPort(): number {\n\tconst env = process.env.PORT;\n\tif (env) {\n\t\tconst p = parseInt(env, 10);\n\t\tif (!Number.isNaN(p) && p > 0 && p < 65536) return p;\n\t}\n\treturn 0;\n}\n\nconst HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>pi</title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{font-size:14px;-webkit-text-size-adjust:100%}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",system-ui,sans-serif;background:#0b1014;color:#c9d1d9;display:flex;flex-direction:column;height:100dvh;overflow:hidden}\n#header{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#0d121a;border-bottom:1px solid #21262d;flex-shrink:0;gap:10px}\n#header-left{display:flex;align-items:center;gap:10px}\n#header h1{font-size:14px;font-weight:600;color:#e6edf3}\n#session-info{font-size:11px;color:#6e7681;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n#header-stats{display:flex;gap:12px;font-size:11px;color:#7d8590}\n#msgs{flex:1;overflow-y:auto;padding:16px 20px;scroll-behavior:smooth;display:flex;flex-direction:column;gap:14px}\n#msgs::-webkit-scrollbar{width:6px}\n#msgs::-webkit-scrollbar-track{background:transparent}\n#msgs::-webkit-scrollbar-thumb{background:#30363d;border-radius:3px}\n.msg-group{display:flex;flex-direction:column;gap:2px;animation:fadeIn .15s ease;max-width:86%}\n.msg-group.user{max-width:82%;align-self:flex-end}\n@keyframes fadeIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n.msg-label{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:0 2px}\n.msg-group.user .msg-label{color:#7ec9ff;text-align:right}\n.msg-group.assistant .msg-label{color:#7d8590}\n.msg{border-radius:8px;font-size:13.5px;line-height:1.6;overflow-wrap:break-word;padding:10px 14px}\n.msg.user{background:#1b4a8b;color:#e6edf3;border-bottom-right-radius:2px}\n.msg.assistant{background:#1a1f2b;color:#c9d1d9;border-bottom-left-radius:2px;border:1px solid #21262d}\n.msg.system{background:transparent;color:#6e7681;font-size:12px;text-align:center;max-width:100%;padding:4px 0}\n.msg p{margin:0 0 6px}\n.msg p:last-child{margin-bottom:0}\n.msg strong{color:#f2a65a;font-weight:600}\n.msg em{color:#c9d1d9;font-style:italic}\n.msg code{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;background:#2d333b;color:#c9d1d9;padding:1px 5px;border-radius:3px}\n.msg pre{background:#161b22;border:1px solid #30363d;border-radius:6px;padding:10px 12px;margin:8px 0;overflow-x:auto;font-size:12px;line-height:1.5}\n.msg pre code{background:transparent;padding:0;border-radius:0;font-size:inherit}\n.msg h1,.msg h2,.msg h3{margin:8px 0 4px;font-weight:600;color:#e6edf3}\n.msg h1{font-size:18px;border-bottom:1px solid #21262d;padding-bottom:4px}\n.msg h2{font-size:15px}\n.msg h3{font-size:13.5px;color:#c9d1d9}\n.msg ul,.msg ol{padding-left:20px;margin:4px 0 8px}\n.msg li{margin:2px 0}\n.msg hr{border:none;border-top:1px solid #21262d;margin:10px 0}\n.tool{border:1px solid #21262d;border-radius:8px;overflow:hidden;transition:border-color .2s}\n.tool:hover{border-color:#30363d}\n.tool-header{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;background:#0d121a;cursor:pointer;user-select:none;gap:8px;transition:background .15s}\n.tool-header:hover{background:#161b22}\n.tool-icon{width:7px;height:7px;border-radius:50%;flex-shrink:0}\n.tool-icon.pending{background:#d29922;animation:pulse 1.5s infinite}\n.tool-icon.done{background:#3fb950}\n.tool-icon.error{background:#f85149}\n@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}}\n.tool-title{flex:1;font-size:12px;font-weight:500;color:#c9d1d9;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.tool-arrow{color:#6e7681;font-size:9px;transition:transform .2s}\n.tool.expanded .tool-arrow{transform:rotate(90deg)}\n.tool-body{font-family:\"JetBrains Mono\",\"Fira Code\",monospace;font-size:12px;line-height:1.55;padding:8px 10px;background:#0b1014;white-space:pre-wrap;word-break:break-all;overflow-x:auto;max-height:30vh;overflow-y:auto;display:none}\n.tool-body::-webkit-scrollbar{width:4px;height:4px}\n.tool-body::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tool.expanded .tool-body{display:block}\n.tool.mini .tool-body{max-height:12vh}\n.thinking{padding:5px 10px;border-left:2px solid #30363d;font-size:11.5px;color:#6e7681;font-style:italic}\n#input-area{background:#0d121a;border-top:1px solid #21262d;padding:12px 16px;flex-shrink:0}\n#input-area form{display:flex;gap:8px;max-width:900px;margin:0 auto}\n#prompt{flex:1;background:#161b22;border:1px solid #30363d;border-radius:8px;padding:9px 12px;color:#c9d1d9;font-size:13.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s}\n#prompt:focus{border-color:#4493f8;box-shadow:0 0 0 3px rgba(68,147,248,.15)}\n#prompt::placeholder{color:#484f58}\nbutton{background:#1f6feb;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:13px;font-weight:500;cursor:pointer;transition:background .15s;flex-shrink:0}\nbutton:hover{background:#2b83ff}\nbutton:disabled{background:#21262d;color:#6e7681;cursor:not-allowed}\n.btn-mini{font-size:11px;padding:4px 10px;background:#21262d}\n.btn-mini:hover{background:#30363d}\n.spinner{display:none;width:16px;height:16px;border:2px solid #30363d;border-top-color:#4493f8;border-radius:50%;animation:spin .6s linear infinite}\n.spinner.active{display:inline-block}\n@keyframes spin{to{transform:rotate(360deg)}}\n@media(max-width:600px){\nhtml{font-size:13px}\n#msgs{padding:10px;gap:10px}\n.msg-group{max-width:94%}\n.msg-group.user{max-width:92%}\n#input-area{padding:8px 10px}\n#prompt{padding:7px 10px}\nbutton{padding:7px 12px}\n}\n</style>\n</head>\n<body>\n<div id=\"header\">\n<div id=\"header-left\"><h1>pi</h1><span id=\"session-info\"></span></div>\n<div id=\"header-stats\"><span id=\"stat-sessions\"></span><span id=\"stat-cost\"></span></div>\n</div>\n<div id=\"msgs\"></div>\n<div id=\"input-area\">\n<form id=\"f\" autocomplete=\"off\">\n<input id=\"prompt\" type=\"text\" placeholder=\"Message pi...\" autofocus autocomplete=\"off\">\n<button id=\"send\">Send</button>\n<span class=\"spinner\" id=\"spinner\"></span>\n</form>\n</div>\n<script>\nconst msgs=document.getElementById(\"msgs\"),f=document.getElementById(\"f\"),prompt=document.getElementById(\"prompt\"),\nsend=document.getElementById(\"send\"),spinner=document.getElementById(\"spinner\"),\nstatSessions=document.getElementById(\"stat-sessions\"),statCost=document.getElementById(\"stat-cost\"),\nsessionInfo=document.getElementById(\"session-info\");\nlet busy=false,toolEls={},curAssistant=null;\n\nfunction scrollDown(){msgs.scrollTop=msgs.scrollHeight}\nfunction esc(s){if(!s)return\"\";return s.replace(/&/g,\"&\").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
|
+
{"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/messages\").then(r=>r.json()).then(events=>{\nfor(const e of events){\nif(e.type===\"user\"){addMsg(\"user\",e.text)}\nelse{handle(e)}\n}\n}).catch(()=>{});\n\nfetch(\"/api/stats\").then(r=>r.json()).then(s=>{\nstatSessions.textContent=s.sessions+\" sessions\";\nstatCost.textContent=\"$\"+s.cost.toFixed(2);\n}).catch(()=>{});\n\nfetch(\"/api/session-info\").then(r=>r.json()).then(s=>{\nsessionInfo.textContent=s.id?(\"session: \"+s.id.slice(0,8)+(s.name?\" | \"+s.name:\"\")):\"\";\n}).catch(()=>{});\n</script>\n</body>\n</html>`;\n\nfunction sendEvent(res: ServerResponse, data: Record<string, unknown>): void {\n\tres.write(`${JSON.stringify(data)}\\n`);\n}\n\nexport async function runWebMode(runtime: AgentSessionRuntime): Promise<void> {\n\tconst session = runtime.session;\n\tconst port = getPort();\n\n\tconst server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n\t\tif (!checkAuth(req, res)) return;\n\n\t\tconst url = req.url ?? \"/\";\n\n\t\tif (req.method === \"POST\" && url === \"/api/prompt\") {\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\treq.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n\t\t\tawait new Promise<void>((resolve) => req.on(\"end\", resolve));\n\t\t\tconst body = JSON.parse(Buffer.concat(chunks).toString());\n\t\t\tconst text = String(body.text ?? \"\").trim();\n\t\t\tif (!text) {\n\t\t\t\tres.writeHead(400);\n\t\t\t\tres.end(JSON.stringify({ error: \"missing text\" }));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tres.writeHead(200, { \"content-type\": \"text/plain; charset=utf-8\", \"cache-control\": \"no-cache\" });\n\n\t\t\tconst unsubscribe = session.subscribe((event: AgentSessionEvent) => {\n\t\t\t\tconst events = toSerializableEvents(event);\n\t\t\t\tfor (const evt of events) {\n\t\t\t\t\tsendEvent(res, evt);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tawait session.prompt(text);\n\t\t\t} catch (err) {\n\t\t\t\tsendEvent(res, { type: \"error\", message: String(err) });\n\t\t\t} finally {\n\t\t\t\tunsubscribe();\n\t\t\t\tres.end();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/stats\") {\n\t\t\tconst stats = await session.getUsageStats();\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ sessions: stats.sessions, cost: stats.cost, input: stats.input, output: stats.output }),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/session-info\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\tid: session.sessionId,\n\t\t\t\t\tname: session.sessionManager.getSessionName(),\n\t\t\t\t\tfile: session.sessionFile,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (req.method === \"GET\" && url === \"/api/messages\") {\n\t\t\tconst entries = session.sessionManager.getEntries();\n\t\t\tconst events: Record<string, unknown>[] = [];\n\n\t\t\tfor (const entry of entries) {\n\t\t\t\tif (entry.type !== \"message\") continue;\n\t\t\t\tconst msg = (entry as { message: { role: string; content: unknown; usage?: unknown } }).message;\n\t\t\t\tif (!msg) continue;\n\n\t\t\t\tif (msg.role === \"user\") {\n\t\t\t\t\tconst content = typeof msg.content === \"string\" ? msg.content : \"\";\n\t\t\t\t\tevents.push({ type: \"user\", text: content });\n\t\t\t\t} else if (msg.role === \"assistant\") {\n\t\t\t\t\tconst content = Array.isArray(msg.content) ? msg.content : [];\n\t\t\t\t\tfor (const block of content as Array<{ type: string; text?: string; toolCall?: unknown }>) {\n\t\t\t\t\t\tif (block.type === \"text\" && block.text) {\n\t\t\t\t\t\t\tevents.push({ type: \"text_delta\", delta: block.text });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (block.type === \"toolCall\" && block.toolCall) {\n\t\t\t\t\t\t\tconst tc = block.toolCall as { toolCallId?: string; toolName?: string; input?: unknown };\n\t\t\t\t\t\t\tevents.push({\n\t\t\t\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\t\t\t\ttoolCallId: tc.toolCallId,\n\t\t\t\t\t\t\t\ttoolName: tc.toolName,\n\t\t\t\t\t\t\t\targs: tc.input,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.role === \"toolResult\") {\n\t\t\t\t\tconst content = (msg as { toolCallId?: string; content?: Array<{ type: string; text?: string }> })\n\t\t\t\t\t\t.content;\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\t\ttoolCallId: (msg as { toolCallId?: string }).toolCallId,\n\t\t\t\t\t\tresult: { content: content ?? [] },\n\t\t\t\t\t\tisError: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst usage = getSessionTokenCount(session);\n\t\t\tevents.push({ type: \"agent_end\", usage });\n\n\t\t\tres.writeHead(200, { \"content-type\": \"application/json\" });\n\t\t\tres.end(JSON.stringify(events));\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\tres.end(HTML);\n\t});\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.listen(port, \"0.0.0.0\", () => resolve());\n\t\tserver.on(\"error\", reject);\n\t});\n\n\tconst addr = server.address();\n\tif (!addr || typeof addr === \"string\") {\n\t\tconsole.error(\"Failed to start web server\");\n\t\treturn;\n\t}\n\n\tconst consolePort = `http://127.0.0.1:${addr.port}`;\n\tconst lan = getLocalIP();\n\tconsole.log(`\\n pi web UI: ${consolePort}`);\n\tif (lan !== \"127.0.0.1\") {\n\t\tconsole.log(` LAN: http://${lan}:${addr.port}`);\n\t}\n\tconsole.log(` session: ${session.sessionId.slice(0, 8)}`);\n\tconsole.log(\" (listening on all interfaces)\");\n\tif (PASSWORD) {\n\t\tconsole.log(` auth: Basic (user \"pi\", password from PI_WEB_PASSWORD)`);\n\t} else {\n\t\tconsole.log(\" auth: none (set PI_WEB_PASSWORD to enable)\");\n\t}\n\tconsole.log();\n\n\t// Keep process alive\n\tawait new Promise(() => {});\n\n\tserver.close();\n}\n\nfunction getSessionTokenCount(session: AgentSessionRuntime[\"session\"]): Record<string, unknown> | undefined {\n\tconst entries = session.sessionManager.getEntries();\n\tlet input = 0;\n\tlet output = 0;\n\tlet costTotal = 0;\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = (\n\t\t\tentry as { message: { role: string; usage?: { input: number; output: number; cost: { total: number } } } }\n\t\t).message;\n\t\tif (msg?.role === \"assistant\" && msg.usage) {\n\t\t\tinput += msg.usage.input;\n\t\t\toutput += msg.usage.output;\n\t\t\tcostTotal += msg.usage.cost.total;\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage ? { input, output, cost: { total: costTotal } } : undefined;\n}\n\nfunction toSerializableEvents(event: AgentSessionEvent): Record<string, unknown>[] {\n\tswitch (event.type) {\n\t\tcase \"agent_start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"agent_end\": {\n\t\t\tconst lastAssistant = event.messages.filter((m) => m.role === \"assistant\").at(-1);\n\t\t\tconst usage = lastAssistant && \"usage\" in lastAssistant ? lastAssistant.usage : undefined;\n\t\t\treturn [{ type: \"agent_end\", usage }];\n\t\t}\n\t\tcase \"message_update\": {\n\t\t\tconst ame: Record<string, unknown> = event.assistantMessageEvent;\n\t\t\tconst serialized = serializeAssistantMessageEvent(ame);\n\t\t\treturn serialized;\n\t\t}\n\t\tcase \"tool_execution_start\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_start\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\ttoolName: event.toolName,\n\t\t\t\t\targs: event.args,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_update\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_update\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.partialResult,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"tool_execution_end\":\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_execution_end\",\n\t\t\t\t\ttoolCallId: event.toolCallId,\n\t\t\t\t\tresult: event.result,\n\t\t\t\t\tisError: event.isError,\n\t\t\t\t},\n\t\t\t];\n\t\tcase \"compaction_start\":\n\t\tcase \"compaction_end\":\n\t\t\treturn [{ type: \"compaction\" }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\nfunction serializeAssistantMessageEvent(event: Record<string, unknown>): Record<string, unknown>[] {\n\tconst type = event.type as string;\n\tswitch (type) {\n\t\tcase \"start\":\n\t\t\treturn [{ type: \"agent_start\" }];\n\t\tcase \"text_start\":\n\t\t\treturn [{ type: \"text_start\", contentIndex: event.contentIndex }];\n\t\tcase \"text_delta\":\n\t\t\treturn [{ type: \"text_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"text_end\":\n\t\t\treturn [{ type: \"text_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_start\":\n\t\t\treturn [{ type: \"thinking_start\", contentIndex: event.contentIndex }];\n\t\tcase \"thinking_delta\":\n\t\t\treturn [{ type: \"thinking_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"thinking_end\":\n\t\t\treturn [{ type: \"thinking_end\", content: event.content, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_start\":\n\t\t\treturn [{ type: \"toolcall_start\", contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_delta\":\n\t\t\treturn [{ type: \"toolcall_delta\", delta: event.delta, contentIndex: event.contentIndex }];\n\t\tcase \"toolcall_end\":\n\t\t\treturn [{ type: \"toolcall_end\", toolCall: event.toolCall, contentIndex: event.contentIndex }];\n\t\tcase \"done\":\n\t\t\treturn [{ type: \"done\", reason: event.reason }];\n\t\tcase \"error\":\n\t\t\treturn [{ type: \"error\", reason: event.reason }];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-custom-provider",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.23",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-custom-provider",
|
|
9
|
-
"version": "0.75.
|
|
9
|
+
"version": "0.75.23",
|
|
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.23",
|
|
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.23",
|
|
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.23",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "0.75.
|
|
9
|
+
"version": "0.75.23",
|
|
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.23",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openeryc/pi-coding-agent",
|
|
9
|
-
"version": "0.75.
|
|
9
|
+
"version": "0.75.23",
|
|
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.23",
|
|
13
|
+
"@openeryc/pi-ai": "^0.75.23",
|
|
14
|
+
"@openeryc/pi-tui": "^0.75.23",
|
|
15
15
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
16
16
|
"chalk": "5.6.2",
|
|
17
17
|
"cross-spawn": "7.0.6",
|
|
@@ -699,11 +699,11 @@
|
|
|
699
699
|
]
|
|
700
700
|
},
|
|
701
701
|
"node_modules/@openeryc/pi-agent-core": {
|
|
702
|
-
"version": "0.75.
|
|
703
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.
|
|
702
|
+
"version": "0.75.23",
|
|
703
|
+
"resolved": "https://registry.npmjs.org/@openeryc/pi-agent-core/-/pi-agent-core-0.75.23.tgz",
|
|
704
704
|
"license": "MIT",
|
|
705
705
|
"dependencies": {
|
|
706
|
-
"@openeryc/pi-ai": "^0.75.
|
|
706
|
+
"@openeryc/pi-ai": "^0.75.23",
|
|
707
707
|
"ignore": "7.0.5",
|
|
708
708
|
"typebox": "1.1.38",
|
|
709
709
|
"yaml": "2.9.0"
|
|
@@ -713,8 +713,8 @@
|
|
|
713
713
|
}
|
|
714
714
|
},
|
|
715
715
|
"node_modules/@openeryc/pi-ai": {
|
|
716
|
-
"version": "0.75.
|
|
717
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.
|
|
716
|
+
"version": "0.75.23",
|
|
717
|
+
"resolved": "https://registry.npmjs.org/@openeryc/pi-ai/-/pi-ai-0.75.23.tgz",
|
|
718
718
|
"license": "MIT",
|
|
719
719
|
"dependencies": {
|
|
720
720
|
"@anthropic-ai/sdk": "0.91.1",
|
|
@@ -735,8 +735,8 @@
|
|
|
735
735
|
}
|
|
736
736
|
},
|
|
737
737
|
"node_modules/@openeryc/pi-tui": {
|
|
738
|
-
"version": "0.75.
|
|
739
|
-
"resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.
|
|
738
|
+
"version": "0.75.23",
|
|
739
|
+
"resolved": "https://registry.npmjs.org/@openeryc/pi-tui/-/pi-tui-0.75.23.tgz",
|
|
740
740
|
"license": "MIT",
|
|
741
741
|
"dependencies": {
|
|
742
742
|
"get-east-asian-width": "1.6.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openeryc/pi-coding-agent",
|
|
3
|
-
"version": "0.75.
|
|
3
|
+
"version": "0.75.23",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@openeryc/pi-agent-core": "^0.75.
|
|
43
|
-
"@openeryc/pi-ai": "^0.75.
|
|
44
|
-
"@openeryc/pi-tui": "^0.75.
|
|
42
|
+
"@openeryc/pi-agent-core": "^0.75.23",
|
|
43
|
+
"@openeryc/pi-ai": "^0.75.23",
|
|
44
|
+
"@openeryc/pi-tui": "^0.75.23",
|
|
45
45
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
46
46
|
"chalk": "5.6.2",
|
|
47
47
|
"cross-spawn": "7.0.6",
|