pi-web-ui 0.64.0 → 0.64.2

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.
Files changed (45) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +504 -504
  3. package/README.zh-CN.md +427 -427
  4. package/bin/pi-web-ui.mjs +1809 -1809
  5. package/deploy/com.xingshuyin.pi-web-ui.plist +48 -48
  6. package/deploy/nginx-subpath.conf +88 -88
  7. package/deploy/pi-web-ui-task.xml +54 -54
  8. package/deploy/pi-web-ui.service +31 -31
  9. package/dist/server/agent-service.js +45 -22
  10. package/dist/server/attachments.js +16 -11
  11. package/dist/server/dsh/dsh-agent-service.js +27 -7
  12. package/dist/server/dsh/runtime/cordis.yml +1 -1
  13. package/dist/server/dsh/runtime/goal-rpc.mjs +645 -645
  14. package/dist/server/dsh/runtime/launcher.mjs +164 -164
  15. package/dist/server/dsh/runtime/override.patch.yml +71 -71
  16. package/dist/server/dsh/runtime/runtime-root.mjs +86 -86
  17. package/dist/server/files-service.js +235 -43
  18. package/dist/server/goal-service.js +4 -1
  19. package/dist/server/index.js +48 -13
  20. package/dist/server/marker-service.js +8 -3
  21. package/dist/server/markers/builtins/rename.js +3 -3
  22. package/dist/server/model-admin.js +12 -6
  23. package/dist/server/plugins.js +4 -4
  24. package/dist/server/slash-commands.js +3 -0
  25. package/dist/server/terminals.js +25 -15
  26. package/dist/server/vision-bridge.js +9 -9
  27. package/dist/server/webui-context.js +2 -2
  28. package/extensions/webui.ts +190 -190
  29. package/package.json +109 -109
  30. package/themes/cyberpunk.css +81 -81
  31. package/themes/dazzle.css +81 -81
  32. package/themes/md-preview.css +98 -98
  33. package/themes/white.css +160 -160
  34. package/web/dist/assets/TerminalPanel-BXISCcrQ.js +6 -0
  35. package/web/dist/assets/index-BxuvIAJQ.js +326 -0
  36. package/web/dist/assets/{index-CMgDryBL.css → index-DVnuQrK5.css} +1 -1
  37. package/web/dist/favicon.svg +8 -8
  38. package/web/dist/index.html +21 -21
  39. package/web/dist/manifest.webmanifest +50 -50
  40. package/web/dist/sw.js +126 -126
  41. package/web/public/favicon.svg +8 -8
  42. package/web/public/manifest.webmanifest +50 -50
  43. package/web/public/sw.js +126 -126
  44. package/web/dist/assets/TerminalPanel-DNkAT34Z.js +0 -2
  45. package/web/dist/assets/index-_1vyEugI.js +0 -326
