pi-web-ui 0.59.0 → 0.60.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 -21
- package/README.md +497 -468
- package/README.zh-CN.md +422 -391
- package/bin/pi-web-ui.mjs +1881 -1848
- package/deploy/com.xingshuyin.pi-web-ui.plist +48 -48
- package/deploy/nginx-subpath.conf +88 -88
- package/deploy/pi-web-ui-task.xml +54 -54
- package/deploy/pi-web-ui.service +31 -31
- package/dist/server/agent-service.js +63 -33
- package/dist/server/attachments.js +3 -3
- package/dist/server/dsh/runtime/goal-rpc.mjs +662 -662
- package/dist/server/index.js +28 -11
- package/dist/server/terminals.js +135 -138
- package/extensions/webui.ts +192 -192
- package/package.json +1 -1
- package/web/dist/assets/TerminalPanel-CSzqnWV2.js +2 -0
- package/web/dist/assets/index-D9iD9G_p.css +10 -0
- package/web/dist/assets/{index-MeifpZzi.js → index-TZDcJFdi.js} +12 -12
- package/web/dist/favicon.svg +8 -8
- package/web/dist/index.html +15 -15
- package/web/public/favicon.svg +8 -8
- package/web/dist/assets/TerminalPanel-_VfntAyG.js +0 -2
- package/web/dist/assets/index-DRsP4BO2.css +0 -10
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
-
<!--
|
|
4
|
-
pi-web-ui launchd agent — macOS auto-start at login.
|
|
5
|
-
|
|
6
|
-
Install:
|
|
7
|
-
npm i -g pi-web-ui
|
|
8
|
-
cp deploy/com.xingshuyin.pi-web-ui.plist ~/Library/LaunchAgents/
|
|
9
|
-
# edit ProgramArguments / WorkingDirectory / PI_WEB_CWD for your setup
|
|
10
|
-
launchctl load ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist
|
|
11
|
-
# (or: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist)
|
|
12
|
-
|
|
13
|
-
Find the pi-web-ui binary path with: which pi-web-ui
|
|
14
|
-
-->
|
|
15
|
-
<plist version="1.0">
|
|
16
|
-
<dict>
|
|
17
|
-
<key>Label</key>
|
|
18
|
-
<string>com.xingshuyin.pi-web-ui</string>
|
|
19
|
-
|
|
20
|
-
<key>ProgramArguments</key>
|
|
21
|
-
<array>
|
|
22
|
-
<string>/usr/local/bin/pi-web-ui</string>
|
|
23
|
-
</array>
|
|
24
|
-
|
|
25
|
-
<key>RunAtLoad</key>
|
|
26
|
-
<true/>
|
|
27
|
-
|
|
28
|
-
<!-- Restart if it crashes -->
|
|
29
|
-
<key>KeepAlive</key>
|
|
30
|
-
<true/>
|
|
31
|
-
|
|
32
|
-
<key>WorkingDirectory</key>
|
|
33
|
-
<string>/Users/YOUR_USER</string>
|
|
34
|
-
|
|
35
|
-
<key>EnvironmentVariables</key>
|
|
36
|
-
<dict>
|
|
37
|
-
<key>PI_WEB_PORT</key>
|
|
38
|
-
<string>8787</string>
|
|
39
|
-
<key>PI_WEB_CWD</key>
|
|
40
|
-
<string>/Users/YOUR_USER</string>
|
|
41
|
-
</dict>
|
|
42
|
-
|
|
43
|
-
<key>StandardOutPath</key>
|
|
44
|
-
<string>/tmp/pi-web-ui.log</string>
|
|
45
|
-
<key>StandardErrorPath</key>
|
|
46
|
-
<string>/tmp/pi-web-ui.err</string>
|
|
47
|
-
</dict>
|
|
48
|
-
</plist>
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<!--
|
|
4
|
+
pi-web-ui launchd agent — macOS auto-start at login.
|
|
5
|
+
|
|
6
|
+
Install:
|
|
7
|
+
npm i -g pi-web-ui
|
|
8
|
+
cp deploy/com.xingshuyin.pi-web-ui.plist ~/Library/LaunchAgents/
|
|
9
|
+
# edit ProgramArguments / WorkingDirectory / PI_WEB_CWD for your setup
|
|
10
|
+
launchctl load ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist
|
|
11
|
+
# (or: launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.xingshuyin.pi-web-ui.plist)
|
|
12
|
+
|
|
13
|
+
Find the pi-web-ui binary path with: which pi-web-ui
|
|
14
|
+
-->
|
|
15
|
+
<plist version="1.0">
|
|
16
|
+
<dict>
|
|
17
|
+
<key>Label</key>
|
|
18
|
+
<string>com.xingshuyin.pi-web-ui</string>
|
|
19
|
+
|
|
20
|
+
<key>ProgramArguments</key>
|
|
21
|
+
<array>
|
|
22
|
+
<string>/usr/local/bin/pi-web-ui</string>
|
|
23
|
+
</array>
|
|
24
|
+
|
|
25
|
+
<key>RunAtLoad</key>
|
|
26
|
+
<true/>
|
|
27
|
+
|
|
28
|
+
<!-- Restart if it crashes -->
|
|
29
|
+
<key>KeepAlive</key>
|
|
30
|
+
<true/>
|
|
31
|
+
|
|
32
|
+
<key>WorkingDirectory</key>
|
|
33
|
+
<string>/Users/YOUR_USER</string>
|
|
34
|
+
|
|
35
|
+
<key>EnvironmentVariables</key>
|
|
36
|
+
<dict>
|
|
37
|
+
<key>PI_WEB_PORT</key>
|
|
38
|
+
<string>8787</string>
|
|
39
|
+
<key>PI_WEB_CWD</key>
|
|
40
|
+
<string>/Users/YOUR_USER</string>
|
|
41
|
+
</dict>
|
|
42
|
+
|
|
43
|
+
<key>StandardOutPath</key>
|
|
44
|
+
<string>/tmp/pi-web-ui.log</string>
|
|
45
|
+
<key>StandardErrorPath</key>
|
|
46
|
+
<string>/tmp/pi-web-ui.err</string>
|
|
47
|
+
</dict>
|
|
48
|
+
</plist>
|
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
# pi-web-ui behind nginx at a sub-path: http://<host>:83/pi/
|
|
2
|
-
# Backend app: http://127.0.0.1:8787 (default PI_WEB_PORT env)
|
|
3
|
-
#
|
|
4
|
-
# IMPORTANT: pi-web-ui 0.23+ checks the WebSocket Origin against the request
|
|
5
|
-
# Host (hostname AND port). Every proxied location MUST forward the original
|
|
6
|
-
# Host with $http_host (keeps the port). Using $host (drops the port) or
|
|
7
|
-
# leaving Host unset (defaults to 127.0.0.1:8787) makes the upgrade fail with
|
|
8
|
-
# 403 — the page loads but chat/terminal keep reconnecting.
|
|
9
|
-
#
|
|
10
|
-
# Topology (two listeners on one port: frp + LAN coexist):
|
|
11
|
-
# frp (public) -> <PUBLIC_IP>:<PUBLIC_PORT> -> 127.0.0.1:83 (PROXY protocol v2)
|
|
12
|
-
# LAN users -> http://<LAN_IP>:83/pi/ (plain HTTP listener)
|
|
13
|
-
#
|
|
14
|
-
# frpc sends PROXY v2 to nginx (transport.proxyProtocolVersion = "v2"):
|
|
15
|
-
# * 127.0.0.1:83 proxy_protocol — only the local frp client connects
|
|
16
|
-
# here (it speaks PROXY v2; nginx then sees the real visitor IP).
|
|
17
|
-
# * <LAN_IP>:83 plain HTTP — LAN browsers, no PROXY header needed.
|
|
18
|
-
#
|
|
19
|
-
# Simpler alternative (no real client IPs): drop proxy_protocol entirely and
|
|
20
|
-
# use a single `listen 83;` — then frpc must NOT set proxyProtocolVersion.
|
|
21
|
-
#
|
|
22
|
-
# The frontend uses absolute paths (/ws WebSocket, /assets/*, /favicon.svg,
|
|
23
|
-
# /api/file…) so those get their own proxied locations next to /pi/.
|
|
24
|
-
|
|
25
|
-
# Reuse for Upgrade/Connection headers (WebSocket).
|
|
26
|
-
map $http_upgrade $connection_upgrade {
|
|
27
|
-
default upgrade;
|
|
28
|
-
'' close;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
server {
|
|
32
|
-
listen 127.0.0.1:83 proxy_protocol;
|
|
33
|
-
listen <LAN_IP>:83;
|
|
34
|
-
|
|
35
|
-
server_name _;
|
|
36
|
-
|
|
37
|
-
# Trust PROXY-protocol headers only from the local frp client; plain
|
|
38
|
-
# (LAN) connections keep their real $remote_addr untouched.
|
|
39
|
-
set_real_ip_from 127.0.0.1;
|
|
40
|
-
real_ip_header proxy_protocol;
|
|
41
|
-
|
|
42
|
-
# ---- main entry: strip /pi/ and forward to the app root ----
|
|
43
|
-
location /pi/ {
|
|
44
|
-
proxy_pass http://127.0.0.1:8787/;
|
|
45
|
-
proxy_http_version 1.1;
|
|
46
|
-
# $http_host keeps the port — origin check compares hostname AND port.
|
|
47
|
-
proxy_set_header Host $http_host;
|
|
48
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
49
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
50
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
51
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
52
|
-
proxy_set_header Connection $connection_upgrade;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
# ---- WebSocket (the frontend connects to ws://<host>/ws) ----
|
|
56
|
-
location /ws {
|
|
57
|
-
proxy_pass http://127.0.0.1:8787;
|
|
58
|
-
proxy_http_version 1.1;
|
|
59
|
-
proxy_set_header Host $http_host;
|
|
60
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
61
|
-
proxy_set_header Connection $connection_upgrade;
|
|
62
|
-
proxy_read_timeout 3600s;
|
|
63
|
-
proxy_send_timeout 3600s;
|
|
64
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
65
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
# ---- absolute asset paths baked into index.html ----
|
|
69
|
-
location /assets/ {
|
|
70
|
-
proxy_pass http://127.0.0.1:8787;
|
|
71
|
-
}
|
|
72
|
-
location = /favicon.svg {
|
|
73
|
-
proxy_pass http://127.0.0.1:8787;
|
|
74
|
-
}
|
|
75
|
-
location = /favicon-streaming.svg {
|
|
76
|
-
proxy_pass http://127.0.0.1:8787;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
# ---- media preview / download / health API ----
|
|
80
|
-
location /api/ {
|
|
81
|
-
proxy_pass http://127.0.0.1:8787;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# bare root → app entry
|
|
85
|
-
location = / {
|
|
86
|
-
return 302 /pi/;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
# pi-web-ui behind nginx at a sub-path: http://<host>:83/pi/
|
|
2
|
+
# Backend app: http://127.0.0.1:8787 (default PI_WEB_PORT env)
|
|
3
|
+
#
|
|
4
|
+
# IMPORTANT: pi-web-ui 0.23+ checks the WebSocket Origin against the request
|
|
5
|
+
# Host (hostname AND port). Every proxied location MUST forward the original
|
|
6
|
+
# Host with $http_host (keeps the port). Using $host (drops the port) or
|
|
7
|
+
# leaving Host unset (defaults to 127.0.0.1:8787) makes the upgrade fail with
|
|
8
|
+
# 403 — the page loads but chat/terminal keep reconnecting.
|
|
9
|
+
#
|
|
10
|
+
# Topology (two listeners on one port: frp + LAN coexist):
|
|
11
|
+
# frp (public) -> <PUBLIC_IP>:<PUBLIC_PORT> -> 127.0.0.1:83 (PROXY protocol v2)
|
|
12
|
+
# LAN users -> http://<LAN_IP>:83/pi/ (plain HTTP listener)
|
|
13
|
+
#
|
|
14
|
+
# frpc sends PROXY v2 to nginx (transport.proxyProtocolVersion = "v2"):
|
|
15
|
+
# * 127.0.0.1:83 proxy_protocol — only the local frp client connects
|
|
16
|
+
# here (it speaks PROXY v2; nginx then sees the real visitor IP).
|
|
17
|
+
# * <LAN_IP>:83 plain HTTP — LAN browsers, no PROXY header needed.
|
|
18
|
+
#
|
|
19
|
+
# Simpler alternative (no real client IPs): drop proxy_protocol entirely and
|
|
20
|
+
# use a single `listen 83;` — then frpc must NOT set proxyProtocolVersion.
|
|
21
|
+
#
|
|
22
|
+
# The frontend uses absolute paths (/ws WebSocket, /assets/*, /favicon.svg,
|
|
23
|
+
# /api/file…) so those get their own proxied locations next to /pi/.
|
|
24
|
+
|
|
25
|
+
# Reuse for Upgrade/Connection headers (WebSocket).
|
|
26
|
+
map $http_upgrade $connection_upgrade {
|
|
27
|
+
default upgrade;
|
|
28
|
+
'' close;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
server {
|
|
32
|
+
listen 127.0.0.1:83 proxy_protocol;
|
|
33
|
+
listen <LAN_IP>:83;
|
|
34
|
+
|
|
35
|
+
server_name _;
|
|
36
|
+
|
|
37
|
+
# Trust PROXY-protocol headers only from the local frp client; plain
|
|
38
|
+
# (LAN) connections keep their real $remote_addr untouched.
|
|
39
|
+
set_real_ip_from 127.0.0.1;
|
|
40
|
+
real_ip_header proxy_protocol;
|
|
41
|
+
|
|
42
|
+
# ---- main entry: strip /pi/ and forward to the app root ----
|
|
43
|
+
location /pi/ {
|
|
44
|
+
proxy_pass http://127.0.0.1:8787/;
|
|
45
|
+
proxy_http_version 1.1;
|
|
46
|
+
# $http_host keeps the port — origin check compares hostname AND port.
|
|
47
|
+
proxy_set_header Host $http_host;
|
|
48
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
49
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
50
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
51
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
52
|
+
proxy_set_header Connection $connection_upgrade;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ---- WebSocket (the frontend connects to ws://<host>/ws) ----
|
|
56
|
+
location /ws {
|
|
57
|
+
proxy_pass http://127.0.0.1:8787;
|
|
58
|
+
proxy_http_version 1.1;
|
|
59
|
+
proxy_set_header Host $http_host;
|
|
60
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
61
|
+
proxy_set_header Connection $connection_upgrade;
|
|
62
|
+
proxy_read_timeout 3600s;
|
|
63
|
+
proxy_send_timeout 3600s;
|
|
64
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
65
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# ---- absolute asset paths baked into index.html ----
|
|
69
|
+
location /assets/ {
|
|
70
|
+
proxy_pass http://127.0.0.1:8787;
|
|
71
|
+
}
|
|
72
|
+
location = /favicon.svg {
|
|
73
|
+
proxy_pass http://127.0.0.1:8787;
|
|
74
|
+
}
|
|
75
|
+
location = /favicon-streaming.svg {
|
|
76
|
+
proxy_pass http://127.0.0.1:8787;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ---- media preview / download / health API ----
|
|
80
|
+
location /api/ {
|
|
81
|
+
proxy_pass http://127.0.0.1:8787;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# bare root → app entry
|
|
85
|
+
location = / {
|
|
86
|
+
return 302 /pi/;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-16"?>
|
|
2
|
-
<!--
|
|
3
|
-
pi-web-ui Task Scheduler task — Windows auto-start at logon.
|
|
4
|
-
|
|
5
|
-
pi-web-ui server install 默认使用「登录 Run 键 + wscript 隐藏启动」(HKCU,无需管理员、
|
|
6
|
-
无黑窗),并自动迁移旧版计划任务安装。本 XML 仅保留给需要手动注册计划任务的场景
|
|
7
|
-
(例如管理员环境),不是默认路径。
|
|
8
|
-
-->
|
|
9
|
-
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
10
|
-
<RegistrationInfo>
|
|
11
|
-
<Description>pi-web-ui — web chat for the pi coding agent (auto-start at logon)</Description>
|
|
12
|
-
</RegistrationInfo>
|
|
13
|
-
<Triggers>
|
|
14
|
-
<LogonTrigger>
|
|
15
|
-
<Enabled>true</Enabled>
|
|
16
|
-
</LogonTrigger>
|
|
17
|
-
</Triggers>
|
|
18
|
-
<Principals>
|
|
19
|
-
<Principal id="Author">
|
|
20
|
-
<LogonType>InteractiveToken</LogonType>
|
|
21
|
-
<RunLevel>LeastPrivilege</RunLevel>
|
|
22
|
-
</Principal>
|
|
23
|
-
</Principals>
|
|
24
|
-
<Settings>
|
|
25
|
-
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
26
|
-
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
27
|
-
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
28
|
-
<AllowHardTerminate>true</AllowHardTerminate>
|
|
29
|
-
<StartWhenAvailable>false</StartWhenAvailable>
|
|
30
|
-
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
31
|
-
<IdleSettings>
|
|
32
|
-
<StopOnIdleEnd>false</StopOnIdleEnd>
|
|
33
|
-
<RestartOnIdle>false</RestartOnIdle>
|
|
34
|
-
</IdleSettings>
|
|
35
|
-
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
36
|
-
<Enabled>true</Enabled>
|
|
37
|
-
<Hidden>false</Hidden>
|
|
38
|
-
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
39
|
-
<WakeToRun>false</WakeToRun>
|
|
40
|
-
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
41
|
-
<Priority>7</Priority>
|
|
42
|
-
<RestartOnFailure>
|
|
43
|
-
<Interval>PT1M</Interval>
|
|
44
|
-
<Count>3</Count>
|
|
45
|
-
</RestartOnFailure>
|
|
46
|
-
</Settings>
|
|
47
|
-
<Actions Context="Author">
|
|
48
|
-
<Exec>
|
|
49
|
-
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
|
|
50
|
-
<Arguments>-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "%APPDATA%\pi-web-ui\pi-web-ui.ps1"</Arguments>
|
|
51
|
-
<WorkingDirectory>%USERPROFILE%</WorkingDirectory>
|
|
52
|
-
</Exec>
|
|
53
|
-
</Actions>
|
|
54
|
-
</Task>
|
|
1
|
+
<?xml version="1.0" encoding="UTF-16"?>
|
|
2
|
+
<!--
|
|
3
|
+
pi-web-ui Task Scheduler task — Windows auto-start at logon.
|
|
4
|
+
|
|
5
|
+
pi-web-ui server install 默认使用「登录 Run 键 + wscript 隐藏启动」(HKCU,无需管理员、
|
|
6
|
+
无黑窗),并自动迁移旧版计划任务安装。本 XML 仅保留给需要手动注册计划任务的场景
|
|
7
|
+
(例如管理员环境),不是默认路径。
|
|
8
|
+
-->
|
|
9
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
10
|
+
<RegistrationInfo>
|
|
11
|
+
<Description>pi-web-ui — web chat for the pi coding agent (auto-start at logon)</Description>
|
|
12
|
+
</RegistrationInfo>
|
|
13
|
+
<Triggers>
|
|
14
|
+
<LogonTrigger>
|
|
15
|
+
<Enabled>true</Enabled>
|
|
16
|
+
</LogonTrigger>
|
|
17
|
+
</Triggers>
|
|
18
|
+
<Principals>
|
|
19
|
+
<Principal id="Author">
|
|
20
|
+
<LogonType>InteractiveToken</LogonType>
|
|
21
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
22
|
+
</Principal>
|
|
23
|
+
</Principals>
|
|
24
|
+
<Settings>
|
|
25
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
26
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
27
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
28
|
+
<AllowHardTerminate>true</AllowHardTerminate>
|
|
29
|
+
<StartWhenAvailable>false</StartWhenAvailable>
|
|
30
|
+
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
31
|
+
<IdleSettings>
|
|
32
|
+
<StopOnIdleEnd>false</StopOnIdleEnd>
|
|
33
|
+
<RestartOnIdle>false</RestartOnIdle>
|
|
34
|
+
</IdleSettings>
|
|
35
|
+
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
36
|
+
<Enabled>true</Enabled>
|
|
37
|
+
<Hidden>false</Hidden>
|
|
38
|
+
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
39
|
+
<WakeToRun>false</WakeToRun>
|
|
40
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
41
|
+
<Priority>7</Priority>
|
|
42
|
+
<RestartOnFailure>
|
|
43
|
+
<Interval>PT1M</Interval>
|
|
44
|
+
<Count>3</Count>
|
|
45
|
+
</RestartOnFailure>
|
|
46
|
+
</Settings>
|
|
47
|
+
<Actions Context="Author">
|
|
48
|
+
<Exec>
|
|
49
|
+
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
|
|
50
|
+
<Arguments>-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "%APPDATA%\pi-web-ui\pi-web-ui.ps1"</Arguments>
|
|
51
|
+
<WorkingDirectory>%USERPROFILE%</WorkingDirectory>
|
|
52
|
+
</Exec>
|
|
53
|
+
</Actions>
|
|
54
|
+
</Task>
|
package/deploy/pi-web-ui.service
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
# pi-web-ui systemd unit — Linux auto-start on boot.
|
|
2
|
-
#
|
|
3
|
-
# Install (adjust User/WorkingDirectory/Environment to taste):
|
|
4
|
-
# sudo npm i -g pi-web-ui
|
|
5
|
-
# sudo cp deploy/pi-web-ui.service /etc/systemd/system/
|
|
6
|
-
# sudo systemctl daemon-reload
|
|
7
|
-
# sudo systemctl enable --now pi-web-ui
|
|
8
|
-
# sudo systemctl status pi-web-ui
|
|
9
|
-
#
|
|
10
|
-
# Logs: journalctl -u pi-web-ui -f
|
|
11
|
-
|
|
12
|
-
[Unit]
|
|
13
|
-
Description=pi-web-ui — web chat for the pi coding agent
|
|
14
|
-
After=network.target
|
|
15
|
-
|
|
16
|
-
[Service]
|
|
17
|
-
Type=simple
|
|
18
|
-
# Run as an unprivileged user (change to your user, e.g. yourname)
|
|
19
|
-
User=YOUR_USER
|
|
20
|
-
# The workspace the agent operates in (read/edit/bash/write)
|
|
21
|
-
WorkingDirectory=/home/YOUR_USER
|
|
22
|
-
Environment=PI_WEB_PORT=8787
|
|
23
|
-
# Point at your pi config dir if it's not the default ~/.pi/agent
|
|
24
|
-
#Environment=PI_CODING_AGENT_DIR=/home/YOUR_USER/.pi/agent
|
|
25
|
-
ExecStart=/usr/bin/pi-web-ui
|
|
26
|
-
Restart=on-failure
|
|
27
|
-
RestartSec=5
|
|
28
|
-
# npm global bin may live elsewhere (nvm, etc.) — find with: which pi-web-ui
|
|
29
|
-
|
|
30
|
-
[Install]
|
|
31
|
-
WantedBy=multi-user.target
|
|
1
|
+
# pi-web-ui systemd unit — Linux auto-start on boot.
|
|
2
|
+
#
|
|
3
|
+
# Install (adjust User/WorkingDirectory/Environment to taste):
|
|
4
|
+
# sudo npm i -g pi-web-ui
|
|
5
|
+
# sudo cp deploy/pi-web-ui.service /etc/systemd/system/
|
|
6
|
+
# sudo systemctl daemon-reload
|
|
7
|
+
# sudo systemctl enable --now pi-web-ui
|
|
8
|
+
# sudo systemctl status pi-web-ui
|
|
9
|
+
#
|
|
10
|
+
# Logs: journalctl -u pi-web-ui -f
|
|
11
|
+
|
|
12
|
+
[Unit]
|
|
13
|
+
Description=pi-web-ui — web chat for the pi coding agent
|
|
14
|
+
After=network.target
|
|
15
|
+
|
|
16
|
+
[Service]
|
|
17
|
+
Type=simple
|
|
18
|
+
# Run as an unprivileged user (change to your user, e.g. yourname)
|
|
19
|
+
User=YOUR_USER
|
|
20
|
+
# The workspace the agent operates in (read/edit/bash/write)
|
|
21
|
+
WorkingDirectory=/home/YOUR_USER
|
|
22
|
+
Environment=PI_WEB_PORT=8787
|
|
23
|
+
# Point at your pi config dir if it's not the default ~/.pi/agent
|
|
24
|
+
#Environment=PI_CODING_AGENT_DIR=/home/YOUR_USER/.pi/agent
|
|
25
|
+
ExecStart=/usr/bin/pi-web-ui
|
|
26
|
+
Restart=on-failure
|
|
27
|
+
RestartSec=5
|
|
28
|
+
# npm global bin may live elsewhere (nvm, etc.) — find with: which pi-web-ui
|
|
29
|
+
|
|
30
|
+
[Install]
|
|
31
|
+
WantedBy=multi-user.target
|
|
@@ -27,7 +27,7 @@ import { ModelAdminService } from "./model-admin.js";
|
|
|
27
27
|
import { FilesService, workspacePath } from "./files-service.js";
|
|
28
28
|
import { isExtensionDisabled, ClientStateStore, } from "./client-state.js";
|
|
29
29
|
import { saveUpload } from "./uploads.js";
|
|
30
|
-
import { makePersistentTerminalTools, makeTerminalBashTool, stripAnsi, TERMINAL_TOOLS_GUIDANCE, TERMINAL_TOOL_NAMES, } from "./terminals.js";
|
|
30
|
+
import { applyHeadTail, makePersistentTerminalTools, makeTerminalBashTool, stripAnsi, TERMINAL_TOOLS_GUIDANCE, TERMINAL_TOOL_NAMES, } from "./terminals.js";
|
|
31
31
|
import { WebUIContext } from "./webui-context.js";
|
|
32
32
|
import { buildAttachmentMessages, parseModelSpec, } from "./attachments.js";
|
|
33
33
|
import { serializeMessage, serializeStreamingMessage, } from "./serialize.js";
|
|
@@ -82,14 +82,14 @@ export class QuiesceRejectedError extends Error {
|
|
|
82
82
|
* programs wait for input that never comes. Legacy Chinese files are often
|
|
83
83
|
* GBK/GB2312 — read them with the right encoding, never paste mojibake into
|
|
84
84
|
* reasoning/answers. */
|
|
85
|
-
const WINDOWS_PERSONA = `You are a coding agent running on Windows. The bash tool runs Git Bash (bash.exe), not PowerShell. Follow these rules to avoid hanging the session:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- ALWAYS pass a timeout parameter to the bash tool (in seconds). There is NO default timeout — a command that never finishes (servers, watchers, infinite loops, slow downloads/installs) will hang the entire conversation indefinitely. Pick a generous timeout for long-running work, but never omit it.
|
|
90
|
-
- NEVER run interactive or foreground long-running commands through the bash tool (vi, less, top, python -, node -, npm run dev, sleep 10000). For servers/daemons use background execution with output redirected to a log file, then poll the log; stop them when done.
|
|
91
|
-
- In the interactive terminal (TTY) — which is Git Bash too, not PowerShell — NEVER use heredocs (<<'EOF' ... EOF) or here-strings, and NEVER start interactive programs (vi, less, python -, node -, npm init): they wait for keyboard input that never arrives and hang the terminal forever. Prefer writing a temp script file (e.g. .pi-tmp.sh) and running it non-interactively. ALWAYS pass a timeout to long-running commands (e.g. \`timeout 120 npm run dev\`).
|
|
92
|
-
|
|
85
|
+
const WINDOWS_PERSONA = `You are a coding agent running on Windows. The bash tool runs Git Bash (bash.exe), not PowerShell. Follow these rules to avoid hanging the session:
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
- ALWAYS pass a timeout parameter to the bash tool (in seconds). There is NO default timeout — a command that never finishes (servers, watchers, infinite loops, slow downloads/installs) will hang the entire conversation indefinitely. Pick a generous timeout for long-running work, but never omit it.
|
|
90
|
+
- NEVER run interactive or foreground long-running commands through the bash tool (vi, less, top, python -, node -, npm run dev, sleep 10000). For servers/daemons use background execution with output redirected to a log file, then poll the log; stop them when done.
|
|
91
|
+
- In the interactive terminal (TTY) — which is Git Bash too, not PowerShell — NEVER use heredocs (<<'EOF' ... EOF) or here-strings, and NEVER start interactive programs (vi, less, python -, node -, npm init): they wait for keyboard input that never arrives and hang the terminal forever. Prefer writing a temp script file (e.g. .pi-tmp.sh) and running it non-interactively. ALWAYS pass a timeout to long-running commands (e.g. \`timeout 120 npm run dev\`).
|
|
92
|
+
|
|
93
93
|
Many legacy Chinese text files (.html/.txt/.md/.log, exported documents) are GBK/GB2312 encoded: the read tool decodes UTF-8 only and will show mojibake (乱码) for them. If a file's content looks garbled, read it through the terminal instead: in Git Bash use \`cat file | iconv -f GBK -t UTF-8\` (or \`iconv -f GBK -t UTF-8 file\`); in cmd use \`chcp 65001 && type file\`; in PowerShell use \`Get-Content -Encoding Default file\`. Never paste mojibake into your reasoning or answer — describe the decoded content instead.`;
|
|
94
94
|
/** bash 工具输出限制/过滤管道引导:模型习惯套 `| tail/-n`、`| head`、`| grep`、`| less`
|
|
95
95
|
* 等限输出。这些管道在持久终端里会①缓冲(可见终端全程哑火、看不到实时进度)②把退出码
|
|
@@ -97,13 +97,14 @@ Many legacy Chinese text files (.html/.txt/.md/.log, exported documents) are GBK
|
|
|
97
97
|
* 让模型改用 bash 的 `tail` 参数限输出;长驻/交互任务改走持久终端工具。 */
|
|
98
98
|
const PIPELESS_BASH_GUIDANCE = `Bash tool output-limiting/filtering: do NOT chain shell pipes to trim or filter output. Avoid \`| tail\`, \`| head\`, \`| grep\`, \`| less\`, \`| more\`, \`| cat\`, \`| sort\`, \`| awk\`, \`| sed\`. They buffer output (so the visible terminal shows nothing live), turn the real exit code into the last pipe command's (tail always 0, grep 1 when no match — hiding the actual failure), and can hang a long-running or failing command until timeout. Instead:\n- To limit returned output use the bash \`tail\` parameter (e.g. \`bash(command=..., tail=20)\`) — the underlying command still streams live to the visible terminal.\n- For a long-running server / watcher / interactive program, use the persistent terminal tools (terminal_create then terminal_read / terminal_input / terminal_key / terminal_wait) instead of piping through bash.\nThe bash tool auto-detects a trailing \`| tail\`/\`| grep\` etc. and runs the underlying command directly so it never hides a failure — but you should still prefer the \`tail\` parameter.`;
|
|
99
99
|
/**
|
|
100
|
-
* Killable bash tool: wraps the SDK bash tool
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* run and the conversation continue
|
|
104
|
-
*
|
|
100
|
+
* Killable bash tool: wraps the SDK bash tool (native process spawn, NO terminal).
|
|
101
|
+
* Used when the「默认 bash 覆盖」setting is OFF. Registers its own AbortController
|
|
102
|
+
* into a client-level set (kills) so abortBash() kills only these commands while the
|
|
103
|
+
* agent run and the conversation continue. Exposes persist (ignored — native has no
|
|
104
|
+
* terminal) plus head/tail (post-processed on the returned output) so the parameter
|
|
105
|
+
* schema stays consistent with the terminal-backed tool.
|
|
105
106
|
*/
|
|
106
|
-
function makeKillableBashTool(cwd, kills) {
|
|
107
|
+
export function makeKillableBashTool(cwd, kills) {
|
|
107
108
|
const base = createLocalBashOperations();
|
|
108
109
|
const tool = createBashTool(cwd, {
|
|
109
110
|
operations: {
|
|
@@ -127,20 +128,50 @@ function makeKillableBashTool(cwd, kills) {
|
|
|
127
128
|
return {
|
|
128
129
|
name: tool.name,
|
|
129
130
|
label: tool.label,
|
|
130
|
-
description: tool.
|
|
131
|
-
parameters:
|
|
131
|
+
description: "Run a shell command natively (process spawn, no terminal) and return its full output plus exit code — the SDK's plain bash tool. persist is ignored here (no terminal); use head/tail to trim the returned output.",
|
|
132
|
+
parameters: Type.Object({
|
|
133
|
+
command: Type.String({ description: "The shell command to run" }),
|
|
134
|
+
timeout: Type.Optional(Type.Number({ description: "Optional timeout in seconds" })),
|
|
135
|
+
persist: Type.Optional(Type.Boolean({
|
|
136
|
+
description: "Ignored in native mode (no terminal). Only meaningful when the terminal-backed bash is active.",
|
|
137
|
+
})),
|
|
138
|
+
head: Type.Optional(Type.Integer({
|
|
139
|
+
minimum: 1,
|
|
140
|
+
maximum: 5000,
|
|
141
|
+
description: "Only return the FIRST N lines of output (like `| head -N`).",
|
|
142
|
+
})),
|
|
143
|
+
tail: Type.Optional(Type.Integer({
|
|
144
|
+
minimum: 1,
|
|
145
|
+
maximum: 5000,
|
|
146
|
+
description: "Only return the LAST N lines of output (like `| tail -N`).",
|
|
147
|
+
})),
|
|
148
|
+
}),
|
|
132
149
|
prepareArguments: tool.prepareArguments,
|
|
133
150
|
executionMode: tool.executionMode,
|
|
134
|
-
execute: (toolCallId, params, signal, onUpdate) =>
|
|
151
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
152
|
+
const result = (await tool.execute(toolCallId, params, signal, onUpdate));
|
|
153
|
+
// head/tail 后处理(native 无终端,直接截返回行即可)。
|
|
154
|
+
const p = params;
|
|
155
|
+
if ((p?.head || p?.tail) && result?.content?.[0]?.text != null) {
|
|
156
|
+
result.content[0].text = applyHeadTail(result.content[0].text, p.head, p.tail);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
},
|
|
135
160
|
};
|
|
136
161
|
}
|
|
137
162
|
/**
|
|
138
|
-
* 动态分流 bash
|
|
139
|
-
*
|
|
163
|
+
* 动态分流 bash:按「默认 bash 覆盖」设置(terminalBash)在调用时决定走哪套——
|
|
164
|
+
* 关 = 原生 SDK bash(纯进程、不开终端);开 = 终端接管 bash(persist 决定一次性/
|
|
165
|
+
* 持久)。开关因此即时生效(customTools 固定于 runtime 创建,不能在创建时二选一)。
|
|
140
166
|
*/
|
|
141
|
-
function makeAdaptiveBashTool(killable, terminalBacked, useTerminal) {
|
|
167
|
+
export function makeAdaptiveBashTool(killable, terminalBacked, useTerminal) {
|
|
142
168
|
return {
|
|
143
169
|
...killable,
|
|
170
|
+
description: "Run a shell command and return its full output plus exit code. Behavior depends on the「default bash override」setting (terminalBash):\n" +
|
|
171
|
+
"Setting OFF → runs natively (process spawn, no terminal) — the SDK's plain bash tool. persist has no effect.\n" +
|
|
172
|
+
"Setting ON → runs in a visible terminal. persist=true keeps that terminal alive ('ai-bash': shell state such as cd/venv/ssh retained across calls, silent commands move to the background and notify when done); persist=false (default in terminal mode) creates a one-shot terminal that exits when the command finishes while its output stays for review.\n" +
|
|
173
|
+
"Run the bare command — do NOT pipe through head/tail/more/less (use the head/tail parameters to trim the returned output instead; piping also hides live progress in the visible terminal). For interactive commands (REPLs, prompts, installers asking y/n) set persist=true (terminal mode) and drive them with terminal_input / terminal_key.",
|
|
174
|
+
promptSnippet: "run shell commands",
|
|
144
175
|
execute: (id, params, signal, onUpdate, ctx) => (useTerminal() ? terminalBacked : killable).execute(id, params, signal, onUpdate, ctx),
|
|
145
176
|
};
|
|
146
177
|
}
|
|
@@ -772,21 +803,20 @@ export class ClientSession {
|
|
|
772
803
|
const created = await createAgentSessionFromServices({
|
|
773
804
|
services,
|
|
774
805
|
sessionManager,
|
|
775
|
-
//
|
|
776
|
-
//
|
|
777
|
-
//
|
|
806
|
+
// 覆盖 SDK 内置 bash(customTools 按 name 覆盖)。双实现分流:
|
|
807
|
+
// 「默认 bash 覆盖」开关(terminalBash)关 → 原生 SDK bash(纯进程、不开终端);
|
|
808
|
+
// 开 → 终端接管 bash(persist 决定一次性/持久,可静默自动转后台)。
|
|
778
809
|
customTools: [
|
|
779
|
-
// bash 双实现动态分流:「终端接管」开启时命令跑进持久可见终端
|
|
780
|
-
// (保留 shell 状态、静默自动转后台),关闭时是原生 killable bash。
|
|
781
810
|
makeAdaptiveBashTool(makeKillableBashTool(effectiveCwd, this.bashKills), makeTerminalBashTool(terminals, {
|
|
782
811
|
cwd: effectiveCwd,
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
: 0,
|
|
812
|
+
// 设置开 = 用终端;此分支里 persist 未显式给时默认一次性(false)。
|
|
813
|
+
defaultPersist: () => false,
|
|
814
|
+
idleMs: () => Math.max(0, Math.floor(this.settingsSvc.current.terminalBashIdleMs) || 0),
|
|
787
815
|
kills: this.bashKills,
|
|
788
816
|
notifyBackgroundDone: (info) => this.notifyTerminalBashDone(terminals, info),
|
|
789
|
-
}),
|
|
817
|
+
}),
|
|
818
|
+
// 设置关 → 原生 bash;开 → 终端 bash。
|
|
819
|
+
() => this.settingsSvc.current.terminalBash),
|
|
790
820
|
...makePersistentTerminalTools(terminals, effectiveCwd),
|
|
791
821
|
// 插件注册的 AI 工具(创建时刻的实时快照;后续注册经
|
|
792
822
|
// refreshPluginTools 动态补入已有会话)。
|
|
@@ -2056,8 +2086,8 @@ export class ClientSession {
|
|
|
2056
2086
|
}
|
|
2057
2087
|
/** Kill only the running bash command(s) — the agent run itself continues
|
|
2058
2088
|
* (the bash tool returns an aborted error and the model moves on). Uses
|
|
2059
|
-
* the per-client AbortController set registered by
|
|
2060
|
-
* makeKillableBashTool. */
|
|
2089
|
+
* the per-client AbortController set registered by the bash tool paths
|
|
2090
|
+
* ({@link makeKillableBashTool} / {@link makeTerminalBashTool}). */
|
|
2061
2091
|
async abortBash() {
|
|
2062
2092
|
if (this.bashKills.size === 0) {
|
|
2063
2093
|
this.emit({
|
|
@@ -448,9 +448,9 @@ export async function buildAttachmentMessages(ctx, attachments) {
|
|
|
448
448
|
content: [
|
|
449
449
|
{
|
|
450
450
|
type: "text",
|
|
451
|
-
text: `
|
|
452
|
-
<vision-bridge>
|
|
453
|
-
${transcript}
|
|
451
|
+
text: `
|
|
452
|
+
<vision-bridge>
|
|
453
|
+
${transcript}
|
|
454
454
|
</vision-bridge>`,
|
|
455
455
|
},
|
|
456
456
|
...(pathImg
|