pi-web-ui 0.15.5 → 0.16.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.zh-CN.md +367 -367
- package/bin/pi-web-ui.mjs +0 -0
- package/deploy/com.xingshuyin.pi-web-ui.plist +48 -48
- package/deploy/nginx-subpath.conf +78 -78
- package/deploy/pi-web-ui-task.xml +71 -71
- package/deploy/pi-web-ui.service +31 -31
- package/dist/server/agent-service.js +74 -4
- package/extensions/webui.ts +184 -184
- package/package.json +1 -1
- package/web/dist/assets/{index-BSyUlz0A.js → index-BslXQ1Pt.js} +50 -50
- package/web/dist/assets/index-CyPZoWiz.css +41 -0
- 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/index-6vAiP5J0.css +0 -41
package/bin/pi-web-ui.mjs
CHANGED
|
File without changes
|
|
@@ -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>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>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,78 +1,78 @@
|
|
|
1
|
-
# pi-web-ui behind nginx at a sub-path: http://<host>:83/pi/
|
|
2
|
-
# Backend app: http://127.0.0.1:8787 (default PORT env)
|
|
3
|
-
#
|
|
4
|
-
# Topology:
|
|
5
|
-
# LAN users → http://192.168.1.101:83/pi/ (plain HTTP listener)
|
|
6
|
-
# frp (public) → 39.99.235.208:60018 → 127.0.0.1:83 (PROXY protocol v2)
|
|
7
|
-
#
|
|
8
|
-
# frpc sends PROXY v2 to nginx (transport.proxyProtocolVersion = "v2"), so
|
|
9
|
-
# direct LAN connections MUST NOT require it — two listeners, one port:
|
|
10
|
-
# * 127.0.0.1:83 proxy_protocol — only the local frp client connects
|
|
11
|
-
# here (it speaks PROXY v2; nginx then sees the real visitor IP).
|
|
12
|
-
# * 192.168.1.101:83 plain HTTP — LAN browsers, no PROXY header needed.
|
|
13
|
-
#
|
|
14
|
-
# The frontend uses absolute paths (/ws WebSocket, /assets/*, /favicon.svg,
|
|
15
|
-
# /api/file…) so those get their own proxied locations next to /pi/.
|
|
16
|
-
|
|
17
|
-
# Reuse for Upgrade/Connection headers (WebSocket).
|
|
18
|
-
map $http_upgrade $connection_upgrade {
|
|
19
|
-
default upgrade;
|
|
20
|
-
'' close;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
server {
|
|
24
|
-
listen 127.0.0.1:83 proxy_protocol;
|
|
25
|
-
listen 192.168.1.101:83;
|
|
26
|
-
|
|
27
|
-
server_name _;
|
|
28
|
-
|
|
29
|
-
# Trust PROXY-protocol headers only from the local frp client; plain
|
|
30
|
-
# (LAN) connections keep their real $remote_addr untouched.
|
|
31
|
-
set_real_ip_from 127.0.0.1;
|
|
32
|
-
real_ip_header proxy_protocol;
|
|
33
|
-
|
|
34
|
-
# ---- main entry: strip /pi/ and forward to the app root ----
|
|
35
|
-
location /pi/ {
|
|
36
|
-
proxy_pass http://127.0.0.1:8787/;
|
|
37
|
-
proxy_http_version 1.1;
|
|
38
|
-
proxy_set_header Host $host;
|
|
39
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
40
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
41
|
-
proxy_set_header X-Forwarded-Proto $scheme;
|
|
42
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
43
|
-
proxy_set_header Connection $connection_upgrade;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# ---- WebSocket (the frontend connects to ws://<host>/ws) ----
|
|
47
|
-
location /ws {
|
|
48
|
-
proxy_pass http://127.0.0.1:8787;
|
|
49
|
-
proxy_http_version 1.1;
|
|
50
|
-
proxy_set_header Upgrade $http_upgrade;
|
|
51
|
-
proxy_set_header Connection $connection_upgrade;
|
|
52
|
-
proxy_read_timeout 3600s;
|
|
53
|
-
proxy_send_timeout 3600s;
|
|
54
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
55
|
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# ---- absolute asset paths baked into index.html ----
|
|
59
|
-
location /assets/ {
|
|
60
|
-
proxy_pass http://127.0.0.1:8787;
|
|
61
|
-
}
|
|
62
|
-
location = /favicon.svg {
|
|
63
|
-
proxy_pass http://127.0.0.1:8787;
|
|
64
|
-
}
|
|
65
|
-
location = /favicon-streaming.svg {
|
|
66
|
-
proxy_pass http://127.0.0.1:8787;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
# ---- media preview / download / health API ----
|
|
70
|
-
location /api/ {
|
|
71
|
-
proxy_pass http://127.0.0.1:8787;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
# bare root → app entry
|
|
75
|
-
location = / {
|
|
76
|
-
return 302 /pi/;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
1
|
+
# pi-web-ui behind nginx at a sub-path: http://<host>:83/pi/
|
|
2
|
+
# Backend app: http://127.0.0.1:8787 (default PORT env)
|
|
3
|
+
#
|
|
4
|
+
# Topology:
|
|
5
|
+
# LAN users → http://192.168.1.101:83/pi/ (plain HTTP listener)
|
|
6
|
+
# frp (public) → 39.99.235.208:60018 → 127.0.0.1:83 (PROXY protocol v2)
|
|
7
|
+
#
|
|
8
|
+
# frpc sends PROXY v2 to nginx (transport.proxyProtocolVersion = "v2"), so
|
|
9
|
+
# direct LAN connections MUST NOT require it — two listeners, one port:
|
|
10
|
+
# * 127.0.0.1:83 proxy_protocol — only the local frp client connects
|
|
11
|
+
# here (it speaks PROXY v2; nginx then sees the real visitor IP).
|
|
12
|
+
# * 192.168.1.101:83 plain HTTP — LAN browsers, no PROXY header needed.
|
|
13
|
+
#
|
|
14
|
+
# The frontend uses absolute paths (/ws WebSocket, /assets/*, /favicon.svg,
|
|
15
|
+
# /api/file…) so those get their own proxied locations next to /pi/.
|
|
16
|
+
|
|
17
|
+
# Reuse for Upgrade/Connection headers (WebSocket).
|
|
18
|
+
map $http_upgrade $connection_upgrade {
|
|
19
|
+
default upgrade;
|
|
20
|
+
'' close;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
server {
|
|
24
|
+
listen 127.0.0.1:83 proxy_protocol;
|
|
25
|
+
listen 192.168.1.101:83;
|
|
26
|
+
|
|
27
|
+
server_name _;
|
|
28
|
+
|
|
29
|
+
# Trust PROXY-protocol headers only from the local frp client; plain
|
|
30
|
+
# (LAN) connections keep their real $remote_addr untouched.
|
|
31
|
+
set_real_ip_from 127.0.0.1;
|
|
32
|
+
real_ip_header proxy_protocol;
|
|
33
|
+
|
|
34
|
+
# ---- main entry: strip /pi/ and forward to the app root ----
|
|
35
|
+
location /pi/ {
|
|
36
|
+
proxy_pass http://127.0.0.1:8787/;
|
|
37
|
+
proxy_http_version 1.1;
|
|
38
|
+
proxy_set_header Host $host;
|
|
39
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
40
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
41
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
42
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
43
|
+
proxy_set_header Connection $connection_upgrade;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# ---- WebSocket (the frontend connects to ws://<host>/ws) ----
|
|
47
|
+
location /ws {
|
|
48
|
+
proxy_pass http://127.0.0.1:8787;
|
|
49
|
+
proxy_http_version 1.1;
|
|
50
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
51
|
+
proxy_set_header Connection $connection_upgrade;
|
|
52
|
+
proxy_read_timeout 3600s;
|
|
53
|
+
proxy_send_timeout 3600s;
|
|
54
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
55
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ---- absolute asset paths baked into index.html ----
|
|
59
|
+
location /assets/ {
|
|
60
|
+
proxy_pass http://127.0.0.1:8787;
|
|
61
|
+
}
|
|
62
|
+
location = /favicon.svg {
|
|
63
|
+
proxy_pass http://127.0.0.1:8787;
|
|
64
|
+
}
|
|
65
|
+
location = /favicon-streaming.svg {
|
|
66
|
+
proxy_pass http://127.0.0.1:8787;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# ---- media preview / download / health API ----
|
|
70
|
+
location /api/ {
|
|
71
|
+
proxy_pass http://127.0.0.1:8787;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# bare root → app entry
|
|
75
|
+
location = / {
|
|
76
|
+
return 302 /pi/;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-16"?>
|
|
2
|
-
<!--
|
|
3
|
-
pi-web-ui Task Scheduler task — Windows auto-start at logon.
|
|
4
|
-
|
|
5
|
-
The easy way (no admin needed, generates everything for you):
|
|
6
|
-
npm i -g pi-web-ui
|
|
7
|
-
pi-web-ui server install --port 8787 --cwd C:\path\to\project
|
|
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).
|
|
25
|
-
-->
|
|
26
|
-
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
27
|
-
<RegistrationInfo>
|
|
28
|
-
<Description>pi-web-ui — web chat for the pi coding agent (auto-start at logon)</Description>
|
|
29
|
-
</RegistrationInfo>
|
|
30
|
-
<Triggers>
|
|
31
|
-
<LogonTrigger>
|
|
32
|
-
<Enabled>true</Enabled>
|
|
33
|
-
</LogonTrigger>
|
|
34
|
-
</Triggers>
|
|
35
|
-
<Principals>
|
|
36
|
-
<Principal id="Author">
|
|
37
|
-
<LogonType>InteractiveToken</LogonType>
|
|
38
|
-
<RunLevel>LeastPrivilege</RunLevel>
|
|
39
|
-
</Principal>
|
|
40
|
-
</Principals>
|
|
41
|
-
<Settings>
|
|
42
|
-
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
43
|
-
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
44
|
-
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
45
|
-
<AllowHardTerminate>true</AllowHardTerminate>
|
|
46
|
-
<StartWhenAvailable>false</StartWhenAvailable>
|
|
47
|
-
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
48
|
-
<IdleSettings>
|
|
49
|
-
<StopOnIdleEnd>false</StopOnIdleEnd>
|
|
50
|
-
<RestartOnIdle>false</RestartOnIdle>
|
|
51
|
-
</IdleSettings>
|
|
52
|
-
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
53
|
-
<Enabled>true</Enabled>
|
|
54
|
-
<Hidden>false</Hidden>
|
|
55
|
-
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
56
|
-
<WakeToRun>false</WakeToRun>
|
|
57
|
-
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
58
|
-
<Priority>7</Priority>
|
|
59
|
-
<RestartOnFailure>
|
|
60
|
-
<Interval>PT1M</Interval>
|
|
61
|
-
<Count>3</Count>
|
|
62
|
-
</RestartOnFailure>
|
|
63
|
-
</Settings>
|
|
64
|
-
<Actions Context="Author">
|
|
65
|
-
<Exec>
|
|
66
|
-
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
|
|
67
|
-
<Arguments>-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "%APPDATA%\pi-web-ui\pi-web-ui.ps1"</Arguments>
|
|
68
|
-
<WorkingDirectory>%USERPROFILE%</WorkingDirectory>
|
|
69
|
-
</Exec>
|
|
70
|
-
</Actions>
|
|
71
|
-
</Task>
|
|
1
|
+
<?xml version="1.0" encoding="UTF-16"?>
|
|
2
|
+
<!--
|
|
3
|
+
pi-web-ui Task Scheduler task — Windows auto-start at logon.
|
|
4
|
+
|
|
5
|
+
The easy way (no admin needed, generates everything for you):
|
|
6
|
+
npm i -g pi-web-ui
|
|
7
|
+
pi-web-ui server install --port 8787 --cwd C:\path\to\project
|
|
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).
|
|
25
|
+
-->
|
|
26
|
+
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
27
|
+
<RegistrationInfo>
|
|
28
|
+
<Description>pi-web-ui — web chat for the pi coding agent (auto-start at logon)</Description>
|
|
29
|
+
</RegistrationInfo>
|
|
30
|
+
<Triggers>
|
|
31
|
+
<LogonTrigger>
|
|
32
|
+
<Enabled>true</Enabled>
|
|
33
|
+
</LogonTrigger>
|
|
34
|
+
</Triggers>
|
|
35
|
+
<Principals>
|
|
36
|
+
<Principal id="Author">
|
|
37
|
+
<LogonType>InteractiveToken</LogonType>
|
|
38
|
+
<RunLevel>LeastPrivilege</RunLevel>
|
|
39
|
+
</Principal>
|
|
40
|
+
</Principals>
|
|
41
|
+
<Settings>
|
|
42
|
+
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
|
43
|
+
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
44
|
+
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
45
|
+
<AllowHardTerminate>true</AllowHardTerminate>
|
|
46
|
+
<StartWhenAvailable>false</StartWhenAvailable>
|
|
47
|
+
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
48
|
+
<IdleSettings>
|
|
49
|
+
<StopOnIdleEnd>false</StopOnIdleEnd>
|
|
50
|
+
<RestartOnIdle>false</RestartOnIdle>
|
|
51
|
+
</IdleSettings>
|
|
52
|
+
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
53
|
+
<Enabled>true</Enabled>
|
|
54
|
+
<Hidden>false</Hidden>
|
|
55
|
+
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
56
|
+
<WakeToRun>false</WakeToRun>
|
|
57
|
+
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
58
|
+
<Priority>7</Priority>
|
|
59
|
+
<RestartOnFailure>
|
|
60
|
+
<Interval>PT1M</Interval>
|
|
61
|
+
<Count>3</Count>
|
|
62
|
+
</RestartOnFailure>
|
|
63
|
+
</Settings>
|
|
64
|
+
<Actions Context="Author">
|
|
65
|
+
<Exec>
|
|
66
|
+
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
|
|
67
|
+
<Arguments>-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "%APPDATA%\pi-web-ui\pi-web-ui.ps1"</Arguments>
|
|
68
|
+
<WorkingDirectory>%USERPROFILE%</WorkingDirectory>
|
|
69
|
+
</Exec>
|
|
70
|
+
</Actions>
|
|
71
|
+
</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=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=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
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* so reconnects just re-request a snapshot.
|
|
12
12
|
*/
|
|
13
13
|
import { spawn } from "node:child_process";
|
|
14
|
-
import { existsSync, readFileSync, statSync, writeFileSync, mkdirSync, } from "node:fs";
|
|
14
|
+
import { existsSync, readFileSync, statSync, writeFileSync, mkdirSync, watch, } from "node:fs";
|
|
15
15
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager, } from "@earendil-works/pi-coding-agent";
|
|
@@ -728,6 +728,14 @@ export class ClientSession {
|
|
|
728
728
|
disposed = false;
|
|
729
729
|
/** pi-config readiness check, cached briefly so 60ms snapshots don't hit disk. */
|
|
730
730
|
piCheckCache = null;
|
|
731
|
+
/** fs.watch on the currently-listed directory — file changes push an instant
|
|
732
|
+
* refresh (`file_changed`) so the tree updates without waiting for the 10s
|
|
733
|
+
* poll. Only the listed directory is watched (one level); navigating
|
|
734
|
+
* re-watches the new target. fs.watch isn't available on every platform /
|
|
735
|
+
* filesystem — failures silently fall back to the poll. */
|
|
736
|
+
fsWatcher = null;
|
|
737
|
+
watchPath = null;
|
|
738
|
+
watchTimer = null;
|
|
731
739
|
constructor(clientId, cwd, agentDir, stateStore) {
|
|
732
740
|
this.clientId = clientId;
|
|
733
741
|
this.cwd = cwd;
|
|
@@ -829,8 +837,11 @@ export class ClientSession {
|
|
|
829
837
|
this.sinks.delete(send);
|
|
830
838
|
// No sockets left for this client — kill its terminals so processes don't
|
|
831
839
|
// survive a closed tab / dropped connection.
|
|
832
|
-
if (this.sinks.size === 0)
|
|
840
|
+
if (this.sinks.size === 0) {
|
|
833
841
|
this.terminals.killAll();
|
|
842
|
+
// No sockets → nobody to refresh; drop the dir watcher too.
|
|
843
|
+
this.unwatchDir();
|
|
844
|
+
}
|
|
834
845
|
}
|
|
835
846
|
/** Broadcast to every connected socket of this client. */
|
|
836
847
|
emit(msg) {
|
|
@@ -1653,8 +1664,13 @@ export class ClientSession {
|
|
|
1653
1664
|
await s.sendCustomMessage(aside.message, { deliverAs: "nextTurn" });
|
|
1654
1665
|
}
|
|
1655
1666
|
if (s.isStreaming) {
|
|
1656
|
-
//
|
|
1657
|
-
|
|
1667
|
+
// Steer: interrupts the current run — the message is delivered right
|
|
1668
|
+
// after the current assistant turn settles (remaining planned tool
|
|
1669
|
+
// calls are skipped) and the agent immediately responds to it. This
|
|
1670
|
+
// is the pi CLI Enter-during-streaming semantic (docs/usage: Enter
|
|
1671
|
+
// queues a steering message); followUp would wait for the whole run
|
|
1672
|
+
// to finish, which users perceive as ordinary queueing.
|
|
1673
|
+
await s.prompt(text, { streamingBehavior: "steer" });
|
|
1658
1674
|
}
|
|
1659
1675
|
else {
|
|
1660
1676
|
await s.prompt(text);
|
|
@@ -2427,6 +2443,9 @@ export class ClientSession {
|
|
|
2427
2443
|
// always use "/", but relative() returns "\\" on Windows.
|
|
2428
2444
|
const rel = rawRel.split(sep).join("/");
|
|
2429
2445
|
const { entries, truncated, error } = await readDirForUI(target, rel);
|
|
2446
|
+
// Watch the listed directory (only after a successful read — a missing
|
|
2447
|
+
// dir throws above and must not create a watcher on a phantom path).
|
|
2448
|
+
this.watchDir(target, rel);
|
|
2430
2449
|
if (error) {
|
|
2431
2450
|
// Windows-only: unreadable system dirs degrade to an empty list
|
|
2432
2451
|
// with a warning instead of a hard error — the panel stays usable.
|
|
@@ -2456,6 +2475,56 @@ export class ClientSession {
|
|
|
2456
2475
|
});
|
|
2457
2476
|
}
|
|
2458
2477
|
}
|
|
2478
|
+
/** Watch a directory for changes so the file panel refreshes instantly
|
|
2479
|
+
* instead of waiting for the 10s poll. Watches the directory exactly as
|
|
2480
|
+
* listed (one level); navigating re-watches the new target. fs.watch is
|
|
2481
|
+
* unavailable on some platforms/filesystems — failures silently fall back
|
|
2482
|
+
* to the poll. */
|
|
2483
|
+
watchDir(absPath, rel) {
|
|
2484
|
+
if (this.disposed || this.watchPath === rel)
|
|
2485
|
+
return;
|
|
2486
|
+
this.unwatchDir();
|
|
2487
|
+
this.watchPath = rel;
|
|
2488
|
+
try {
|
|
2489
|
+
// persistent: false — the watcher must not keep the process alive.
|
|
2490
|
+
this.fsWatcher = watch(absPath, { persistent: false }, () => {
|
|
2491
|
+
// Burst events (npm install, git ops, editor save→rename) are
|
|
2492
|
+
// debounced into a single refresh.
|
|
2493
|
+
if (this.watchTimer)
|
|
2494
|
+
return;
|
|
2495
|
+
this.watchTimer = setTimeout(() => {
|
|
2496
|
+
this.watchTimer = null;
|
|
2497
|
+
this.emit({ type: "file_changed", path: this.watchPath ?? "" });
|
|
2498
|
+
}, 400);
|
|
2499
|
+
});
|
|
2500
|
+
this.fsWatcher.on("error", () => {
|
|
2501
|
+
// Directory deleted / unsupported fs — stop watching; the poll (or
|
|
2502
|
+
// the next navigation) restores things.
|
|
2503
|
+
this.unwatchDir();
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
catch {
|
|
2507
|
+
// fs.watch unsupported (some network mounts, containers) — poll covers it.
|
|
2508
|
+
this.fsWatcher = null;
|
|
2509
|
+
this.watchPath = null;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
unwatchDir() {
|
|
2513
|
+
if (this.watchTimer) {
|
|
2514
|
+
clearTimeout(this.watchTimer);
|
|
2515
|
+
this.watchTimer = null;
|
|
2516
|
+
}
|
|
2517
|
+
if (this.fsWatcher) {
|
|
2518
|
+
try {
|
|
2519
|
+
this.fsWatcher.close();
|
|
2520
|
+
}
|
|
2521
|
+
catch {
|
|
2522
|
+
// already closed
|
|
2523
|
+
}
|
|
2524
|
+
this.fsWatcher = null;
|
|
2525
|
+
}
|
|
2526
|
+
this.watchPath = null;
|
|
2527
|
+
}
|
|
2459
2528
|
/** Read a workspace file for the preview panel (size-capped, binary-safe). */
|
|
2460
2529
|
async readFile(relPath) {
|
|
2461
2530
|
try {
|
|
@@ -2822,6 +2891,7 @@ export class ClientSession {
|
|
|
2822
2891
|
clearInterval(this.widgetsTimer);
|
|
2823
2892
|
this.widgetsTimer = null;
|
|
2824
2893
|
}
|
|
2894
|
+
this.unwatchDir();
|
|
2825
2895
|
this.webUi.dispose();
|
|
2826
2896
|
for (const conv of this.convs.values()) {
|
|
2827
2897
|
conv.unsubscribe?.();
|