pi-web-ui 0.13.1 → 0.14.1
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 +15 -15
- package/bin/pi-web-ui.mjs +0 -0
- package/deploy/com.xingshuyin.pi-web-ui.plist +48 -48
- package/deploy/pi-web-ui-task.xml +71 -71
- package/deploy/pi-web-ui.service +31 -31
- package/dist/server/agent-service.js +68 -49
- package/dist/server/index.js +13 -5
- package/extensions/webui.ts +184 -184
- package/package.json +1 -1
- package/web/dist/assets/{index-CgC_H3ZV.js → index-Bt4Sht-Z.js} +48 -48
- package/web/dist/assets/{index-TOCPj148.css → index-Dn87_VWZ.css} +1 -1
- package/web/dist/favicon.svg +8 -8
- package/web/dist/index.html +15 -15
- package/web/public/favicon.svg +8 -8
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 xing-shuyin
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xing-shuyin
|
|
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.zh-CN.md
CHANGED
|
@@ -157,13 +157,13 @@ pi-web-ui server uninstall # 彻底移除服务
|
|
|
157
157
|
|
|
158
158
|
## 配置
|
|
159
159
|
|
|
160
|
-
| 环境变量
|
|
161
|
-
|
|
|
162
|
-
| `PORT`
|
|
163
|
-
| `PI_WEB_CWD`
|
|
164
|
-
| `PI_WEB_DATA_DIR`
|
|
165
|
-
| `PI_WEB_INLINE_FILE_MAX` | `12288` (12KB)
|
|
166
|
-
| `PI_CODING_AGENT_DIR`
|
|
160
|
+
| 环境变量 | 默认值 | 说明 |
|
|
161
|
+
| -------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| `PORT` | `8787` | HTTP/WebSocket 端口 |
|
|
163
|
+
| `PI_WEB_CWD` | 服务端 cwd | 智能体操作的工作区目录(读/编辑/bash/写) |
|
|
164
|
+
| `PI_WEB_DATA_DIR` | `<cwd>/.pi-web` | 每客户端会话目录的存放位置 |
|
|
165
|
+
| `PI_WEB_INLINE_FILE_MAX` | `12288` (12KB) | 小于等于该大小的文本附件直接内联进模型上下文;更大的文件以路径引用方式传入,模型按需用 read 工具读取(小改动省 token) |
|
|
166
|
+
| `PI_CODING_AGENT_DIR` | `~/.pi/agent` | pi 配置目录(auth.json、models.json、skills、extensions) |
|
|
167
167
|
|
|
168
168
|
示例——让智能体面向某个项目:
|
|
169
169
|
|
|
@@ -265,14 +265,14 @@ server/agent-service.ts 每客户端 ClientSession:
|
|
|
265
265
|
|
|
266
266
|
## 脚本
|
|
267
267
|
|
|
268
|
-
| 脚本
|
|
269
|
-
|
|
|
270
|
-
| `npm run dev`
|
|
271
|
-
| `npm run build`
|
|
272
|
-
| `npm start`
|
|
273
|
-
| `npm run typecheck`
|
|
274
|
-
| `node terminal-smoke-test.mjs`
|
|
275
|
-
| `node terminal-browser-test.mjs` | 终端视图的无头浏览器 E2E(先 build)
|
|
268
|
+
| 脚本 | 作用 |
|
|
269
|
+
| ---------------------------------- | ---------------------------------------------- |
|
|
270
|
+
| `npm run dev` | 服务端(tsx watch)+ Vite dev server + WS 代理 |
|
|
271
|
+
| `npm run build` | 类型检查 + 构建前端和服务端 |
|
|
272
|
+
| `npm start` | 运行生产服务端(提供`web/dist`) |
|
|
273
|
+
| `npm run typecheck` | 双端`tsc --noEmit` |
|
|
274
|
+
| `node terminal-smoke-test.mjs` | WS 层终端/命令协议测试(先 build) |
|
|
275
|
+
| `node terminal-browser-test.mjs` | 终端视图的无头浏览器 E2E(先 build) |
|
|
276
276
|
|
|
277
277
|
## 部署与开机自启
|
|
278
278
|
|
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,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
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgentService — wraps the pi SDK (@earendil-works/pi-coding-agent) for the web
|
|
3
3
|
* frontend. Each browser client (identified by a persistent clientId) gets its
|
|
4
|
-
* own AgentSessionRuntime
|
|
5
|
-
*
|
|
4
|
+
* own AgentSessionRuntime, but sessions live in the SDK default per-project
|
|
5
|
+
* directory (<agentDir>/sessions/--<cwd>--/) — the same transcript files the
|
|
6
|
+
* pi CLI/TUI use — so every conversation of a folder shows up everywhere.
|
|
6
7
|
*
|
|
7
8
|
* Streaming model: the SDK emits AgentSessionEvents; we forward lightweight
|
|
8
9
|
* `tool_delta` messages for live tool output and schedule throttled full-state
|
|
@@ -446,10 +447,6 @@ export class WebUIContext {
|
|
|
446
447
|
this.pendingDialogs.clear();
|
|
447
448
|
}
|
|
448
449
|
}
|
|
449
|
-
/** Sanitize a clientId (UUID) for use as a directory name. */
|
|
450
|
-
function sanitizeId(id) {
|
|
451
|
-
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80) || "anon";
|
|
452
|
-
}
|
|
453
450
|
const IS_WIN32 = process.platform === "win32";
|
|
454
451
|
// mac/linux: hide build & dependency noise (original behavior).
|
|
455
452
|
const IGNORED_ENTRIES = new Set([
|
|
@@ -683,9 +680,6 @@ function conversationTitle(session) {
|
|
|
683
680
|
export class ClientSession {
|
|
684
681
|
clientId;
|
|
685
682
|
cwd;
|
|
686
|
-
/** Absolute per-client session directory.
|
|
687
|
-
/** Absolute per-client session directory. */
|
|
688
|
-
sessionDir;
|
|
689
683
|
/** pi config dir (auth/models/skills). */
|
|
690
684
|
agentDir;
|
|
691
685
|
/** Persisted per-client UI state (last workspace + recent projects). */
|
|
@@ -734,22 +728,22 @@ export class ClientSession {
|
|
|
734
728
|
disposed = false;
|
|
735
729
|
/** pi-config readiness check, cached briefly so 60ms snapshots don't hit disk. */
|
|
736
730
|
piCheckCache = null;
|
|
737
|
-
constructor(clientId, cwd,
|
|
731
|
+
constructor(clientId, cwd, agentDir, stateStore) {
|
|
738
732
|
this.clientId = clientId;
|
|
739
733
|
this.cwd = cwd;
|
|
740
|
-
this.sessionDir = sessionDir;
|
|
741
734
|
this.agentDir = agentDir;
|
|
742
735
|
this.stateStore = stateStore;
|
|
743
736
|
}
|
|
744
|
-
static async create(clientId, cwd,
|
|
737
|
+
static async create(clientId, cwd, stateStore) {
|
|
745
738
|
const agentDir = process.env.PI_CODING_AGENT_DIR ?? getAgentDir();
|
|
746
|
-
const cs = new ClientSession(clientId, cwd,
|
|
739
|
+
const cs = new ClientSession(clientId, cwd, agentDir, stateStore);
|
|
747
740
|
const runtime = await createAgentSessionRuntime(cs.makeRuntimeFactory(), {
|
|
748
741
|
cwd,
|
|
749
742
|
agentDir,
|
|
750
|
-
// Resume the most recent session for this
|
|
751
|
-
//
|
|
752
|
-
|
|
743
|
+
// Resume the most recent session for this project — the SDK default
|
|
744
|
+
// per-project dir (<agentDir>/sessions/--<cwd>--/, shared with the
|
|
745
|
+
// pi CLI/TUI) — or start a fresh one on first visit.
|
|
746
|
+
sessionManager: SessionManager.continueRecent(cwd),
|
|
753
747
|
});
|
|
754
748
|
// First conversation = the resumed session; it also seeds the shared
|
|
755
749
|
// ModelRuntime that every later conversation reuses.
|
|
@@ -809,6 +803,7 @@ export class ClientSession {
|
|
|
809
803
|
lastMessagesArray: [],
|
|
810
804
|
queueSteering: 0,
|
|
811
805
|
queueFollowUp: 0,
|
|
806
|
+
toolStartTimes: new Map(),
|
|
812
807
|
};
|
|
813
808
|
}
|
|
814
809
|
/** Add a socket to this client's broadcast set; flushes buffered startup notices. */
|
|
@@ -883,6 +878,52 @@ export class ClientSession {
|
|
|
883
878
|
}
|
|
884
879
|
break;
|
|
885
880
|
}
|
|
881
|
+
case "tool_execution_start": {
|
|
882
|
+
// Record the moment the tool actually starts so tool_status can
|
|
883
|
+
// report real execution time (vs. time spent waiting on the model).
|
|
884
|
+
conv.toolStartTimes.set(event.toolCallId, Date.now());
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
case "tool_execution_end": {
|
|
888
|
+
const startedAt = conv.toolStartTimes.get(event.toolCallId);
|
|
889
|
+
conv.toolStartTimes.delete(event.toolCallId);
|
|
890
|
+
const durationMs = startedAt !== undefined ? Date.now() - startedAt : undefined;
|
|
891
|
+
// The bash tool does not put its exit code in result.details — on
|
|
892
|
+
// failure it throws "Command exited with code N" and the agent
|
|
893
|
+
// wraps that into the error result text. Try details first (future
|
|
894
|
+
// tools / SDK changes), then parse the error text.
|
|
895
|
+
const details = event.result?.details;
|
|
896
|
+
let exitCode;
|
|
897
|
+
if (typeof details === "object" &&
|
|
898
|
+
details !== null &&
|
|
899
|
+
typeof details.exitCode === "number") {
|
|
900
|
+
exitCode = details.exitCode;
|
|
901
|
+
}
|
|
902
|
+
else if (event.isError) {
|
|
903
|
+
const content = event.result?.content;
|
|
904
|
+
const text = Array.isArray(content)
|
|
905
|
+
? content
|
|
906
|
+
.map((c) => (typeof c === "object" &&
|
|
907
|
+
c !== null &&
|
|
908
|
+
c.type === "text")
|
|
909
|
+
? (c.text ?? "")
|
|
910
|
+
: "")
|
|
911
|
+
.join("\n")
|
|
912
|
+
: "";
|
|
913
|
+
const m = text.match(/exited with code (\d+)/);
|
|
914
|
+
if (m)
|
|
915
|
+
exitCode = Number(m[1]);
|
|
916
|
+
}
|
|
917
|
+
this.emit({
|
|
918
|
+
type: "tool_status",
|
|
919
|
+
toolCallId: event.toolCallId,
|
|
920
|
+
toolName: event.toolName,
|
|
921
|
+
isError: event.isError,
|
|
922
|
+
exitCode,
|
|
923
|
+
durationMs,
|
|
924
|
+
});
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
886
927
|
case "tool_execution_update": {
|
|
887
928
|
const text = extractPartialText(event.partialResult);
|
|
888
929
|
if (text) {
|
|
@@ -2100,7 +2141,7 @@ export class ClientSession {
|
|
|
2100
2141
|
const runtime = await createAgentSessionRuntime(this.makeRuntimeFactory(), {
|
|
2101
2142
|
cwd: this.cwd,
|
|
2102
2143
|
agentDir: this.agentDir,
|
|
2103
|
-
sessionManager: SessionManager.create(this.cwd
|
|
2144
|
+
sessionManager: SessionManager.create(this.cwd),
|
|
2104
2145
|
});
|
|
2105
2146
|
const conv = this.makeConversation(runtime);
|
|
2106
2147
|
this.convs.set(conv.id, conv);
|
|
@@ -2206,23 +2247,12 @@ export class ClientSession {
|
|
|
2206
2247
|
}
|
|
2207
2248
|
async pushSessions() {
|
|
2208
2249
|
try {
|
|
2209
|
-
|
|
2210
|
-
//
|
|
2211
|
-
//
|
|
2212
|
-
|
|
2213
|
-
// ones created in this web UI.
|
|
2214
|
-
const safePath = `--${resolve(this.cwd)
|
|
2215
|
-
.replace(/^[/\\]/, "")
|
|
2216
|
-
.replace(/[/\\:]/g, "-")}--`;
|
|
2217
|
-
const tuiSessionDir = join(this.agentDir, "sessions", safePath);
|
|
2218
|
-
const [webInfos, tuiInfos] = await Promise.all([
|
|
2219
|
-
SessionManager.list(this.cwd, this.sessionDir),
|
|
2220
|
-
existsSync(tuiSessionDir)
|
|
2221
|
-
? SessionManager.list(this.cwd, tuiSessionDir).catch(() => [])
|
|
2222
|
-
: Promise.resolve([]),
|
|
2223
|
-
]);
|
|
2250
|
+
// Sessions live in the SDK default per-project dir
|
|
2251
|
+
// (<agentDir>/sessions/--<cwd>--/), the same files the pi CLI/TUI
|
|
2252
|
+
// use — one listing covers every conversation of the current folder.
|
|
2253
|
+
const infos = await SessionManager.list(this.cwd);
|
|
2224
2254
|
const sessions = new Map();
|
|
2225
|
-
for (const s of
|
|
2255
|
+
for (const s of infos) {
|
|
2226
2256
|
sessions.set(s.path, {
|
|
2227
2257
|
path: s.path,
|
|
2228
2258
|
name: s.name,
|
|
@@ -2232,16 +2262,6 @@ export class ClientSession {
|
|
|
2232
2262
|
source: "web",
|
|
2233
2263
|
});
|
|
2234
2264
|
}
|
|
2235
|
-
for (const s of tuiInfos) {
|
|
2236
|
-
sessions.set(s.path, {
|
|
2237
|
-
path: s.path,
|
|
2238
|
-
name: s.name,
|
|
2239
|
-
firstMessage: s.firstMessage,
|
|
2240
|
-
messageCount: s.messageCount,
|
|
2241
|
-
modified: s.modified.getTime(),
|
|
2242
|
-
source: "tui",
|
|
2243
|
-
});
|
|
2244
|
-
}
|
|
2245
2265
|
const sorted = [...sessions.values()].sort((a, b) => b.modified - a.modified);
|
|
2246
2266
|
this.emit({ type: "sessions", sessions: sorted });
|
|
2247
2267
|
}
|
|
@@ -2366,7 +2386,7 @@ export class ClientSession {
|
|
|
2366
2386
|
const map = new Map();
|
|
2367
2387
|
for (const p of saved.projects)
|
|
2368
2388
|
map.set(p.path, p.lastUsed);
|
|
2369
|
-
const all = await SessionManager.listAll(
|
|
2389
|
+
const all = await SessionManager.listAll();
|
|
2370
2390
|
for (const s of all) {
|
|
2371
2391
|
if (s.cwd) {
|
|
2372
2392
|
const t = s.modified.getTime();
|
|
@@ -2655,7 +2675,7 @@ export class ClientSession {
|
|
|
2655
2675
|
const newRuntime = await createAgentSessionRuntime(this.makeRuntimeFactory(), {
|
|
2656
2676
|
cwd: abs,
|
|
2657
2677
|
agentDir: this.agentDir,
|
|
2658
|
-
sessionManager: SessionManager.continueRecent(abs
|
|
2678
|
+
sessionManager: SessionManager.continueRecent(abs),
|
|
2659
2679
|
});
|
|
2660
2680
|
const conv = this.makeConversation(newRuntime);
|
|
2661
2681
|
this.convs.set(conv.id, conv);
|
|
@@ -2816,7 +2836,6 @@ export class ClientSession {
|
|
|
2816
2836
|
}
|
|
2817
2837
|
export class AgentService {
|
|
2818
2838
|
cwd;
|
|
2819
|
-
sessionDirRoot;
|
|
2820
2839
|
clients = new Map();
|
|
2821
2840
|
pending = new Map();
|
|
2822
2841
|
stateStore;
|
|
@@ -2825,9 +2844,8 @@ export class AgentService {
|
|
|
2825
2844
|
* self-update; returns whether the process will restart itself.
|
|
2826
2845
|
*/
|
|
2827
2846
|
onUpdateReady = undefined;
|
|
2828
|
-
constructor(cwd,
|
|
2847
|
+
constructor(cwd, stateFile) {
|
|
2829
2848
|
this.cwd = cwd;
|
|
2830
|
-
this.sessionDirRoot = sessionDirRoot;
|
|
2831
2849
|
this.stateStore = new ClientStateStore(stateFile);
|
|
2832
2850
|
}
|
|
2833
2851
|
/** Get or create the session for a client, racing attach calls safely. */
|
|
@@ -2852,7 +2870,8 @@ export class AgentService {
|
|
|
2852
2870
|
// gone (unmounted drive / deleted) — fall back to the default
|
|
2853
2871
|
}
|
|
2854
2872
|
}
|
|
2855
|
-
|
|
2873
|
+
// Sessions use the SDK default per-project dir — no per-client dir.
|
|
2874
|
+
const creating = ClientSession.create(clientId, cwd, this.stateStore).finally(() => {
|
|
2856
2875
|
this.pending.delete(clientId);
|
|
2857
2876
|
});
|
|
2858
2877
|
this.pending.set(clientId, creating);
|