pi-webapp 0.1.0
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/LICENSE +21 -0
- package/README.md +50 -0
- package/assets/cover-webapp.png +0 -0
- package/dist/extension.js +6 -0
- package/package.json +60 -0
- package/web/dist/assets/app.js +93 -0
- package/web/dist/assets/style.css +1 -0
- package/web/dist/index.html +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pi-webapp contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# pi-webapp
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
**pi-webapp** opens the active Pi coding agent session in a local browser. Pi continues to run the agent, tools, and session storage; the browser provides a live interface for conversation and control.
|
|
6
|
+
|
|
7
|
+
## Install and open
|
|
8
|
+
|
|
9
|
+
Requires Node.js 22.19 or later and Pi 0.87.1 or later.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pi install npm:pi-webapp
|
|
13
|
+
pi
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
In Pi, enter `/web` to open the interface in your default browser. Pi also prints the local URL for remote or headless environments. The URL contains a random token valid only for the current Pi process. Enter `/web` again to reopen it.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- View the current conversation with Markdown, streaming output, expandable reasoning and tool calls, token usage, and a turn-by-turn trace.
|
|
21
|
+
- Send messages, stop a run, start a new Pi session, and change the current model or thinking level.
|
|
22
|
+
- Browse workspaces and saved sessions. Add a workspace with the native directory picker when available, or use the built-in directory browser over SSH.
|
|
23
|
+
- Attach up to 20 files of 20 MB each by selecting, dropping, or pasting them. Images show previews and are also sent as Pi image content.
|
|
24
|
+
- Manage appearance, Pi packages, extensions, skills, and models from the settings panel. Provider credentials remain in Pi's local authentication store and are never returned to the browser.
|
|
25
|
+
|
|
26
|
+
The Pi terminal session that opened `/web` stays synchronized with the browser. Sessions opened in other workspaces use the Pi SDK and load that workspace's skills and project context. They do not load extensions again in the same process.
|
|
27
|
+
|
|
28
|
+
Workspace records and attachments stay under Pi's existing `pi-web/` data directory for compatibility with earlier releases. Removing a workspace from the sidebar does not delete its files or sessions.
|
|
29
|
+
|
|
30
|
+
## Develop locally
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install
|
|
34
|
+
npm run check
|
|
35
|
+
npm run test
|
|
36
|
+
npm run build
|
|
37
|
+
pi -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
After editing the extension, rebuild it, then enter `/reload` and `/web` in Pi. Refreshing an old browser page alone does not replace the running bridge.
|
|
41
|
+
|
|
42
|
+
The source lives in `extension/`, `shared/`, and `web/src/`. The npm package contains only the bundled, minified JavaScript extension, built web assets, cover image, and this README. No TypeScript source or source maps are published. Minification makes the shipped code harder to read; it does not encrypt JavaScript.
|
|
43
|
+
|
|
44
|
+
## Publish
|
|
45
|
+
|
|
46
|
+
`npm publish` runs the checks, tests, and production build. The `pi-package` keyword makes the published npm package eligible for the [Pi package catalog](https://pi.dev/packages); catalog updates may lag behind npm.
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT. See the `LICENSE` file.
|
|
Binary file
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{dirname as Wt,join as Nt,resolve as Vt}from"node:path";import{realpath as B}from"node:fs/promises";import{fileURLToPath as jt}from"node:url";import{DefaultPackageManager as we,getAgentDir as _,ModelRuntime as Rt,SettingsManager as Bt}from"@earendil-works/pi-coding-agent";import{getSupportedThinkingLevels as _t}from"@earendil-works/pi-ai";import{randomBytes as st,timingSafeEqual as nt}from"node:crypto";import{readFile as ot}from"node:fs/promises";import{createServer as at}from"node:http";import{join as ct}from"node:path";import{mkdir as be,opendir as Se,stat as Pe}from"node:fs/promises";import{homedir as xe}from"node:os";import{basename as Ie,dirname as G,join as K,posix as Ce,resolve as z,win32 as Ee}from"node:path";function Y(e){return process.platform==="win32"?Ee.isAbsolute(e)&&/^(?:[A-Za-z]:[\\/]|[\\/]{2}[^\\/]+[\\/]+[^\\/]+)/.test(e):Ce.isAbsolute(e)}function Ae(e,t,r){if(e.length===r&&t.name.localeCompare(e[e.length-1].name)>=0)return!0;let i=0,a=e.length;for(;i<a;){let p=i+a>>>1;t.name.localeCompare(e[p].name)<0?a=p:i=p+1}return e.splice(i,0,t),e.length<=r?!1:(e.pop(),!0)}async function X(e){if(e!==void 0&&!Y(e))throw new Error("\u76EE\u5F55\u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");let t=xe(),r=z(e??t),i=[];for(let c=r;;c=G(c)){let g=G(c);if(i.unshift({name:g===c?c:Ie(c),path:c,hidden:!1}),g===c)break}let a=[],p=!1,n=await Se(r);for await(let c of n)!c.isDirectory()&&!c.isSymbolicLink()||Ae(a,{name:c.name,isDirectory:c.isDirectory(),isSymbolicLink:c.isSymbolicLink()},1001)&&(p=!0);let l=[];for(let c of a){let g=K(r,c.name);if(!c.isDirectory&&c.isSymbolicLink)try{if(!(await Pe(g)).isDirectory())continue}catch{continue}if(l.length===1e3){p=!0;break}l.push({name:c.name,path:g,hidden:c.name.startsWith(".")})}return{path:r,home:t,crumbs:i,entries:l,truncated:p}}async function Q(e,t){if(!Y(e))throw new Error("\u7236\u76EE\u5F55\u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84");if(!t.trim()||t==="."||t===".."||/[/\\]/.test(t))throw new Error("\u6587\u4EF6\u5939\u540D\u79F0\u65E0\u6548");let r=K(z(e),t);return await be(r),r}import{execFile as Me}from"node:child_process";import{accessSync as Te,constants as Le}from"node:fs";import{delimiter as We,join as Ne}from"node:path";var Ve=(e,t,r)=>new Promise((i,a)=>{Me(e,[...t],{encoding:"utf8",signal:r,windowsHide:!0},(p,n,l)=>{p?a(Object.assign(new Error(p.message,{cause:p}),{code:p.code,stdout:n,stderr:l})):i({stdout:n,stderr:l})})});function je(e){for(let t of(e??"").split(We))if(t)for(let r of["zenity","kdialog"])try{return Te(Ne(t,r),Le.X_OK),!0}catch{}return!1}function Z(e=process.platform,t=process.env){return t.SSH_CONNECTION||t.SSH_TTY?"browse":e==="darwin"||e==="linux"&&(t.DISPLAY||t.WAYLAND_DISPLAY)&&je(t.PATH)?"native":"browse"}function T(e){if(!(typeof e!="object"||e===null||!("code"in e)))return typeof e.code=="string"||typeof e.code=="number"?e.code:void 0}function Re(e){return typeof e=="object"&&e!==null&&"stderr"in e&&typeof e.stderr=="string"?e.stderr:""}function D(e){return e.replace(/[\r\n]+$/,"")||null}async function q(e,t=process.platform,r=Ve){if(t==="darwin")try{let i=await r("osascript",["-e",'set selectedFolder to choose folder with prompt "Select Workspace Directory"',"-e","POSIX path of selectedFolder"],e);return D(i.stdout)}catch(i){if(!e.aborted&&T(i)===1&&/(?:User canceled|-128)/i.test(Re(i)))return null;throw i}if(t==="linux"){try{return D((await r("zenity",["--file-selection","--directory","--title=Select Workspace Directory"],e)).stdout)}catch(i){if(e.aborted)throw i;if(T(i)===1)return null;if(T(i)!=="ENOENT")throw i}try{return D((await r("kdialog",["--getexistingdirectory",".","--title","Select Workspace Directory"],e)).stdout)}catch(i){if(e.aborted)throw i;if(T(i)===1)return null;throw i}}throw new Error(`\u5F53\u524D\u7CFB\u7EDF\u4E0D\u652F\u6301\u672C\u5730\u76EE\u5F55\u9009\u62E9\u5668\uFF1A${t}`)}import{createHash as Be,randomUUID as _e}from"node:crypto";import{mkdir as De,readFile as Oe,writeFile as Ue}from"node:fs/promises";import{join as ee}from"node:path";import{getAgentDir as $e}from"@earendil-works/pi-coding-agent";var L=20*1024*1024,te=20,Fe=/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/iu;function He(e){let t=e.indexOf("."),r=(t<0?e:e.slice(0,t)).replace(/[. ]+$/u,"");return Fe.test(r)}function Je(e,t){let r=0,i="";for(let a of Buffer.from(e).toString("utf8")){let p=Buffer.byteLength(a);if(r+p>t)break;i+=a,r+=p}return i}function Ge(e){if(e===void 0)return"file";let r=e.slice(Math.max(e.lastIndexOf("/"),e.lastIndexOf("\\"))+1).replace(/[\u0000-\u001f\u007f]/g,"").replace(/[<>:"|?*]/g,"_").trim().replace(/[. ]+$/u,"");return He(r)&&(r=`_${r}`),r=Je(r,255).replace(/[. ]+$/u,""),r===""||r==="."||r===".."?"file":r}function Ke(e){return e.length>=8&&e.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10]))?"image/png":e.length>=3&&e.subarray(0,3).equals(Buffer.from([255,216,255]))?"image/jpeg":e.length>=6&&["GIF87a","GIF89a"].includes(e.toString("ascii",0,6))?"image/gif":e.length>=12&&e.toString("ascii",0,4)==="RIFF"&&e.toString("ascii",8,12)==="WEBP"?"image/webp":null}async function re(e,t,r,i){if(i.byteLength===0||i.byteLength>L)throw new Error("\u6587\u4EF6\u4E3A\u7A7A\u6216\u8D85\u8FC7 20 MB");let a=Ge(t),p=Be("sha256").update(i).digest("hex"),n=ee($e(),"pi-web","attachments",p);await De(n,{recursive:!0});let l=ee(n,a);try{await Ue(l,i,{flag:"wx",mode:384})}catch(c){if(!(c instanceof Error&&"code"in c&&c.code==="EEXIST"))throw c}return{id:_e(),name:a,bytes:i.byteLength,path:l,mediaType:Ke(i)??r.replace(/^image\//,"application/"),sessionId:e}}async function ie(e){return{type:"image",data:(await Oe(e.path)).toString("base64"),mimeType:e.mediaType}}import{randomBytes as ze}from"node:crypto";var W=class{flows=new Map;start(t,r,i){if([...this.flows.values()].some(({view:n})=>n.status==="running"||n.status==="waiting"))throw new Error("\u5DF2\u6709\u767B\u5F55\u6B63\u5728\u8FDB\u884C");this.flows.clear();let a=ze(18).toString("base64url"),p={view:{id:a,status:"running"},controller:new AbortController};return this.flows.set(a,p),i(t,r,{signal:p.controller.signal,notify:n=>{p.view={...p.view,event:n}},prompt:n=>new Promise((l,c)=>{let{signal:g,...y}=n;p.answer=l,p.reject=c,p.view={...p.view,status:"waiting",prompt:y},g?.aborted?c(new Error("\u767B\u5F55\u8F93\u5165\u5DF2\u53D6\u6D88")):g?.addEventListener("abort",()=>c(new Error("\u767B\u5F55\u8F93\u5165\u5DF2\u53D6\u6D88")),{once:!0})})}).then(()=>{p.view.status!=="cancelled"&&(p.view={id:a,status:"done"})}).catch(()=>{p.view.status!=="cancelled"&&(p.view={id:a,status:"error",error:"\u767B\u5F55\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u63D0\u4F9B\u65B9\u63D0\u793A\u5E76\u91CD\u8BD5\u3002"})}),{...p.view}}get(t){let r=this.flows.get(t)?.view;return r?{...r}:void 0}respond(t,r){let i=this.flows.get(t);if(!i?.answer||i.view.status!=="waiting"||i.view.prompt?.type==="select"&&!i.view.prompt.options.some(p=>p.id===r))return!1;let a=i.answer;return i.answer=void 0,i.reject=void 0,i.view={id:t,status:"running",event:i.view.event},a(r),!0}cancel(t){let r=this.flows.get(t);return!r||r.view.status==="done"||r.view.status==="error"?!1:(r.view={id:t,status:"cancelled"},r.controller.abort(),r.reject?.(new Error("\u767B\u5F55\u5DF2\u53D6\u6D88")),r.answer=void 0,r.reject=void 0,!0)}close(){for(let t of this.flows.keys())this.cancel(t);this.flows.clear()}};import{randomBytes as Ye}from"node:crypto";import{readFile as Xe,rename as Qe,rm as Ze,stat as qe,writeFile as et,mkdir as tt}from"node:fs/promises";import{dirname as rt}from"node:path";var it=["openai-completions","openai-responses","anthropic-messages"];function O(e){if(!e||typeof e!="object")return!1;let t=e;if(typeof t.id!="string"||!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(t.id)||typeof t.name!="string"||!t.name.trim()||t.name.length>100||typeof t.modelId!="string"||!t.modelId.trim()||t.modelId.length>200||!it.includes(t.api)||typeof t.baseUrl!="string"||t.baseUrl.length>2048)return!1;try{let r=new URL(t.baseUrl);return(r.protocol==="https:"||r.protocol==="http:")&&!!r.hostname&&!r.username&&!r.password&&!r.hash}catch{return!1}}async function se(e,t){if(!O(t))throw new Error("\u81EA\u5B9A\u4E49\u63D0\u4F9B\u65B9\u540D\u79F0\u3001\u5730\u5740\u6216\u6A21\u578B\u65E0\u6548");let r;try{r=await Xe(e,"utf8")}catch(l){if(l.code!=="ENOENT")throw l}let i;if(r===void 0)i={providers:{}};else{try{i=JSON.parse(r)}catch{throw new Error("models.json \u542B\u6CE8\u91CA\u6216\u683C\u5F0F\u9519\u8BEF\uFF0C\u8BF7\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\u624B\u52A8\u6DFB\u52A0\u63D0\u4F9B\u65B9")}if(!i||typeof i!="object"||!i.providers||typeof i.providers!="object"||Array.isArray(i.providers))throw new Error("models.json \u7ED3\u6784\u65E0\u6548")}if(Object.hasOwn(i.providers,t.id))throw new Error("\u63D0\u4F9B\u65B9\u5DF2\u5B58\u5728");let a={...i,providers:{...i.providers,[t.id]:{name:t.name.trim(),baseUrl:t.baseUrl,api:t.api,models:[{id:t.modelId.trim()}]}}};await tt(rt(e),{recursive:!0});let p=r===void 0?384:(await qe(e)).mode&511,n=`${e}.pi-web-${Ye(6).toString("hex")}.tmp`;try{await et(n,`${JSON.stringify(a,null,2)}
|
|
2
|
+
`,{mode:p,flag:"wx"}),await Qe(n,e)}finally{await Ze(n,{force:!0})}}function ne(e){return/^(?:npm:|git:|https?:\/\/|ssh:\/\/|git@)/i.test(e.trim())}var lt={"/":{path:"index.html",type:"text/html; charset=utf-8"},"/assets/app.js":{path:"assets/app.js",type:"text/javascript; charset=utf-8"},"/assets/style.css":{path:"assets/style.css",type:"text/css; charset=utf-8"}};function u(e,t,r){e.writeHead(t,{"Content-Type":"application/json; charset=utf-8","Cache-Control":"no-store"}),e.end(JSON.stringify(r))}function dt(e,t){let r=e.headers.authorization;if(typeof r!="string"||!r.startsWith("Bearer "))return!1;let i=Buffer.from(r.slice(7),"utf8");return i.length===t.length&&nt(i,t)}async function pt(e){let t=0,r=[];for await(let i of e){let a=Buffer.isBuffer(i)?i:Buffer.from(i);if(t+=a.length,t>64*1024)throw new Error("\u8BF7\u6C42\u5185\u5BB9\u8FC7\u5927");r.push(a)}return JSON.parse(Buffer.concat(r).toString("utf8"))}async function ut(e){let t=0,r=[];for await(let i of e){let a=Buffer.isBuffer(i)?i:Buffer.from(i);if(t+=a.length,t>L)throw new Error("\u6587\u4EF6\u8D85\u8FC7 20 MB");r.push(a)}return Buffer.concat(r)}async function mt(e){await new Promise((r,i)=>{e.once("error",i),e.listen(0,"127.0.0.1",()=>{e.off("error",i),r()})});let t=e.address();if(t===null||typeof t=="string")throw new Error("\u65E0\u6CD5\u786E\u5B9A\u76D1\u542C\u7AEF\u53E3");return t.port}async function oe(e,t){let r=st(24).toString("base64url"),i=Buffer.from(r),a=Z(),p=new Set,n=new Map,l=new W,c="",g=at(async(f,d)=>{d.setHeader("X-Content-Type-Options","nosniff"),d.setHeader("Referrer-Policy","no-referrer"),d.setHeader("Content-Security-Policy","default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self' data: blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'");let m;try{m=new URL(f.url??"/",c).pathname}catch{u(d,400,{error:"\u8BF7\u6C42\u5730\u5740\u65E0\u6548"});return}let A=lt[m];if(f.method==="GET"&&A){try{let o=await ot(ct(t,A.path));d.writeHead(200,{"Content-Type":A.type,"Cache-Control":"no-store"}),d.end(o)}catch{u(d,500,{error:"\u9875\u9762\u6587\u4EF6\u4E0D\u53EF\u7528"})}return}if(!m.startsWith("/api/")){u(d,404,{error:"\u672A\u627E\u5230"});return}if(!dt(f,i)){u(d,401,{error:"\u8BBF\u95EE\u4EE4\u724C\u65E0\u6548"});return}if(f.method==="GET"&&m==="/api/image"){let o=new URL(f.url??m,c),w=o.searchParams.get("sessionId")??"",b=o.searchParams.get("messageId")??"",h=Number(o.searchParams.get("index"));if(!w||w!==e.snapshot().sessionId||!b||!Number.isInteger(h)||h<0||h>100){u(d,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362\u6216\u56FE\u7247\u5730\u5740\u65E0\u6548"});return}let E=e.image?.(w,b,h);if(!E||!["image/png","image/jpeg","image/webp","image/gif"].includes(E.mimeType)){u(d,404,{error:"\u56FE\u7247\u4E0D\u53EF\u7528"});return}d.writeHead(200,{"Content-Type":E.mimeType,"Cache-Control":"private, no-store"}),d.end(E.data);return}if(f.method==="POST"&&m==="/api/attachment"){if(f.headers.origin!==void 0&&f.headers.origin!==c){u(d,403,{error:"\u6765\u6E90\u4E0D\u5339\u914D"});return}let o=new URL(f.url??m,c),w=o.searchParams.get("sessionId"),b=o.searchParams.get("name");try{if(!w||!b||b.length>1024||w!==e.snapshot().sessionId){u(d,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362\u6216\u6587\u4EF6\u540D\u65E0\u6548"});return}if(Number(f.headers["content-length"]??0)>L){u(d,413,{error:"\u6587\u4EF6\u8D85\u8FC7 20 MB"});return}let h=await re(w,b,f.headers["content-type"]??"application/octet-stream",await ut(f));if(w!==e.snapshot().sessionId){u(d,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}n.set(h.id,h),u(d,201,{id:h.id,name:h.name,bytes:h.bytes})}catch(h){u(d,400,{error:h instanceof Error?h.message:"\u4E0A\u4F20\u5931\u8D25"})}return}if(f.method==="GET"&&m==="/api/session"){try{u(d,200,e.snapshot())}catch{u(d,503,{error:"\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53EF\u7528"})}return}if(f.method==="GET"&&m==="/api/providers"){try{u(d,200,await e.providers?.()??[])}catch{u(d,503,{error:"\u6A21\u578B\u63D0\u4F9B\u65B9\u4E0D\u53EF\u7528"})}return}if(f.method==="GET"&&m==="/api/provider/login"){let o=new URL(f.url??m,c).searchParams.get("id")??"",w=l.get(o);u(d,w?200:404,w??{error:"\u767B\u5F55\u6D41\u7A0B\u4E0D\u5B58\u5728"});return}if(f.method==="GET"&&(m==="/api/models"||m==="/api/config"||m==="/api/workspaces"||m==="/api/commands")){try{u(d,200,m==="/api/models"?await e.models():m==="/api/config"?await e.config():m==="/api/commands"?e.commands?.()??[]:await e.workspaces())}catch{u(d,503,{error:"Pi \u914D\u7F6E\u4E0D\u53EF\u7528"})}return}if(f.method==="GET"&&m==="/api/directory/capability"){u(d,200,{kind:a});return}if(f.method==="GET"&&m==="/api/events"){let o;try{o=e.snapshot()}catch{u(d,503,{error:"\u5F53\u524D\u4F1A\u8BDD\u4E0D\u53EF\u7528"});return}d.writeHead(200,{"Content-Type":"application/x-ndjson; charset=utf-8","Cache-Control":"no-store",Connection:"keep-alive","X-Accel-Buffering":"no"}),p.add(d),d.write(`${JSON.stringify({type:"snapshot",session:o})}
|
|
3
|
+
`),d.on("close",()=>p.delete(d));return}if(f.method==="POST"&&["/api/message","/api/attachment/remove","/api/abort","/api/compact","/api/new-session","/api/model","/api/thinking-level","/api/config","/api/provider/login","/api/provider/login/respond","/api/provider/login/cancel","/api/provider/custom","/api/workspace/add","/api/workspace/select","/api/workspace/new-session","/api/workspace/remove","/api/session/select","/api/directory/list","/api/directory/create","/api/directory/pick"].includes(m)){if(f.headers.origin!==void 0&&f.headers.origin!==c){u(d,403,{error:"\u6765\u6E90\u4E0D\u5339\u914D"});return}if(!f.headers["content-type"]?.startsWith("application/json")){u(d,415,{error:"\u9700\u8981 JSON \u8BF7\u6C42"});return}try{let o=await pt(f);if(typeof o!="object"||o===null||!("sessionId"in o)||typeof o.sessionId!="string"){u(d,400,{error:"\u7F3A\u5C11\u4F1A\u8BDD\u6807\u8BC6"});return}if(m==="/api/provider/custom"){if(o.sessionId!==e.snapshot().sessionId){u(d,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(!("provider"in o)||!O(o.provider)||!e.addCustomProvider){u(d,400,{error:"\u81EA\u5B9A\u4E49\u63D0\u4F9B\u65B9\u65E0\u6548"});return}await e.addCustomProvider(o.sessionId,o.provider),u(d,201,{accepted:!0});return}if(m.startsWith("/api/provider/login")){if(o.sessionId!==e.snapshot().sessionId){u(d,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362"});return}if(m==="/api/provider/login"){if(!("providerId"in o)||typeof o.providerId!="string"||!("method"in o)||o.method!=="api_key"&&o.method!=="oauth"||!e.loginProvider||!e.providers){u(d,400,{error:"\u767B\u5F55\u65B9\u5F0F\u65E0\u6548"});return}if(!(await e.providers()).some(h=>h.id===o.providerId&&h.methods.includes(o.method))){u(d,400,{error:"\u63D0\u4F9B\u65B9\u4E0D\u652F\u6301\u6B64\u767B\u5F55\u65B9\u5F0F"});return}let b=l.start(o.providerId,o.method,e.loginProvider);u(d,202,{id:b.id});return}if(!("id"in o)||typeof o.id!="string"){u(d,400,{error:"\u767B\u5F55\u6D41\u7A0B\u65E0\u6548"});return}if(m==="/api/provider/login/cancel"){u(d,l.cancel(o.id)?202:404,{accepted:!0});return}if(!("value"in o)||typeof o.value!="string"||o.value.length>16384||!l.respond(o.id,o.value)){u(d,400,{error:"\u767B\u5F55\u8F93\u5165\u65E0\u6548"});return}u(d,202,{accepted:!0});return}if(m==="/api/attachment/remove"){if(!("id"in o)||typeof o.id!="string"||o.sessionId!==e.snapshot().sessionId){u(d,409,{error:"\u4F1A\u8BDD\u5DF2\u5207\u6362\u6216\u9644\u4EF6\u65E0\u6548"});return}n.get(o.id)?.sessionId===o.sessionId&&n.delete(o.id),u(d,202,{accepted:!0});return}if(m==="/api/directory/pick"){if(a!=="native"){u(d,409,{error:"\u672C\u5730\u76EE\u5F55\u9009\u62E9\u5668\u4E0D\u53EF\u7528"});return}let w=new AbortController;d.once("close",()=>w.abort()),u(d,200,{path:await q(w.signal)});return}if(m==="/api/directory/list"){if("path"in o&&(typeof o.path!="string"||o.path.length>4096)){u(d,400,{error:"\u76EE\u5F55\u8DEF\u5F84\u65E0\u6548"});return}u(d,200,await X("path"in o?o.path:void 0));return}if(m==="/api/directory/create"){if(!("path"in o)||typeof o.path!="string"||o.path.length>4096||!("name"in o)||typeof o.name!="string"||o.name.length>255){u(d,400,{error:"\u6587\u4EF6\u5939\u540D\u79F0\u6216\u8DEF\u5F84\u65E0\u6548"});return}u(d,200,{path:await Q(o.path,o.name)});return}if(m==="/api/workspace/add"){if(!("path"in o)||typeof o.path!="string"||o.path.length>4096||!("create"in o)||typeof o.create!="boolean"){u(d,400,{error:"\u5DE5\u4F5C\u533A\u8DEF\u5F84\u65E0\u6548"});return}await e.addWorkspace(o.path,o.create),u(d,202,{accepted:!0});return}if(m==="/api/workspace/select"||m==="/api/workspace/remove"||m==="/api/workspace/new-session"){if(!("id"in o)||typeof o.id!="string"){u(d,400,{error:"\u5DE5\u4F5C\u533A\u65E0\u6548"});return}m==="/api/workspace/select"?await e.selectWorkspace(o.id):m==="/api/workspace/new-session"?await e.newSessionInWorkspace(o.id):await e.removeWorkspace(o.id),u(d,202,{accepted:!0});return}if(m==="/api/session/select"){if(!("workspaceId"in o)||typeof o.workspaceId!="string"||!("id"in o)||typeof o.id!="string"||!("path"in o)||typeof o.path!="string"){u(d,400,{error:"\u4F1A\u8BDD\u65E0\u6548"});return}await e.selectSession(o.workspaceId,o.id,o.path),u(d,202,{accepted:!0});return}if(m==="/api/thinking-level"){if(!("level"in o)||typeof o.level!="string"){u(d,400,{error:"\u63A8\u7406\u5F3A\u5EA6\u65E0\u6548"});return}await e.setThinkingLevel(o.sessionId,o.level)}else if(m==="/api/model"){if(!("provider"in o)||typeof o.provider!="string"||!("id"in o)||typeof o.id!="string"){u(d,400,{error:"\u6A21\u578B\u65E0\u6548"});return}await e.setModel(o.sessionId,o.provider,o.id)}else if(m==="/api/config"){let w=o;if(!["packages","extensions","skills"].includes(w.kind)||!["global","project"].includes(w.scope)||!["add","remove"].includes(w.action)||typeof w.value!="string"||!w.value.trim()||w.value.length>2048){u(d,400,{error:"\u914D\u7F6E\u9879\u65E0\u6548"});return}if(w.action==="add"&&w.kind!=="packages"&&ne(w.value)){u(d,400,{error:"npm \u6216 Git \u6765\u6E90\u8BF7\u5728\u300C\u5305\u300D\u4E2D\u6DFB\u52A0\uFF1B\u6269\u5C55\u548C\u6280\u80FD\u53EA\u63A5\u53D7\u6587\u4EF6\u6216\u76EE\u5F55\u8DEF\u5F84"});return}u(d,200,await e.updateConfig(w));return}else if(m==="/api/message"){let w="attachments"in o?o.attachments:[];if(!Array.isArray(w)||w.length>te||w.some(v=>typeof v!="string")||new Set(w).size!==w.length){u(d,400,{error:"\u9644\u4EF6\u65E0\u6548\u6216\u8FC7\u591A"});return}let b=w.map(v=>n.get(v));if(b.some(v=>!v||v.sessionId!==o.sessionId)){u(d,400,{error:"\u9644\u4EF6\u4E0D\u53EF\u7528"});return}if(!("text"in o)||typeof o.text!="string"||!o.text.trim()&&w.length===0||o.text.length>32e3){u(d,400,{error:"\u6D88\u606F\u65E0\u6548\u6216\u8FC7\u957F"});return}let h=b,E=[o.text.trim(),...h.map(v=>`\u9644\u4EF6 ${JSON.stringify(v.name)}\uFF1A${v.path}`)].filter(Boolean).join(`
|
|
4
|
+
|
|
5
|
+
`),ke=await Promise.all(h.filter(v=>["image/png","image/jpeg","image/webp","image/gif"].includes(v.mediaType)).map(ie));await e.send(o.sessionId,E,ke);for(let v of w)n.delete(v)}else if(m==="/api/compact"){if(!e.compact)throw new Error("\u5F53\u524D\u4F1A\u8BDD\u4E0D\u652F\u6301\u538B\u7F29");await e.compact(o.sessionId)}else m==="/api/abort"?e.abort(o.sessionId):await e.newSession(o.sessionId);u(d,202,{accepted:!0})}catch(o){let w=o instanceof Error?o.message:"\u8BF7\u6C42\u5931\u8D25";u(d,w==="\u4F1A\u8BDD\u5DF2\u5207\u6362"?409:400,{error:w})}return}u(d,404,{error:"\u672A\u627E\u5230"})});return c=`http://127.0.0.1:${await mt(g)}`,{protocolVersion:3,url:`${c}/#${r}`,publish(f){if(f.type==="snapshot")for(let[m,A]of n)A.sessionId!==f.session.sessionId&&n.delete(m);let d=`${JSON.stringify(f)}
|
|
6
|
+
`;for(let m of p)m.write(d)},async close(){l.close();for(let f of p)f.end();p.clear(),await new Promise((f,d)=>g.close(m=>m?d(m):f()))}}}import{getSupportedThinkingLevels as gt}from"@earendil-works/pi-ai";function ae(e){let t=[],r=e;for(;;){let i=/(^|\n\n)附件 ("(?:\\.|[^"\\])*"):([^\n]+)$/u.exec(r);if(!i||!/[\\/]pi-web[\\/]attachments[\\/][a-f0-9]{64}[\\/]/u.test(i[3]))break;let a;try{a=JSON.parse(i[2])}catch{break}t.unshift({name:a,image:/\.(?:png|jpe?g|webp|gif)$/iu.test(a)}),r=r.slice(0,i.index)}return{text:r,attachments:t}}function C(e){return typeof e=="string"?[{kind:"text",text:e}]:e.flatMap(t=>t.type==="text"?[{kind:"text",text:t.text}]:t.type==="thinking"?[{kind:"thinking",text:t.thinking}]:t.type==="image"?[{kind:"image",text:"\u56FE\u7247\u9644\u4EF6"}]:t.type==="toolCall"?[{kind:"toolCall",text:JSON.stringify(t.arguments??{}),toolName:t.name,toolCallId:t.id}]:[])}function U(e){if(e.type==="custom_message")return e.display?{id:e.id,role:"notice",timestamp:e.timestamp,blocks:C(e.content)}:null;if(e.type!=="message")return null;let{message:t}=e;return t.role==="user"||t.role==="assistant"?{id:e.id,role:t.role,timestamp:e.timestamp,blocks:C(t.content),...t.role==="assistant"?{...t.stopReason==="error"&&t.errorMessage?{error:t.errorMessage}:{},usage:{input:t.usage.input,output:t.usage.output,cacheRead:t.usage.cacheRead,cacheWrite:t.usage.cacheWrite,...t.usage.reasoning===void 0?{}:{reasoning:t.usage.reasoning},totalTokens:t.usage.totalTokens,cost:t.usage.cost.total},model:`${t.provider}/${t.model}`}:{}}:t.role==="toolResult"?{id:e.id,role:"tool",timestamp:e.timestamp,blocks:C(t.content),isError:t.isError,toolCallId:t.toolCallId,toolName:t.toolName}:null}function $(e,t){let r=e?.trim();if(r)return r;let i=t.find(p=>p.role==="user"),a=ae(i?.blocks.find(p=>p.kind==="text")?.text??"").text.replace(/\s+/g," ").trim();return a?a.slice(0,50):"\u5F53\u524D\u4F1A\u8BDD"}function ce(e,t){let r=e.sessionManager.getBranch().flatMap(i=>{let a=U(i);return a?[a]:[]});return{schemaVersion:1,sessionId:e.sessionManager.getSessionId(),cwd:e.cwd,name:$(t,r),model:e.model?`${e.model.provider}/${e.model.id}`:null,thinkingLevel:e.thinkingLevel??null,thinkingLevels:e.model?gt(e.model):["off"],idle:e.isIdle(),contextUsage:e.getContextUsage()??null,messages:r}}import{randomUUID as le}from"node:crypto";import{mkdir as de,readFile as ft,realpath as wt,rename as ht,rm as yt,stat as vt,writeFile as kt}from"node:fs/promises";import{basename as bt,dirname as St,isAbsolute as Pt,join as xt}from"node:path";var N=class{items=[];mutationChain=Promise.resolve();file;constructor(t){this.file=t}async load(){try{let t=JSON.parse(await ft(this.file,"utf8"));if(!Array.isArray(t))throw new Error("\u5DE5\u4F5C\u533A\u8BB0\u5F55\u65E0\u6548");this.items=t.filter(r=>typeof r=="object"&&r!==null&&typeof r.id=="string"&&typeof r.path=="string"&&typeof r.title=="string")}catch(t){if(t.code!=="ENOENT")throw t}}list(){return[...this.items]}get(t){return this.items.find(r=>r.id===t)}async add(t,r=!1){if(!Pt(t)||t.includes("\0"))throw new Error("\u8BF7\u8F93\u5165\u5B8C\u6574\u7684\u76EE\u5F55\u8DEF\u5F84");r&&await de(t,{recursive:!0});let i=await wt(t);if(!(await vt(i)).isDirectory())throw new Error("\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55");return this.mutate(async()=>{let a=this.items.find(l=>l.path===i);if(a)return a;let p={id:le(),path:i,title:bt(i)||i},n=[p,...this.items];return await this.save(n),this.items=n,p})}async remove(t){await this.mutate(async()=>{if(!this.get(t))throw new Error("\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728");let r=this.items.filter(i=>i.id!==t);await this.save(r),this.items=r})}mutate(t){let r=this.mutationChain.then(t,t);return this.mutationChain=r.then(()=>{},()=>{}),r}async save(t){await de(St(this.file),{recursive:!0});let r=`${this.file}.${le()}.tmp`;try{await kt(r,JSON.stringify(t,null,2),{mode:384}),await ht(r,this.file)}catch(i){throw await yt(r,{force:!0}),i}}};function pe(e){return xt(e,"pi-web","workspaces.json")}import{createAgentSession as Ct,DefaultResourceLoader as Et,getAgentDir as At,SessionManager as V}from"@earendil-works/pi-coding-agent";import{realpath as me}from"node:fs/promises";import{realpath as ue}from"node:fs/promises";import{SessionManager as It}from"@earendil-works/pi-coding-agent";async function M(e){let t=await ue(e),r=await It.list(t),i=await Promise.all(r.map(async a=>a.cwd!==""&&await ue(a.cwd).catch(()=>null)===t));return r.filter((a,p)=>i[p])}var j=class{active=null;detach=null;cwd=null;lastStreamAt=0;publish;fallbackModel;constructor(t,r){this.publish=t,this.fallbackModel=r}get session(){return this.active}get path(){return this.cwd}async open(t,r){if(this.active&&!this.active.isIdle)throw new Error("\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F\u518D\u5207\u6362\u5DE5\u4F5C\u533A");let i=await me(t),a;if(r==="new")a=V.create(i);else if(r==="continue"){let c=(await M(i))[0];a=c?V.open(c.path):V.create(i)}else a=V.open(r.sessionFile);if(await me(a.getCwd())!==i)throw new Error("\u4F1A\u8BDD\u4E0D\u5C5E\u4E8E\u8BE5\u5DE5\u4F5C\u533A");let p=a.buildSessionProjection().model?void 0:this.active?.model??this.fallbackModel(),n=new Et({cwd:i,agentDir:At(),noExtensions:!0});await n.reload();let{session:l}=await Ct({cwd:i,sessionManager:a,model:p,resourceLoader:n});this.detach?.(),this.active?.dispose(),this.active=l,this.cwd=i,this.detach=l.subscribe(c=>{if(c.type==="message_update"&&c.message.role==="assistant"){let g=Date.now();g-this.lastStreamAt>=60&&(this.lastStreamAt=g,this.publish({type:"stream",message:{id:"stream",role:"assistant",timestamp:new Date(c.message.timestamp).toISOString(),blocks:C(c.message.content)}}))}(c.type==="message_end"||c.type==="agent_start"||c.type==="agent_settled"||c.type==="session_info_changed")&&(c.type==="message_end"&&c.message.role==="assistant"&&this.publish({type:"stream",message:null}),this.publish({type:"snapshot",session:this.snapshot()}))}),this.publish({type:"stream",message:null}),this.publish({type:"snapshot",session:this.snapshot()})}snapshot(){let t=this.active;if(!t||!this.cwd)throw new Error("\u6CA1\u6709\u6D3B\u52A8\u5DE5\u4F5C\u533A\u4F1A\u8BDD");let r=t.sessionManager.getBranch().flatMap(i=>{let a=U(i);return a?[a]:[]});return{schemaVersion:1,sessionId:t.sessionManager.getSessionId(),cwd:this.cwd,name:$(t.sessionManager.getSessionName(),r),model:t.model?`${t.model.provider}/${t.model.id}`:null,thinkingLevel:t.thinkingLevel,thinkingLevels:t.getAvailableThinkingLevels(),idle:t.isIdle,contextUsage:t.getContextUsage()??null,messages:r}}async send(t,r=[]){if(!this.active||!this.active.isIdle)throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");let i=this.active,a=!1,p,n,l=new Promise((c,g)=>{p=c,n=g});i.prompt(t,{images:r,preflightResult:c=>{c&&(a=!0,p())}}).then(()=>{a||p()}).catch(c=>{a?this.publish({type:"error",message:c instanceof Error?c.message:"Pi \u8BF7\u6C42\u5931\u8D25"}):n(c)}),await l}async stop(){await this.active?.abort()}async models(){return(await this.active?.modelRuntime.getAvailable()??[]).map(r=>({provider:r.provider,id:r.id,name:r.name}))}async setModel(t,r){if(!this.active||!this.active.isIdle)throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");let i=this.active.modelRuntime.getModel(t,r);if(!i)throw new Error("\u6A21\u578B\u4E0D\u53EF\u7528");await this.active.setModel(i),this.publish({type:"snapshot",session:this.snapshot()})}dispose(){this.detach?.(),this.active?.dispose(),this.detach=null,this.active=null,this.cwd=null}};import{basename as F,dirname as Mt}from"node:path";function ge(e,t){let r=i=>t[i].map(a=>({name:i==="skills"&&F(a.path).toLowerCase()==="skill.md"?F(Mt(a.path)):F(a.path),path:a.path,source:a.metadata.source,scope:a.metadata.scope,enabled:a.enabled}));return{packages:e.map(i=>({name:i.source,path:i.installedPath??"",source:i.source,scope:i.scope,enabled:!!i.installedPath})),extensions:r("extensions"),skills:r("skills")}}import{execFile as Tt}from"node:child_process";var Lt=(e,t)=>new Promise((r,i)=>{Tt(e,[...t],{windowsHide:!0},a=>{a?i(a):r()})});async function fe(e,t=process.platform,r=process.env,i=Lt){if(r.SSH_CONNECTION||r.SSH_TTY||r.SSH_CLIENT||t==="linux"&&!r.DISPLAY&&!r.WAYLAND_DISPLAY)return!1;let a=t==="darwin"?"open":t==="linux"?"xdg-open":t==="win32"?"explorer.exe":null;return a===null?!1:(await i(a,[e]),!0)}var Dt=Vt(Wt(jt(import.meta.url)),"../web/dist"),Ot=Symbol.for("pi-web.bridge-state"),s=globalThis[Ot]??={bridge:null,current:null,commandContext:null,pi:null,replacementContext:null,replacementSessionId:null,lastStreamAt:0,workspaces:null,workspaceSessions:null,selected:"tui",activeWorkspaceId:null},Ut=null;function $t(){return Ut??=Rt.create({refreshOnCreate:!1})}var R=new Map;async function H(){let e=await $t(),t=s.selected==="tui"?s.current?.modelRegistry:void 0,r=new Set(t?.getRegisteredProviderIds()??[]);for(let i of R.keys())r.has(i)||(e.unregisterProvider(i),R.delete(i));for(let i of r){let a=t?.getRegisteredNativeProvider(i),p=t?.getRegisteredProviderConfig(i),n=a??p;!n||R.get(i)===n||(a?e.registerNativeProvider(a):p&&e.registerProvider(i,p),R.set(i,n))}return await e.refresh({allowNetwork:!1}),e}function k(){if(s.selected==="sdk")return s.workspaceSessions?.snapshot()??(()=>{throw new Error("\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD")})();if(s.current===null)throw new Error("\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD");return ce(s.current,s.current.sessionManager.getSessionName())}function P(e){if(k().sessionId!==e)throw new Error("\u4F1A\u8BDD\u5DF2\u5207\u6362")}async function he(){let e=s.workspaces?.list()??[],t=k(),r=await B(t.cwd).catch(()=>t.cwd),i=(await Promise.all(e.map(async a=>{let n=(await M(a.path).catch(()=>[])).map(l=>({id:l.id,workspaceId:a.id,path:l.path,name:l.name||l.firstMessage?.slice(0,50)||"\u5F53\u524D\u4F1A\u8BDD",modified:l.modified.toISOString()}));return a.path===r&&!n.some(l=>l.id===t.sessionId)&&n.unshift({id:t.sessionId,workspaceId:a.id,path:null,name:t.name,modified:new Date().toISOString()}),n}))).flat();return{items:e,activeId:s.activeWorkspaceId,sessions:i}}async function x(){s.bridge&&s.bridge.publish({type:"workspaces",value:await he()})}async function J(e,t=!1){let r=s.workspaces?.get(e);if(!r)throw new Error("\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728");if(await B(r.path),!t&&s.current&&r.path===await B(s.current.cwd)){if(s.selected==="sdk"&&s.workspaceSessions?.session&&!s.workspaceSessions.session.isIdle)throw new Error("\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F\u518D\u5207\u6362\u5DE5\u4F5C\u533A");s.selected="tui",s.activeWorkspaceId=e,s.bridge?.publish({type:"stream",message:null}),s.bridge?.publish({type:"snapshot",session:k()})}else{if(!s.workspaceSessions)throw new Error("Pi \u5DE5\u4F5C\u533A\u670D\u52A1\u4E0D\u53EF\u7528");await s.workspaceSessions.open(r.path,t?"new":"continue"),s.selected="sdk",s.activeWorkspaceId=e,s.bridge?.publish({type:"snapshot",session:k()})}await x()}function I(e){let t=s.current;if(t===null||t.sessionManager.getSessionId()!==e)throw new Error("\u4F1A\u8BDD\u5DF2\u5207\u6362");return t}function S(e){s.current=e,s.selected==="tui"&&s.bridge?.publish({type:"snapshot",session:k()})}function ye(){if(s.current===null)throw new Error("\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD");let e=s.selected==="sdk"?s.workspaceSessions?.path:s.current.cwd;if(!e)throw new Error("\u6CA1\u6709\u6D3B\u52A8\u5DE5\u4F5C\u533A");let t=s.selected==="tui"?s.current.isProjectTrusted():s.workspaceSessions?.session?.settingsManager.isProjectTrusted()??!1;return Bt.create(e,void 0,{projectTrusted:t})}async function ve(){let e=ye(),t=a=>({packages:(a.packages??[]).map(p=>typeof p=="string"?p:p.source),extensions:a.extensions??[],skills:a.skills??[]}),r=new we({cwd:k().cwd,agentDir:_(),settingsManager:e}),i=ge(r.listConfiguredPackages(),await r.resolve(async()=>"skip"));return{projectTrusted:e.isProjectTrusted(),global:t(e.getGlobalSettings()),project:t(e.getProjectSettings()),installed:i}}async function Ft(e){P(e.sessionId);let t=s.selected==="tui"?I(e.sessionId):null;if(t?!t.isIdle():!s.workspaceSessions?.session?.isIdle)throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");let r=ye();if(e.scope==="project"&&!r.isProjectTrusted())throw new Error("\u5F53\u524D\u9879\u76EE\u5C1A\u672A\u53D7\u4FE1\u4EFB\uFF0C\u65E0\u6CD5\u4FEE\u6539\u9879\u76EE\u8BBE\u7F6E");let i=e.value.trim(),a=e.scope==="project"?r.getProjectSettings():r.getGlobalSettings();if(e.kind==="packages"){let p=new we({cwd:k().cwd,agentDir:_(),settingsManager:r});e.action==="add"?await p.installAndPersist(i,{local:e.scope==="project"}):await p.removeAndPersist(i,{local:e.scope==="project"})}else if(e.kind==="extensions"){let p=a.extensions??[],n=e.action==="add"?[...new Set([...p,i])]:p.filter(l=>l!==i);e.scope==="project"?r.setProjectExtensionPaths(n):r.setExtensionPaths(n)}else{let p=a.skills??[],n=e.action==="add"?[...new Set([...p,i])]:p.filter(l=>l!==i);e.scope==="project"?r.setProjectSkillPaths(n):r.setSkillPaths(n)}return await r.flush(),s.selected==="tui"?(s.commandContext&&await s.commandContext.reload(),s.current&&S(s.current)):(await s.workspaceSessions?.session?.resourceLoader.reload(),s.workspaceSessions?.session?.refreshContext()),await ve()}function Ht(e,t=fe){s.pi=e,e.on("session_start",(r,i)=>{s.replacementSessionId!==i.sessionManager.getSessionId()&&(s.replacementContext=null,s.replacementSessionId=null),S(i),s.selected==="tui"&&s.workspaces&&s.workspaces.add(i.cwd).then(async a=>{s.selected!=="tui"||s.current?.sessionManager.getSessionId()!==i.sessionManager.getSessionId()||(s.activeWorkspaceId=a.id,await x())}).catch(a=>{s.bridge?.publish({type:"error",message:a instanceof Error?a.message:"\u65E0\u6CD5\u6CE8\u518C\u5F53\u524D\u5DE5\u4F5C\u533A"})})}),e.on("agent_start",(r,i)=>S(i)),e.on("agent_end",(r,i)=>S(i)),e.on("agent_settled",(r,i)=>S(i)),e.on("model_select",(r,i)=>S(i)),e.on("thinking_level_select",(r,i)=>S(i)),e.on("message_end",(r,i)=>{s.selected==="tui"&&r.message.role==="assistant"&&s.bridge?.publish({type:"stream",message:null}),S(i),s.selected==="tui"&&x()}),e.on("session_info_changed",(r,i)=>S(i)),e.on("message_update",(r,i)=>{if(s.current=i,s.selected!=="tui"||r.message.role!=="assistant"||s.bridge===null)return;let a=Date.now();a-s.lastStreamAt<60||(s.lastStreamAt=a,s.bridge.publish({type:"stream",message:{id:"stream",role:"assistant",timestamp:new Date(r.message.timestamp).toISOString(),blocks:C(r.message.content)}}))}),e.on("session_shutdown",r=>{if(s.current=null,r.reason==="new"||r.reason==="resume"||r.reason==="fork")return;let i=s.bridge;s.bridge=null,s.commandContext=null,s.replacementContext=null,s.replacementSessionId=null,s.pi=null,s.workspaceSessions?.dispose(),s.workspaceSessions=null,s.selected="tui",s.activeWorkspaceId=null,i&&i.close()}),e.registerCommand("web",{description:"Open the current Pi session in a local web interface",handler:async(r,i)=>{s.current=i,s.commandContext=i,s.pi=e,s.workspaces===null&&(s.workspaces=new N(pe(_())),await s.workspaces.load());let a=await s.workspaces.add(i.cwd);if(s.selected==="tui"&&(s.activeWorkspaceId=a.id),s.bridge&&s.bridge.protocolVersion!==3){let n=s.bridge;s.bridge=null,await n.close()}s.bridge===null&&(s.workspaceSessions=new j(n=>{s.selected==="sdk"&&(s.bridge?.publish(n),n.type==="snapshot"&&n.session.idle&&x())},()=>s.current?.model),s.bridge=await oe({snapshot:k,image(n,l,c){P(n);let y=(s.selected==="sdk"?s.workspaceSessions?.session?.sessionManager.getBranch():s.current?.sessionManager.getBranch())?.find(d=>d.id===l);if(y?.type!=="message"||y.message.role!=="user"||!Array.isArray(y.message.content))return null;let f=y.message.content[c];return f?.type!=="image"?null:{data:Buffer.from(f.data,"base64"),mimeType:f.mimeType}},async workspaces(){return he()},async addWorkspace(n,l){let c=await s.workspaces.add(n,l);try{await J(c.id,!0)}catch(g){throw await x(),g}},async selectWorkspace(n){await J(n)},async newSessionInWorkspace(n){await J(n,!0)},async removeWorkspace(n){if(n===s.activeWorkspaceId)throw new Error("\u8BF7\u5148\u5207\u6362\u5230\u5176\u4ED6\u5DE5\u4F5C\u533A");await s.workspaces.remove(n),await x()},async selectSession(n,l,c){let g=s.workspaces?.get(n);if(!g)throw new Error("\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728");if(l===s.current?.sessionManager.getSessionId()&&g.path===await B(s.current.cwd)){if(s.selected==="sdk"&&s.workspaceSessions?.session&&!s.workspaceSessions.session.isIdle)throw new Error("\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F\u518D\u5207\u6362\u4F1A\u8BDD");s.selected="tui",s.activeWorkspaceId=n,s.bridge?.publish({type:"snapshot",session:k()})}else{if(s.selected==="sdk"&&s.workspaceSessions?.session?.sessionManager.getSessionId()===l&&s.workspaceSessions.path===g.path)return;if(!(await M(g.path)).some(f=>f.id===l&&f.path===c))throw new Error("\u4F1A\u8BDD\u4E0D\u5C5E\u4E8E\u8BE5\u5DE5\u4F5C\u533A");await s.workspaceSessions.open(g.path,{sessionFile:c}),s.selected="sdk",s.activeWorkspaceId=n,s.bridge?.publish({type:"snapshot",session:k()})}await x()},async models(){if(s.selected==="sdk")return s.workspaceSessions?.models()??[];let n=s.current;if(!n)throw new Error("\u6CA1\u6709\u6D3B\u52A8\u4F1A\u8BDD");let l=n.scopedModels?.map(c=>`${c.model.provider}/${c.model.id}`)??[];return n.modelRegistry.getAvailable().filter(c=>l.length===0||l.includes(`${c.provider}/${c.id}`)).map(c=>({provider:c.provider,id:c.id,name:c.name}))},async providers(){let n=await H();return n.getProviders().map(l=>({id:l.id,name:l.name,configured:n.getProviderAuthStatus(l.id).configured,methods:[l.auth.apiKey?.login?"api_key":null,l.auth.oauth?"oauth":null].filter(c=>c!==null)}))},async loginProvider(n,l,c){let g=await H();await g.login(n,l,c),await g.refresh({providers:[n],allowNetwork:!1}),await s.current?.modelRegistry.refresh({providers:[n],allowNetwork:!1}),await s.workspaceSessions?.session?.modelRuntime.refresh({providers:[n],allowNetwork:!1})},async addCustomProvider(n,l){if(P(n),s.selected==="tui"?!I(n).isIdle():!s.workspaceSessions?.session?.isIdle)throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");let c=await H();if(c.getProvider(l.id))throw new Error("\u63D0\u4F9B\u65B9\u5DF2\u5B58\u5728");await se(Nt(_(),"models.json"),l),await c.refresh({allowNetwork:!1}),await s.current?.modelRegistry.refresh({allowNetwork:!1}),await s.workspaceSessions?.session?.modelRuntime.refresh({allowNetwork:!1})},commands(){return s.selected==="tui"?(s.pi?.getCommands()??[]).filter(n=>n.name!=="web").map(n=>({name:n.name,description:n.description})):[]},async compact(n){if(P(n),s.selected==="sdk"){let c=s.workspaceSessions?.session;if(!c||!c.isIdle)throw new Error("\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");await c.compact(),s.bridge?.publish({type:"snapshot",session:k()});return}let l=I(n);if(!l.isIdle())throw new Error("\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");l.compact()},async setModel(n,l,c){if(P(n),s.selected==="sdk"){await s.workspaceSessions.setModel(l,c);return}let g=I(n);if(!g.isIdle())throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");let y=g.modelRegistry.find(l,c);if(!y||!g.modelRegistry.getAvailable().some(f=>f.provider===l&&f.id===c))throw new Error("\u6A21\u578B\u4E0D\u53EF\u7528");if(!s.pi||!await s.pi.setModel(y))throw new Error("\u6A21\u578B\u63D0\u4F9B\u65B9\u672A\u914D\u7F6E\u8BA4\u8BC1");S(g)},async setThinkingLevel(n,l){if(P(n),!["off","minimal","low","medium","high","xhigh"].includes(l))throw new Error("\u63A8\u7406\u5F3A\u5EA6\u65E0\u6548");if(s.selected==="sdk"){let y=s.workspaceSessions?.session;if(!y||!y.isIdle||!y.getAvailableThinkingLevels().includes(l))throw new Error("\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u6B64\u63A8\u7406\u5F3A\u5EA6");y.setThinkingLevel(l),s.bridge?.publish({type:"snapshot",session:k()});return}let g=I(n);if(!g.isIdle())throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");if(!g.model||!_t(g.model).includes(l))throw new Error("\u5F53\u524D\u6A21\u578B\u4E0D\u652F\u6301\u6B64\u63A8\u7406\u5F3A\u5EA6");if(!s.pi)throw new Error("Pi \u4F1A\u8BDD\u4E0D\u53EF\u7528");s.pi.setThinkingLevel(l),S(g)},config:ve,updateConfig:Ft,async send(n,l,c=[]){if(P(n),s.selected==="sdk"){await s.workspaceSessions.send(l,c);return}if(!I(n).isIdle())throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");if(s.replacementContext!==null&&s.replacementSessionId===n){await s.replacementContext.sendUserMessage(c.length?[{type:"text",text:l},...c]:l,{expandPromptTemplates:!0});return}if(s.pi===null)throw new Error("Pi \u4F1A\u8BDD\u4E0D\u53EF\u7528");s.pi.sendUserMessage(c.length?[{type:"text",text:l},...c]:l,{expandPromptTemplates:!0})},abort(n){P(n),s.selected==="sdk"?s.workspaceSessions.stop():I(n).abort()},async newSession(n){if(P(n),s.selected==="sdk"){let g=s.workspaces?.get(s.activeWorkspaceId??"");if(!g)throw new Error("\u5DE5\u4F5C\u533A\u4E0D\u5B58\u5728");await s.workspaceSessions.open(g.path,"new"),await x();return}if(!I(n).isIdle())throw new Error("Pi \u6B63\u5728\u8FD0\u884C\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u56DE\u590D\u7ED3\u675F");if(s.commandContext===null)throw new Error("\u8BF7\u5728 Pi \u4E2D\u91CD\u65B0\u8F93\u5165 /web");if((await s.commandContext.newSession({withSession:async g=>{s.commandContext=g,s.replacementContext=g,s.replacementSessionId=g.sessionManager.getSessionId(),s.current=g}})).cancelled)throw new Error("\u65B0\u4F1A\u8BDD\u5DF2\u53D6\u6D88");await x()}},Dt));let p=await t(s.bridge.url).catch(()=>!1);i.ui.notify(`pi-webapp: ${s.bridge.url}`,p?"info":"warning"),i.ui.setStatus("pi-webapp","Web \u5DF2\u542F\u52A8 \xB7 /web \u91CD\u65B0\u6253\u5F00")}})}export{Ht as default};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-webapp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A browser interface for Pi with live chat, workspaces, models, and attachments",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package"
|
|
9
|
+
],
|
|
10
|
+
"pi": {
|
|
11
|
+
"image": "https://unpkg.com/pi-webapp@0.1.0/assets/cover-webapp.png",
|
|
12
|
+
"extensions": [
|
|
13
|
+
"./dist/extension.js"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"assets/cover-webapp.png",
|
|
18
|
+
"dist",
|
|
19
|
+
"web/dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"check": "tsc --noEmit && tsc -p web/tsconfig.json",
|
|
25
|
+
"test": "node --experimental-strip-types --test test/*.test.ts",
|
|
26
|
+
"build": "npm run build:web && npm run build:extension",
|
|
27
|
+
"build:web": "vite build",
|
|
28
|
+
"build:extension": "esbuild extension/index.ts --bundle --platform=node --format=esm --target=node22 --packages=external --minify --outfile=dist/extension.js",
|
|
29
|
+
"prepack": "npm run build",
|
|
30
|
+
"prepublishOnly": "npm run check && npm run test"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@earendil-works/pi-ai": "*",
|
|
37
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@earendil-works/pi-ai": "0.87.1",
|
|
41
|
+
"@earendil-works/pi-coding-agent": "0.87.1",
|
|
42
|
+
"@types/node": "22.19.19",
|
|
43
|
+
"@types/react": "^18.3.1",
|
|
44
|
+
"@types/react-dom": "^18.3.0",
|
|
45
|
+
"esbuild": "^0.28.2",
|
|
46
|
+
"typescript": "7.0.2",
|
|
47
|
+
"vite": "^6.4.3"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22.19.0"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"anser": "^2.3.5",
|
|
54
|
+
"clsx": "^2.1.1",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"react-dom": "^18.3.1",
|
|
57
|
+
"react-markdown": "^10.1.0",
|
|
58
|
+
"remark-gfm": "^4.0.1"
|
|
59
|
+
}
|
|
60
|
+
}
|