pi-web-ui 0.29.0 → 0.29.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.
- package/dist/server/agent-service.js +201 -27
- package/dist/server/bg-servers.js +11 -2
- package/dist/server/client-state.js +22 -0
- package/dist/server/files-service.js +76 -0
- package/dist/server/index.js +7 -2
- package/dist/server/process-utils.js +26 -0
- package/dist/server/protocol-version.js +1 -1
- package/dist/server/terminals.js +40 -5
- package/package.json +96 -96
- package/themes/light.css +431 -0
- package/web/dist/assets/{TerminalPanel-B-bsYqea.js → TerminalPanel-D7lQ3g5k.js} +1 -1
- package/web/dist/assets/index-BiJTtKxQ.js +18 -0
- package/web/dist/assets/index-DxO8_hl1.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BsrqFaSZ.js +0 -13
- package/web/dist/assets/index-Dsb8Bak1.css +0 -10
package/dist/server/terminals.js
CHANGED
|
@@ -109,6 +109,8 @@ export async function saveCommandsFile(workspaceRoot, commands) {
|
|
|
109
109
|
}
|
|
110
110
|
const isWindows = process.platform === "win32";
|
|
111
111
|
const MAX_TERMINALS = 16;
|
|
112
|
+
/** Coalescing window for terminal_output WS messages (one animation frame). */
|
|
113
|
+
const OUTPUT_FLUSH_MS = 16;
|
|
112
114
|
const MAX_TERMINAL_HISTORY = 32;
|
|
113
115
|
const MAX_OUTPUT = 200_000;
|
|
114
116
|
const MAX_INPUT = 64 * 1024;
|
|
@@ -429,6 +431,7 @@ export class TerminalManager {
|
|
|
429
431
|
// process group) and start a fresh shell with the same id. Keep the
|
|
430
432
|
// last known size so the replacement matches the xterm's dimensions.
|
|
431
433
|
if (!existing.exited) {
|
|
434
|
+
this.flushPending(existing);
|
|
432
435
|
existing.exited = true;
|
|
433
436
|
try {
|
|
434
437
|
existing.pty.kill();
|
|
@@ -509,6 +512,8 @@ export class TerminalManager {
|
|
|
509
512
|
output: "",
|
|
510
513
|
outputOffset: 0,
|
|
511
514
|
waiters: new Set(),
|
|
515
|
+
pendingOut: "",
|
|
516
|
+
flushTimer: null,
|
|
512
517
|
};
|
|
513
518
|
this.terms.set(id, entry);
|
|
514
519
|
// The closures capture `entry`: after a restart the map points at the
|
|
@@ -517,7 +522,13 @@ export class TerminalManager {
|
|
|
517
522
|
if (this.terms.get(id) !== entry)
|
|
518
523
|
return;
|
|
519
524
|
this.appendOutput(entry, data);
|
|
520
|
-
|
|
525
|
+
// Coalesce instead of emitting per chunk: node-pty onData fires per
|
|
526
|
+
// ConPTY read buffer (dozens of bytes to a few KB each), so a build or
|
|
527
|
+
// `cat` of a big file used to produce hundreds/thousands of WS frames
|
|
528
|
+
// per second — each paying stringify + frame + parse + dispatch cost.
|
|
529
|
+
// A one-frame micro-batch cuts the frame rate 10-50× at ≤16ms latency
|
|
530
|
+
// (imperceptible; xterm writes batch data more efficiently anyway).
|
|
531
|
+
this.queueOut(entry, data);
|
|
521
532
|
});
|
|
522
533
|
pty.onExit(({ exitCode }) => {
|
|
523
534
|
if (this.terms.get(id) !== entry)
|
|
@@ -526,12 +537,33 @@ export class TerminalManager {
|
|
|
526
537
|
});
|
|
527
538
|
return true;
|
|
528
539
|
}
|
|
540
|
+
/** Emit output immediately, bypassing the coalescing window (rare paths:
|
|
541
|
+
* one-shot hints/banners — not per-chunk data). */
|
|
529
542
|
writeOut(id, data) {
|
|
530
|
-
const entry = this.terms.get(id);
|
|
531
|
-
if (!entry)
|
|
532
|
-
return;
|
|
533
543
|
this.emit({ type: "terminal_output", terminalId: id, data });
|
|
534
544
|
}
|
|
545
|
+
/** Queue output for the coalescing window; flushes via flushPending. */
|
|
546
|
+
queueOut(entry, data) {
|
|
547
|
+
entry.pendingOut += data;
|
|
548
|
+
if (entry.flushTimer)
|
|
549
|
+
return;
|
|
550
|
+
entry.flushTimer = setTimeout(() => {
|
|
551
|
+
entry.flushTimer = null;
|
|
552
|
+
this.flushPending(entry);
|
|
553
|
+
}, OUTPUT_FLUSH_MS);
|
|
554
|
+
}
|
|
555
|
+
/** Emit everything queued for this terminal (no-op when nothing pending). */
|
|
556
|
+
flushPending(entry) {
|
|
557
|
+
if (entry.flushTimer) {
|
|
558
|
+
clearTimeout(entry.flushTimer);
|
|
559
|
+
entry.flushTimer = null;
|
|
560
|
+
}
|
|
561
|
+
if (!entry.pendingOut)
|
|
562
|
+
return;
|
|
563
|
+
const pending = entry.pendingOut;
|
|
564
|
+
entry.pendingOut = "";
|
|
565
|
+
this.emit({ type: "terminal_output", terminalId: entry.id, data: pending });
|
|
566
|
+
}
|
|
535
567
|
appendOutput(entry, data) {
|
|
536
568
|
entry.output += data;
|
|
537
569
|
if (entry.output.length > MAX_OUTPUT) {
|
|
@@ -668,9 +700,11 @@ export class TerminalManager {
|
|
|
668
700
|
const entry = this.terms.get(id);
|
|
669
701
|
if (!entry || entry.exited)
|
|
670
702
|
return;
|
|
703
|
+
// Flush queued output BEFORE the exit banner so ordering is preserved.
|
|
704
|
+
this.flushPending(entry);
|
|
671
705
|
const banner = `\r\n\x1b[90m[进程已退出,退出码 ${exitCode}]\x1b[0m\r\n`;
|
|
672
706
|
this.appendOutput(entry, banner);
|
|
673
|
-
this.
|
|
707
|
+
this.emit({ type: "terminal_output", terminalId: id, data: banner });
|
|
674
708
|
entry.exited = true;
|
|
675
709
|
entry.exitCode = exitCode;
|
|
676
710
|
this.terms.delete(id);
|
|
@@ -705,6 +739,7 @@ export class TerminalManager {
|
|
|
705
739
|
kill(id) {
|
|
706
740
|
const entry = this.terms.get(id);
|
|
707
741
|
if (entry) {
|
|
742
|
+
this.flushPending(entry);
|
|
708
743
|
entry.exited = true;
|
|
709
744
|
try {
|
|
710
745
|
entry.pty.kill();
|
package/package.json
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-web-ui",
|
|
3
|
+
"version": "0.29.2",
|
|
4
|
+
"description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pi-web-ui": "bin/pi-web-ui.mjs"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/server/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"dist/",
|
|
14
|
+
"web/dist/",
|
|
15
|
+
"web/public/",
|
|
16
|
+
"themes/",
|
|
17
|
+
"deploy/",
|
|
18
|
+
"extensions/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"pi",
|
|
24
|
+
"pi-package",
|
|
25
|
+
"coding-agent",
|
|
26
|
+
"ai",
|
|
27
|
+
"chat",
|
|
28
|
+
"web-ui",
|
|
29
|
+
"terminal",
|
|
30
|
+
"llm"
|
|
31
|
+
],
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/xing-shuyin/pi-web-ui.git"
|
|
35
|
+
},
|
|
36
|
+
"pi": {
|
|
37
|
+
"extensions": [
|
|
38
|
+
"./extensions"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22.19.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"prepublishOnly": "npm run build",
|
|
46
|
+
"dev": "concurrently -k -n server,web -c blue,green \"npm:dev:server\" \"npm:dev:web\"",
|
|
47
|
+
"dev:server": "cross-env PORT=8788 PI_WEB_ALLOW_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 node --watch --import tsx server/index.ts",
|
|
48
|
+
"dev:web": "vite --config web/vite.config.ts",
|
|
49
|
+
"build": "npm run build:web && npm run build:server",
|
|
50
|
+
"build:web": "vite build --config web/vite.config.ts",
|
|
51
|
+
"build:server": "tsc -p tsconfig.server.json",
|
|
52
|
+
"typecheck": "tsc -p tsconfig.server.json --noEmit && tsc -p web/tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:unit": "vitest run",
|
|
55
|
+
"test:smoke": "node tests/run-smoke.mjs",
|
|
56
|
+
"check:protocol": "node scripts/check-protocol-sync.mjs",
|
|
57
|
+
"test:freeze": "node tests/freeze-test.mjs",
|
|
58
|
+
"start": "node dist/server/index.js"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@earendil-works/pi-coding-agent": "^0.84.2",
|
|
62
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
63
|
+
"@xterm/xterm": "^6.0.0",
|
|
64
|
+
"compression": "^1.8.1",
|
|
65
|
+
"express": "^4.21.2",
|
|
66
|
+
"highlight.js": "^11.10.0",
|
|
67
|
+
"node-pty": "^1.1.0",
|
|
68
|
+
"react": "^18.3.1",
|
|
69
|
+
"react-dom": "^18.3.1",
|
|
70
|
+
"react-icons": "^5.7.0",
|
|
71
|
+
"react-markdown": "^9.0.1",
|
|
72
|
+
"rehype-highlight": "^7.0.1",
|
|
73
|
+
"remark-gfm": "^4.0.0",
|
|
74
|
+
"typebox": "^1.3.14",
|
|
75
|
+
"ws": "^8.18.0"
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@types/compression": "^1.8.1",
|
|
79
|
+
"@types/express": "^4.17.21",
|
|
80
|
+
"@types/node": "^22.10.0",
|
|
81
|
+
"@types/react": "^18.3.12",
|
|
82
|
+
"@types/react-dom": "^18.3.1",
|
|
83
|
+
"@types/ws": "^8.5.13",
|
|
84
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
85
|
+
"concurrently": "^9.1.0",
|
|
86
|
+
"cross-env": "^10.1.0",
|
|
87
|
+
"playwright-core": "^1.62.1",
|
|
88
|
+
"tsx": "^4.19.2",
|
|
89
|
+
"typescript": "^5.7.2",
|
|
90
|
+
"vite": "^6.0.3",
|
|
91
|
+
"vitest": "^4.1.11"
|
|
92
|
+
},
|
|
93
|
+
"allowScripts": {
|
|
94
|
+
"node-pty@1.1.0": true
|
|
95
|
+
}
|
|
96
|
+
}
|