@@ -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>
@@ -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
@@ -84,14 +84,14 @@ export class QuiesceRejectedError extends Error {
84
84
  * programs wait for input that never comes. Legacy Chinese files are often
85
85
  * GBK/GB2312 — read them with the right encoding, never paste mojibake into
86
86
  * reasoning/answers. */
87
- 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:
88
-
89
-
90
-
91
- - 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.
92
- - 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.
93
- - 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\`).
94
-
87
+ 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:
88
+
89
+
90
+
91
+ - 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.
92
+ - 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.
93
+ - 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\`).
94
+
95
95
  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.`;
96
96
  /** bash 工具输出限制/过滤管道引导:模型习惯套 `| tail/-n`、`| head`、`| grep`、`| less`
97
97
  * 等限输出。这些管道在持久终端里会①缓冲(可见终端全程哑火、看不到实时进度)②把退出码
@@ -595,7 +595,7 @@ export class ClientSession {
595
595
  try {
596
596
  await conv.session.bindExtensions({
597
597
  mode: "rpc",
598
- onError: (err) => this.emit({ type: "notice", level: "error", text: err.error }),
598
+ onError: (err) => this.emit({ type: "notice", level: "error", text: err.error, textEn: err.error }),
599
599
  });
600
600
  }
601
601
  catch {
@@ -607,6 +607,7 @@ export class ClientSession {
607
607
  type: "notice",
608
608
  level: "error",
609
609
  text: `子代理 ${conversationId} 启动失败: ${err instanceof Error ? err.message : String(err)}`,
610
+ textEn: `Subagent ${conversationId} failed to start: ${err instanceof Error ? err.message : String(err)}`,
610
611
  });
611
612
  });
612
613
  this.emitConversations();
@@ -910,6 +911,7 @@ export class ClientSession {
910
911
  type: "notice",
911
912
  level: d.type,
912
913
  text: d.message,
914
+ textEn: d.message,
913
915
  });
914
916
  }
915
917
  }
@@ -1180,7 +1182,7 @@ export class ClientSession {
1180
1182
  mode: "rpc",
1181
1183
  uiContext: this.webUi,
1182
1184
  onError: (err) => {
1183
- this.emit({ type: "notice", level: "error", text: err.error });
1185
+ this.emit({ type: "notice", level: "error", text: err.error, textEn: err.error });
1184
1186
  },
1185
1187
  });
1186
1188
  conv.unsubscribe = conv.session.subscribe((event) => this.onEvent(conv, event));
@@ -1437,7 +1439,7 @@ export class ClientSession {
1437
1439
  if (aborted) {
1438
1440
  const stopNotice = this.goalSvc.onAgentEnd(conv, true);
1439
1441
  if (stopNotice) {
1440
- this.emit({ type: "notice", level: "warning", text: stopNotice });
1442
+ this.emit({ type: "notice", level: "warning", text: stopNotice.text, textEn: stopNotice.textEn });
1441
1443
  }
1442
1444
  break;
1443
1445
  }
@@ -2017,7 +2019,7 @@ export class ClientSession {
2017
2019
  const result = await def.run(args, { clientId: this.clientId });
2018
2020
  // 字符串返回值 → 通知条回显给发起人;富展示用 broadcast/sendTo。
2019
2021
  if (typeof result === "string" && result.trim()) {
2020
- this.emit({ type: "notice", level: "info", text: result });
2022
+ this.emit({ type: "notice", level: "info", text: result, textEn: result });
2021
2023
  }
2022
2024
  }
2023
2025
  catch (err) {
@@ -2518,8 +2520,8 @@ export class ClientSession {
2518
2520
  this.bg.push();
2519
2521
  }
2520
2522
  /** 插件设置保存结果等需要从 index.ts 发 notice 时用(emit 是私有的)。 */
2521
- emitNotice(level, text) {
2522
- this.emit({ type: "notice", level, text });
2523
+ emitNotice(level, text, textEn) {
2524
+ this.emit({ type: "notice", level, text, textEn });
2523
2525
  }
2524
2526
  /** Kill ONE background server (by port); returns whether anything was killed. */
2525
2527
  /** Kill ONE background server (by port) OR a plugin task (by taskId). */
@@ -2649,7 +2651,12 @@ export class ClientSession {
2649
2651
  });
2650
2652
  conv.runtime = runtime;
2651
2653
  conv.session = runtime.session;
2652
- this.emit({ type: "notice", level: "warning", text: reason });
2654
+ this.emit({
2655
+ type: "notice",
2656
+ level: "warning",
2657
+ text: reason,
2658
+ textEn: `${reason} (forced reset: run did not terminate)`,
2659
+ });
2653
2660
  await this.bindSession();
2654
2661
  this.emitConversations();
2655
2662
  void this.pushSlashCommands();
@@ -3041,6 +3048,9 @@ export class ClientSession {
3041
3048
  text: stillRunning
3042
3049
  ? "对话仍在后台运行,已停止删除;请等待其结束后再删除"
3043
3050
  : "未能切换到其他对话,已取消删除本次操作",
3051
+ textEn: stillRunning
3052
+ ? "Conversation is still running in the background; delete aborted — wait for it to finish and retry"
3053
+ : "Could not switch to another conversation; delete cancelled",
3044
3054
  });
3045
3055
  return;
3046
3056
  }
