pi-web-ui 0.51.0 → 0.53.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/README.md +1 -1
- package/bin/pi-web-ui.mjs +210 -180
- package/deploy/com.xingshuyin.pi-web-ui.plist +1 -1
- package/deploy/nginx-subpath.conf +1 -1
- package/deploy/pi-web-ui-task.xml +3 -20
- package/deploy/pi-web-ui.service +1 -1
- package/dist/server/agent-service.js +58 -5
- package/dist/server/index.js +2 -2
- package/package.json +2 -2
- package/web/dist/assets/{TerminalPanel-uB-DaBC5.js → TerminalPanel-CgkoEcJf.js} +1 -1
- package/web/dist/assets/{index-GrSZ6qyt.js → index-KGJNsUEy.js} +13 -13
- package/web/dist/index.html +1 -1
|
@@ -2,26 +2,9 @@
|
|
|
2
2
|
<!--
|
|
3
3
|
pi-web-ui Task Scheduler task — Windows auto-start at logon.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
pi-web-ui server status | restart | stop | uninstall
|
|
9
|
-
|
|
10
|
-
Manual install with this template (edit the paths below first):
|
|
11
|
-
schtasks /Create /TN "pi-web-ui" /XML pi-web-ui-task.xml /F
|
|
12
|
-
schtasks /Run /TN "pi-web-ui"
|
|
13
|
-
|
|
14
|
-
Notes:
|
|
15
|
-
- The task runs the PowerShell launcher the CLI generates at
|
|
16
|
-
%APPDATA%\pi-web-ui\pi-web-ui.ps1 with -WindowStyle Hidden, so the
|
|
17
|
-
server runs with no black console window (nothing to accidentally
|
|
18
|
-
close/kill). The ps1 sets PORT/PI_WEB_CWD, cd's to the workspace,
|
|
19
|
-
launches node, and appends output to %USERPROFILE%\pi-web-ui.log.
|
|
20
|
-
Preview both generated files with: pi-web-ui server install --print
|
|
21
|
-
- Save this file as UTF-16 LE (schtasks requires it; the CLI does this
|
|
22
|
-
automatically when it writes the task XML).
|
|
23
|
-
- LogonTrigger = starts when you log in, same as a launchd user agent.
|
|
24
|
-
For boot-start without login, use Docker instead (see README).
|
|
5
|
+
pi-web-ui server install 默认使用「登录 Run 键 + wscript 隐藏启动」(HKCU,无需管理员、
|
|
6
|
+
无黑窗),并自动迁移旧版计划任务安装。本 XML 仅保留给需要手动注册计划任务的场景
|
|
7
|
+
(例如管理员环境),不是默认路径。
|
|
25
8
|
-->
|
|
26
9
|
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
27
10
|
<RegistrationInfo>
|
package/deploy/pi-web-ui.service
CHANGED
|
@@ -19,7 +19,7 @@ Type=simple
|
|
|
19
19
|
User=YOUR_USER
|
|
20
20
|
# The workspace the agent operates in (read/edit/bash/write)
|
|
21
21
|
WorkingDirectory=/home/YOUR_USER
|
|
22
|
-
Environment=
|
|
22
|
+
Environment=PI_WEB_PORT=8787
|
|
23
23
|
# Point at your pi config dir if it's not the default ~/.pi/agent
|
|
24
24
|
#Environment=PI_CODING_AGENT_DIR=/home/YOUR_USER/.pi/agent
|
|
25
25
|
ExecStart=/usr/bin/pi-web-ui
|
|
@@ -2341,7 +2341,16 @@ export class ClientSession {
|
|
|
2341
2341
|
this.stateStore.removeProject(this.clientId, path);
|
|
2342
2342
|
await this.pushProjects();
|
|
2343
2343
|
}
|
|
2344
|
-
/** Permanently delete a persisted session transcript file (history list ✕).
|
|
2344
|
+
/** Permanently delete a persisted session transcript file (history list ✕).
|
|
2345
|
+
*
|
|
2346
|
+
* Deleting the ACTIVE conversation's own transcript is allowed: the session
|
|
2347
|
+
* first switches away to the next-latest persisted chat (or a fresh blank
|
|
2348
|
+
* chat when no other history exists). If the displacement could not release
|
|
2349
|
+
* the file (streaming / open terminals / pending wake subscription /
|
|
2350
|
+
* conversation cap), the deletion is aborted with a notice instead of
|
|
2351
|
+
* yanking the file out of a live runtime. Background conversations still
|
|
2352
|
+
* block deletion outright.
|
|
2353
|
+
*/
|
|
2345
2354
|
async deleteSession(path) {
|
|
2346
2355
|
try {
|
|
2347
2356
|
const abs = resolve(path);
|
|
@@ -2356,13 +2365,57 @@ export class ClientSession {
|
|
|
2356
2365
|
});
|
|
2357
2366
|
return;
|
|
2358
2367
|
}
|
|
2359
|
-
//
|
|
2360
|
-
|
|
2361
|
-
|
|
2368
|
+
// A live conversation may hold the target transcript. A BACKGROUND
|
|
2369
|
+
// conversation must still block deletion outright, but when the ACTIVE
|
|
2370
|
+
// conversation holds it the request can be satisfied by switching away
|
|
2371
|
+
// first (next-latest history chat, or a fresh blank one) and letting
|
|
2372
|
+
// the displacement drop the old runtime.
|
|
2373
|
+
const holdsTarget = (conv) => {
|
|
2374
|
+
const file = conv.session.sessionFile;
|
|
2375
|
+
return file !== undefined && resolve(file) === abs;
|
|
2376
|
+
};
|
|
2377
|
+
const holder = [...this.convs.values()].find(holdsTarget);
|
|
2378
|
+
if (holder && holder.id !== this.activeId) {
|
|
2379
|
+
this.emit({
|
|
2380
|
+
type: "notice",
|
|
2381
|
+
level: "warning",
|
|
2382
|
+
text: "该对话正在后台运行,请先停止或关闭该对话再删除",
|
|
2383
|
+
});
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
if (holder) {
|
|
2387
|
+
// Same source the history panel uses (refreshSessions): newest first.
|
|
2388
|
+
const infos = await SessionManager.list(this.cwd);
|
|
2389
|
+
const next = infos
|
|
2390
|
+
.filter((s) => resolve(s.path) !== abs)
|
|
2391
|
+
.sort((a, b) => b.modified.getTime() - a.modified.getTime())[0];
|
|
2392
|
+
if (next)
|
|
2393
|
+
await this.switchSession(next.path);
|
|
2394
|
+
else
|
|
2395
|
+
await this.newChat();
|
|
2396
|
+
// displaceActive() may have RETAINED the old conversation as a
|
|
2397
|
+
// background run (streaming, open terminals, pending wake
|
|
2398
|
+
// subscription, conversation cap) — in every such case the file is
|
|
2399
|
+
// still held, so abort instead of yanking it from a live runtime.
|
|
2400
|
+
// Only a conversation that is genuinely still running in the
|
|
2401
|
+
// background (streaming / listed) keeps the "wait for it" notice;
|
|
2402
|
+
// a retained-but-idle hold means the switch itself failed (cap,
|
|
2403
|
+
// quiesce, runtime creation) — say that instead.
|
|
2404
|
+
const stillHeld = [...this.convs.values()].find(holdsTarget);
|
|
2405
|
+
if (stillHeld) {
|
|
2406
|
+
let stillRunning = stillHeld.listed;
|
|
2407
|
+
try {
|
|
2408
|
+
stillRunning = stillHeld.session.isStreaming || stillRunning;
|
|
2409
|
+
}
|
|
2410
|
+
catch {
|
|
2411
|
+
// session being replaced — keep the listed-flag fallback
|
|
2412
|
+
}
|
|
2362
2413
|
this.emit({
|
|
2363
2414
|
type: "notice",
|
|
2364
2415
|
level: "warning",
|
|
2365
|
-
text:
|
|
2416
|
+
text: stillRunning
|
|
2417
|
+
? "对话仍在后台运行,已停止删除;请等待其结束后再删除"
|
|
2418
|
+
: "未能切换到其他对话,已取消删除本次操作",
|
|
2366
2419
|
});
|
|
2367
2420
|
return;
|
|
2368
2421
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* protocol defined in protocol.ts.
|
|
8
8
|
*
|
|
9
9
|
* Env:
|
|
10
|
-
*
|
|
10
|
+
* PI_WEB_PORT HTTP port (default 8787; legacy PORT also honored)
|
|
11
11
|
* PI_WEB_CWD workspace the agent operates in (default: process.cwd())
|
|
12
12
|
* PI_WEB_DATA_DIR where per-client UI state is stored (client-state.json,
|
|
13
13
|
* default: <home>/.pi-web). Chat sessions are NOT stored here — they live
|
|
@@ -37,7 +37,7 @@ import { ensureWindowsBash, windowsBashDir } from "./ensure-bash.js";
|
|
|
37
37
|
import { listThemes, resolveThemeFile } from "./themes.js";
|
|
38
38
|
import { PluginManager, resolvePluginClientFile } from "./plugins.js";
|
|
39
39
|
import { McpBridge } from "./mcp-bridge.js";
|
|
40
|
-
const PORT = Number(process.env.PORT ?? 8787);
|
|
40
|
+
const PORT = Number(process.env.PI_WEB_PORT ?? process.env.PORT ?? 8787);
|
|
41
41
|
const CWD = resolve(process.env.PI_WEB_CWD ?? process.cwd());
|
|
42
42
|
const DATA_DIR = resolve(process.env.PI_WEB_DATA_DIR ?? join(homedir(), ".pi-web"));
|
|
43
43
|
/** Bind address. Default is loopback ONLY — the service is a local personal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-web-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"prepublishOnly": "npm run build",
|
|
50
50
|
"dev": "concurrently -k -n server,web -c blue,green \"npm:dev:server\" \"npm:dev:web\"",
|
|
51
|
-
"dev:server": "cross-env
|
|
51
|
+
"dev:server": "cross-env PI_WEB_PORT=8788 PI_WEB_ALLOW_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 node --watch --import tsx server/index.ts",
|
|
52
52
|
"dev:web": "vite --config web/vite.config.ts",
|
|
53
53
|
"build": "npm run build:web && npm run build:server",
|
|
54
54
|
"build:web": "vite build --config web/vite.config.ts",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as d,j as n}from"./markdown-DRBrS2Nf.js";import{b as $,T as R,u as O,F as P,a as K,c as q,d as L,e as B,f as J,g as X,h as G,r as Q}from"./index-
|
|
1
|
+
import{a as d,j as n}from"./markdown-DRBrS2Nf.js";import{b as $,T as R,u as O,F as P,a as K,c as q,d as L,e as B,f as J,g as X,h as G,r as Q}from"./index-KGJNsUEy.js";import{D as U,o as V}from"./xterm-D1D2FVe3.js";import"./react-C9ovnpIm.js";function W({conversationId:s,terminalId:r,command:x,cwd:a,active:f,send:m,register:v}){const w=d.useRef(null),N=d.useRef(null),b=x?JSON.stringify(x):"";return d.useEffect(()=>{const p=w.current;if(!p)return;const t=new U({theme:$(),fontFamily:'"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',fontSize:13,cursorBlink:!0,scrollback:8e3}),c=new V;t.loadAddon(c),t.open(p),N.current={term:t,fit:c},f&&t.focus();const h=()=>{t.options.theme=$()};window.addEventListener(R,h),t.attachCustomKeyEventHandler(o=>{var F;if(o.type!=="keydown")return!0;const E=(F=o.key)==null?void 0:F.toLowerCase();if((o.ctrlKey||o.metaKey)&&E==="v")return!1;if(o.ctrlKey&&!o.shiftKey&&!o.altKey&&E==="c"&&t.hasSelection()){const T=t.textarea;return T&&(T.value=t.getSelection(),T.select()),!1}return!0});const g=v(s,r,{write:o=>t.write(o),dispose:()=>t.dispose()}),C=()=>{try{c.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.cols,rows:t.rows})}catch{}},j=requestAnimationFrame(()=>{try{c.fit()}catch{}m(x?{type:"run_command",terminalId:r,conversationId:s,command:x,cols:t.cols,rows:t.rows}:{type:"terminal_create",terminalId:r,conversationId:s,cwd:a,cols:t.cols,rows:t.rows})}),k=t.onData(o=>{m({type:"terminal_input",terminalId:r,conversationId:s,data:o})});let y=null;return typeof ResizeObserver<"u"&&(y=new ResizeObserver(()=>{p.offsetWidth>0&&p.offsetHeight>0&&C()}),y.observe(p)),()=>{cancelAnimationFrame(j),k.dispose(),window.removeEventListener(R,h),y==null||y.disconnect(),g(),t.dispose(),N.current=null}},[s,r,b,m,v]),d.useEffect(()=>{if(!f)return;const p=requestAnimationFrame(()=>{const t=N.current;if(t){try{t.fit.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.term.cols,rows:t.term.rows})}catch{}t.term.focus()}});return()=>cancelAnimationFrame(p)},[f]),n.jsx("div",{ref:w,className:`term-xterm ${f?"":"hidden"}`})}const A={name:"",command:"",cwd:"${pwd}"};function se({chat:s,send:r,terminal:x}){const a=O(),[f,m]=d.useState(null),[v,w]=d.useState(!1),[N,b]=d.useState(!1),[p,t]=d.useState(null),[c,h]=d.useState(A),[g,C]=d.useState(null),j=d.useRef(null);d.useEffect(()=>{s.terminals.length===0?m(null):s.terminals.some(e=>e.id===f)||m(s.terminals[s.terminals.length-1].id)},[s.terminals,f]),d.useEffect(()=>()=>{j.current&&clearTimeout(j.current)},[]);const k=e=>{var u;if(!s.ready)return;const i=Q(),l=s.activeConversationId||((u=s.state)==null?void 0:u.conversationId)||"";x.create({...e,id:i,conversationId:l,cols:e.cols??80,rows:e.rows??24,running:!0,exitCode:null}),m(i),w(!1)},y=()=>{var e;return k({title:a("terminalTitle",{n:s.terminals.length+1}),cwd:((e=s.state)==null?void 0:e.cwd)??""})},o=e=>{var u;const i=e.name||e.command,l=s.terminals.find(_=>_.title===i);if(l){x.restart(l.id),m(l.id),r({type:"run_command",terminalId:l.id,conversationId:l.conversationId,command:e,cols:80,rows:24});return}k({title:i,cwd:((u=s.state)==null?void 0:u.cwd)??"",command:e})},E=e=>{const i=s.terminals.find(l=>l.id===e);if(i&&r({type:"terminal_kill",terminalId:e,conversationId:i.conversationId}),x.close(e),f===e){const l=s.terminals.filter(u=>u.id!==e);m(l.length>0?l[l.length-1].id:null)}},F=()=>{b(!0),t(null),h(A)},T=e=>{const i=s.commands[e];i&&(b(!1),t(e),h({name:i.name,command:i.command,cwd:i.cwd??""}))},D=()=>{b(!1),t(null)},S=()=>{const e=c.name.trim(),i=c.command.trim();if(!e||!i)return;const l=c.cwd.trim(),u={name:e,command:i,cwd:l||void 0},_=N?[...s.commands,u]:p!==null?s.commands.map((z,H)=>H===p?u:z):s.commands;r({type:"save_commands",commands:_}),D()},I=e=>{if(g===e){const i=s.commands.filter((l,u)=>u!==e);r({type:"save_commands",commands:i}),C(null),j.current&&clearTimeout(j.current)}else C(e),j.current&&clearTimeout(j.current),j.current=setTimeout(()=>C(null),2500)},M=N||p!==null;return n.jsxs("div",{className:"terminal-view",children:[n.jsxs("aside",{className:`term-side term-commands ${v?"open":""}`,children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("commands")}),n.jsxs("div",{className:"panel-header-actions",children:[n.jsx("button",{type:"button",className:"panel-refresh",title:a("rerun"),onClick:()=>r({type:"list_commands"}),children:n.jsx(P,{})}),n.jsx("button",{type:"button",className:"panel-new",title:a("newCommand"),onClick:F,children:n.jsx(K,{})})]})]}),n.jsx("div",{className:"panel-body",children:M?n.jsxs("div",{className:"cmd-form",children:[n.jsx("label",{htmlFor:"cmd-name",children:a("name")}),n.jsx("input",{id:"cmd-name",className:"cmd-input",value:c.name,placeholder:a("exampleName"),autoFocus:!0,onChange:e=>h({...c,name:e.target.value})}),n.jsx("label",{htmlFor:"cmd-command",children:a("command")}),n.jsx("input",{id:"cmd-command",className:"cmd-input",value:c.command,placeholder:a("exampleCommand"),onChange:e=>h({...c,command:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("label",{htmlFor:"cmd-cwd",children:[a("directory")," ",n.jsx("span",{className:"cmd-hint",children:a("cwdHint")})]}),n.jsx("input",{id:"cmd-cwd",className:"cmd-input",value:c.cwd,placeholder:"${pwd}",onChange:e=>h({...c,cwd:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("div",{className:"cmd-form-actions",children:[n.jsx("button",{type:"button",className:"btn",onClick:D,children:a("cancel")}),n.jsx("button",{type:"button",className:"btn primary",disabled:!c.name.trim()||!c.command.trim(),onClick:S,children:a("save")})]})]}):n.jsxs(n.Fragment,{children:[s.commands.length===0&&n.jsx("div",{className:"panel-empty",children:a("noCommands")}),s.commands.map((e,i)=>n.jsxs("div",{className:"cmd-item",children:[n.jsx("button",{type:"button",className:"cmd-run",title:a("clickToRun"),onClick:()=>o(e),children:n.jsx(q,{})}),n.jsxs("button",{type:"button",className:"cmd-main",title:a("clickToRun"),onClick:()=>o(e),children:[n.jsx("span",{className:"cmd-name",children:e.name}),n.jsx("span",{className:"cmd-command",children:e.command}),e.cwd&&n.jsx("span",{className:"cmd-cwd",children:e.cwd})]}),n.jsx("button",{type:"button",className:"cmd-act",title:a("edit"),onClick:()=>T(i),children:n.jsx(L,{})}),n.jsx("button",{type:"button",className:`cmd-act del ${g===i?"confirm":""}`,title:a("delete"),onClick:()=>I(i),children:g===i?a("confirmQ"):n.jsx(B,{})})]},i))]})}),n.jsxs("div",{className:"term-tabs-block",children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("terminal")}),n.jsx("button",{type:"button",className:"panel-new",title:a("newTerminal"),onClick:y,children:n.jsx(K,{})})]}),n.jsxs("div",{className:"panel-body",children:[s.terminals.length===0&&n.jsx("div",{className:"panel-empty",children:a("noTerminal")}),s.terminals.map(e=>n.jsxs("div",{className:`term-tab ${e.id===f?"active":""}`,children:[n.jsxs("button",{type:"button",className:"term-tab-main",title:`${e.cwd}${e.command?`
|
|
2
2
|
> ${e.command.command}`:""}`,onClick:()=>{m(e.id),w(!1)},children:[n.jsx("span",{className:`term-tab-dot ${e.running?"run":"exit"}`}),n.jsxs("span",{className:"term-tab-title",children:[e.title,!e.running&&n.jsx("span",{className:"term-tab-exit",children:a("exited",{code:e.exitCode===null?"":` ${e.exitCode}`})})]})]}),n.jsx("button",{type:"button",className:"term-tab-close",title:a("closeTerminal"),onClick:()=>E(e.id),children:n.jsx(J,{})})]},e.id))]})]})]}),n.jsxs("div",{className:"term-main",children:[v&&n.jsx("div",{className:"drawer-backdrop",onClick:()=>w(!1)}),n.jsx("button",{type:"button",className:"term-side-toggle",title:a("commands"),onClick:()=>w(e=>!e),children:n.jsx(X,{})}),s.terminals.length===0?n.jsxs("div",{className:"term-empty",children:[n.jsx(G,{className:"term-empty-icon"}),n.jsx("div",{className:"term-empty-title",children:a("builtinTerminal")}),n.jsx("div",{className:"term-empty-sub",children:a("termEmptySub")})]}):s.terminals.map(e=>n.jsx(W,{conversationId:e.conversationId,terminalId:e.id,command:e.command,cwd:e.cwd,active:e.id===f,send:r,register:x.register},`${e.conversationId}:${e.id}`))]})]})}export{se as TerminalPanel};
|