@@ -3147,11 +3157,21 @@ export class ClientSession {
3147
3157
  async dismissConversation(id) {
3148
3158
  const conv = this.convs.get(id);
3149
3159
  if (!conv) {
3150
- this.emit({ type: "notice", level: "warning", text: "该对话不存在或已关闭" });
3160
+ this.emit({
3161
+ type: "notice",
3162
+ level: "warning",
3163
+ text: "该对话不存在或已关闭",
3164
+ textEn: "This conversation does not exist or is already closed",
3165
+ });
3151
3166
  return;
3152
3167
  }
3153
3168
  if (id === this.activeId) {
3154
- this.emit({ type: "notice", level: "warning", text: "当前对话不能直接移出,请先切换到其他对话" });
3169
+ this.emit({
3170
+ type: "notice",
3171
+ level: "warning",
3172
+ text: "当前对话不能直接移出,请先切换到其他对话",
3173
+ textEn: "The active conversation cannot be removed directly — switch to another conversation first",
3174
+ });
3155
3175
  return;
3156
3176
  }
3157
3177
  if (!conv.listed) {
@@ -3176,6 +3196,7 @@ export class ClientSession {
3176
3196
  type: "notice",
3177
3197
  level: "warning",
3178
3198
  text: `对话「${conv.title}」仍在运行中,请先等待结束或点击停止后再移出`,
3199
+ textEn: `Conversation "${conv.title}" is still running — wait for it to finish or press Stop before removing`,
3179
3200
  });
3180
3201
  }
3181
3202
  else if (conv.terminals.list().length > 0) {
@@ -3183,6 +3204,7 @@ export class ClientSession {
3183
3204
  type: "notice",
3184
3205
  level: "warning",
3185
3206
  text: `对话「${conv.title}」还有未关闭的终端,请先关闭终端后再移出`,
3207
+ textEn: `Conversation "${conv.title}" still has open terminals — close them before removing`,
3186
3208
  });
3187
3209
  }
3188
3210
  else {
@@ -3190,6 +3212,7 @@ export class ClientSession {
3190
3212
  type: "notice",
3191
3213
  level: "warning",
3192
3214
  text: `对话「${conv.title}」暂时无法移出(存在待处理的后台任务/审查)`,
3215
+ textEn: `Conversation "${conv.title}" cannot be removed right now (pending background task/review)`,
3193
3216
  });
3194
3217
  }
3195
3218
  return;
@@ -3573,7 +3596,7 @@ export class ClientSession {
3573
3596
  this.removeConversation(displaced.id);
3574
3597
  for (const d of newRuntime.diagnostics) {
3575
3598
  if (d.type !== "info") {
3576
- this.emit({ type: "notice", level: d.type, text: d.message });
3599
+ this.emit({ type: "notice", level: d.type, text: d.message, textEn: d.message });
3577
3600
  }
3578
3601
  }
3579
3602
  await this.bindSession();
@@ -3733,17 +3756,17 @@ export class ClientSession {
3733
3756
  }
3734
3757
  /** Push the user command list (.pi/commands.json) to the client. */
3735
3758
  async listCommands() {
3736
- const { commands, path, warning } = await loadCommands(this.cwd);
3759
+ const { commands, path, warning, warningEn } = await loadCommands(this.cwd);
3737
3760
  if (warning) {
3738
- this.emit({ type: "notice", level: "warning", text: warning });
3761
+ this.emit({ type: "notice", level: "warning", text: warning, textEn: warningEn });
3739
3762
  }
3740
3763
  this.emit({ type: "commands", commands, path });
3741
3764
  }
3742
3765
  /** Persist the user command list (.pi/commands.json). */
3743
3766
  async saveCommands(commands) {
3744
- const { path, error } = await saveCommandsFile(this.cwd, commands);
3767
+ const { path, error, errorEn } = await saveCommandsFile(this.cwd, commands);
3745
3768
  if (error) {
3746
- this.emit({ type: "notice", level: "error", text: error });
3769
+ this.emit({ type: "notice", level: "error", text: error, textEn: errorEn });
3747
3770
  return;
3748
3771
  }
3749
3772
  this.emit({ type: "commands", commands, path });