u1s1-cli 1.2.6 → 1.2.8
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/package.json +1 -1
- package/scripts/patch-pi.js +128 -58
- package/scripts/render-regression.mjs +56 -2
package/package.json
CHANGED
package/scripts/patch-pi.js
CHANGED
|
@@ -1,103 +1,173 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Apply u1s1 runtime patches to pi during postinstall.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* The repository applies patches/*.patch through pnpm for development, while
|
|
5
|
+
* global `npm i -g u1s1-cli` installations do not understand pnpm patches.
|
|
6
|
+
* This script applies the same compact-UI and Windows TUI fixes to installed
|
|
7
|
+
* dependencies so both installation paths behave identically.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
9
|
+
* Properties:
|
|
10
|
+
* - Pure Node implementation with no patch(1) dependency, including Windows
|
|
11
|
+
* - Idempotent across pnpm-patched and previously patched installations
|
|
12
|
+
* - Never fails installation; mismatches only emit a notice and exit 0
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
|
-
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync, realpathSync } from "node:fs";
|
|
15
16
|
import { join, dirname } from "node:path";
|
|
16
17
|
import { fileURLToPath } from "node:url";
|
|
17
18
|
import { createRequire } from "node:module";
|
|
18
19
|
|
|
19
20
|
const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
20
21
|
|
|
21
|
-
// npm
|
|
22
|
-
//
|
|
23
|
-
function
|
|
22
|
+
// npm may hoist dependencies above the package, especially for global installs.
|
|
23
|
+
// Resolve packages through Node first, then walk upward as a fallback.
|
|
24
|
+
function addPackageDir(found, candidate) {
|
|
25
|
+
if (!existsSync(join(candidate, "package.json"))) return;
|
|
26
|
+
try {
|
|
27
|
+
found.add(realpathSync(candidate));
|
|
28
|
+
} catch {
|
|
29
|
+
found.add(candidate);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function findPackageDirs(packageName) {
|
|
34
|
+
const found = new Set();
|
|
24
35
|
try {
|
|
25
36
|
const req = createRequire(join(pkgRoot, "package.json"));
|
|
26
|
-
|
|
37
|
+
addPackageDir(found, dirname(req.resolve(`${packageName}/package.json`)));
|
|
27
38
|
} catch {
|
|
28
|
-
//
|
|
29
|
-
let dir = pkgRoot;
|
|
30
|
-
while (true) {
|
|
31
|
-
const candidate = join(dir, "node_modules", "@earendil-works", "pi-coding-agent");
|
|
32
|
-
if (existsSync(candidate)) return candidate;
|
|
33
|
-
const parent = dirname(dir);
|
|
34
|
-
if (parent === dir) return null;
|
|
35
|
-
dir = parent;
|
|
36
|
-
}
|
|
39
|
+
// The package may block package.json through exports; the upward scan below still finds it.
|
|
37
40
|
}
|
|
41
|
+
const [scope, name] = packageName.split("/");
|
|
42
|
+
let dir = pkgRoot;
|
|
43
|
+
while (true) {
|
|
44
|
+
addPackageDir(found, join(dir, "node_modules", scope, name));
|
|
45
|
+
const parent = dirname(dir);
|
|
46
|
+
if (parent === dir) break;
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
return [...found];
|
|
38
50
|
}
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
function findTuiDirs() {
|
|
53
|
+
const found = new Set(findPackageDirs("@earendil-works/pi-tui"));
|
|
54
|
+
for (const codingAgentDir of findPackageDirs("@earendil-works/pi-coding-agent")) {
|
|
55
|
+
addPackageDir(found, join(codingAgentDir, "node_modules", "@earendil-works", "pi-tui"));
|
|
56
|
+
}
|
|
57
|
+
return [...found];
|
|
58
|
+
}
|
|
44
59
|
|
|
45
|
-
//
|
|
46
|
-
const
|
|
47
|
-
//
|
|
60
|
+
// Each tuple is [original, replacement], matching the pnpm coding-agent patch.
|
|
61
|
+
const COMPACT_UI_REPLACEMENTS = [
|
|
62
|
+
// Hidden thinking does not count as visible content.
|
|
48
63
|
[
|
|
49
64
|
'|| (c.type === "thinking" && c.thinking.trim())',
|
|
50
65
|
'|| (c.type === "thinking" && !this.hideThinkingBlock && c.thinking.trim())',
|
|
51
66
|
],
|
|
52
|
-
//
|
|
67
|
+
// Skip an empty collapsed-thinking label instead of rendering an ANSI-only row.
|
|
53
68
|
[
|
|
54
69
|
'this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", this.hiddenThinkingLabel)), this.outputPad, 0));',
|
|
55
70
|
'if (this.hiddenThinkingLabel !== "") { this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", this.hiddenThinkingLabel)), this.outputPad, 0)); }',
|
|
56
71
|
],
|
|
57
|
-
//
|
|
72
|
+
// Do not add spacing after a fully hidden thinking block.
|
|
58
73
|
[
|
|
59
74
|
"if (hasVisibleContentAfter) {",
|
|
60
75
|
'if (hasVisibleContentAfter && !(this.hideThinkingBlock && this.hiddenThinkingLabel === "")) {',
|
|
61
76
|
],
|
|
62
77
|
];
|
|
63
78
|
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
function patchCompactUi() {
|
|
80
|
+
const piDirs = findPackageDirs("@earendil-works/pi-coding-agent");
|
|
81
|
+
if (piDirs.length === 0) {
|
|
66
82
|
console.log("[u1s1] 提示: 未找到 @earendil-works/pi-coding-agent,精简 UI 空行补丁未应用(不影响使用)");
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
let text;
|
|
70
|
-
try {
|
|
71
|
-
text = readFileSync(target, "utf8");
|
|
72
|
-
} catch {
|
|
73
|
-
// 找不到 pi 就静默退出(不该发生:postinstall 在依赖装完后跑)
|
|
74
|
-
process.exit(0);
|
|
83
|
+
return;
|
|
75
84
|
}
|
|
85
|
+
for (const piDir of piDirs) {
|
|
86
|
+
const target = join(piDir, "dist", "modes", "interactive", "components", "assistant-message.js");
|
|
87
|
+
try {
|
|
88
|
+
let text = readFileSync(target, "utf8");
|
|
89
|
+
if (text.includes("!this.hideThinkingBlock && c.thinking.trim()")) continue;
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
91
|
+
let applied = 0;
|
|
92
|
+
for (const [from, to] of COMPACT_UI_REPLACEMENTS) {
|
|
93
|
+
if (text.includes(to)) {
|
|
94
|
+
applied++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!text.includes(from)) continue;
|
|
98
|
+
text = text.split(from).join(to);
|
|
99
|
+
applied++;
|
|
100
|
+
}
|
|
81
101
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
102
|
+
if (applied < COMPACT_UI_REPLACEMENTS.length) {
|
|
103
|
+
console.log(
|
|
104
|
+
`[u1s1] 提示: pi 版本可能已更新,精简 UI 空行补丁只应用了 ${applied}/${COMPACT_UI_REPLACEMENTS.length} 处(不影响使用)`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
writeFileSync(target, text);
|
|
108
|
+
} catch {
|
|
86
109
|
continue;
|
|
87
110
|
}
|
|
88
|
-
if (!text.includes(from)) continue;
|
|
89
|
-
text = text.split(from).join(to);
|
|
90
|
-
applied++;
|
|
91
111
|
}
|
|
112
|
+
}
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
114
|
+
function patchMainScreenAutowrap() {
|
|
115
|
+
const tuiDirs = findTuiDirs();
|
|
116
|
+
if (tuiDirs.length === 0) {
|
|
117
|
+
console.log("[u1s1] 提示: 未找到 @earendil-works/pi-tui,Windows 重绘补丁未应用(不影响安装)");
|
|
118
|
+
return;
|
|
97
119
|
}
|
|
120
|
+
for (const tuiDir of tuiDirs) {
|
|
121
|
+
const target = join(tuiDir, "dist", "tui-main-screen.js");
|
|
122
|
+
try {
|
|
123
|
+
let text = readFileSync(target, "utf8");
|
|
124
|
+
if (text.includes('const DISABLE_AUTOWRAP = "\\x1b[?7l";')) continue;
|
|
125
|
+
|
|
126
|
+
const rules = [
|
|
127
|
+
{
|
|
128
|
+
from: 'const KITTY_SEQUENCE_PREFIX = "\\x1b_G";',
|
|
129
|
+
to: `const KITTY_SEQUENCE_PREFIX = "\\x1b_G";
|
|
130
|
+
// Windows ConPTY eagerly wraps full-width lines, which desynchronizes cursor-row tracking.
|
|
131
|
+
const DISABLE_AUTOWRAP = "\\x1b[?7l";
|
|
132
|
+
const ENABLE_AUTOWRAP = "\\x1b[?7h";`,
|
|
133
|
+
expected: 1,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
from: 'let buffer = "\\x1b[?2026h";',
|
|
137
|
+
to: 'let buffer = "\\x1b[?2026h" + DISABLE_AUTOWRAP;',
|
|
138
|
+
expected: 3,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
from: 'buffer += "\\x1b[?2026l";',
|
|
142
|
+
to: 'buffer += ENABLE_AUTOWRAP + "\\x1b[?2026l";',
|
|
143
|
+
expected: 3,
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
let applied = 0;
|
|
147
|
+
let expected = 0;
|
|
148
|
+
for (const rule of rules) {
|
|
149
|
+
expected += rule.expected;
|
|
150
|
+
const occurrences = text.split(rule.from).length - 1;
|
|
151
|
+
if (occurrences !== rule.expected) continue;
|
|
152
|
+
text = text.split(rule.from).join(rule.to);
|
|
153
|
+
applied += occurrences;
|
|
154
|
+
}
|
|
98
155
|
|
|
99
|
-
|
|
156
|
+
if (applied !== expected) {
|
|
157
|
+
console.log(`[u1s1] 提示: pi-tui 版本可能已更新,Windows 重绘补丁只应用了 ${applied}/${expected} 处(不影响安装)`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
writeFileSync(target, text);
|
|
161
|
+
} catch {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
patchCompactUi();
|
|
169
|
+
patchMainScreenAutowrap();
|
|
100
170
|
} catch (err) {
|
|
101
|
-
console.log(`[u1s1] 提示:
|
|
171
|
+
console.log(`[u1s1] 提示: pi 运行时补丁未生效(${err?.message ?? err}),不影响安装`);
|
|
102
172
|
}
|
|
103
173
|
process.exit(0);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 运行:node scripts/render-regression.mjs(prepublishOnly 会自动跑)
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { Container, Spacer, Text } from "@earendil-works/pi-tui";
|
|
15
|
+
import { Container, Spacer, Text, TuiMainScreen } from "@earendil-works/pi-tui";
|
|
16
16
|
import { existsSync } from "node:fs";
|
|
17
17
|
import { join, dirname } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
@@ -48,6 +48,12 @@ function check(name, actual, expected) {
|
|
|
48
48
|
console.log(`${ok ? "✓" : "✗"} ${name}: ${actual} 行(期望 ${expected})`);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
function checkTrue(name, actual) {
|
|
52
|
+
const ok = actual === true;
|
|
53
|
+
if (!ok) failed++;
|
|
54
|
+
console.log(`${ok ? "✓" : "✗"} ${name}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
// 与 u1s1-compact-ui 扩展一致的隐藏渲染器
|
|
52
58
|
function hiddenToolDef(orig) {
|
|
53
59
|
return {
|
|
@@ -123,9 +129,57 @@ function mkMsg(thinking, text) {
|
|
|
123
129
|
}
|
|
124
130
|
check("完整一轮对话", chat3.render(80).length, 6);
|
|
125
131
|
|
|
132
|
+
// ---- 4. Regular-screen frames guard full-width lines against ConPTY eager autowrap ----
|
|
133
|
+
class RecordingTerminal {
|
|
134
|
+
columns = 40;
|
|
135
|
+
rows = 10;
|
|
136
|
+
kittyProtocolActive = false;
|
|
137
|
+
writes = [];
|
|
138
|
+
start() {}
|
|
139
|
+
stop() {}
|
|
140
|
+
async drainInput() {}
|
|
141
|
+
write(data) {
|
|
142
|
+
this.writes.push(data);
|
|
143
|
+
}
|
|
144
|
+
moveBy() {}
|
|
145
|
+
hideCursor() {}
|
|
146
|
+
showCursor() {}
|
|
147
|
+
clearLine() {}
|
|
148
|
+
clearFromCursor() {}
|
|
149
|
+
clearScreen() {}
|
|
150
|
+
setTitle() {}
|
|
151
|
+
setProgress() {}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const terminal = new RecordingTerminal();
|
|
155
|
+
const tui = new TuiMainScreen(terminal);
|
|
156
|
+
const fullWidthComponent = {
|
|
157
|
+
lines: ["─".repeat(40), "editor line", "─".repeat(40)],
|
|
158
|
+
render() {
|
|
159
|
+
return this.lines;
|
|
160
|
+
},
|
|
161
|
+
invalidate() {},
|
|
162
|
+
};
|
|
163
|
+
tui.addChild(fullWidthComponent);
|
|
164
|
+
tui.renderNow();
|
|
165
|
+
const initialFrame = terminal.writes.join("");
|
|
166
|
+
checkTrue("Windows 首次绘制关闭并恢复 autowrap", initialFrame.includes("\x1b[?2026h\x1b[?7l") && initialFrame.includes("\x1b[?7h\x1b[?2026l"));
|
|
167
|
+
|
|
168
|
+
terminal.writes = [];
|
|
169
|
+
fullWidthComponent.lines[1] = "editor line changed";
|
|
170
|
+
tui.renderNow();
|
|
171
|
+
const incrementalFrame = terminal.writes.join("");
|
|
172
|
+
checkTrue("Windows 增量绘制关闭并恢复 autowrap", incrementalFrame.includes("\x1b[?2026h\x1b[?7l") && incrementalFrame.includes("\x1b[?7h\x1b[?2026l"));
|
|
173
|
+
|
|
174
|
+
terminal.writes = [];
|
|
175
|
+
fullWidthComponent.lines = fullWidthComponent.lines.slice(0, 2);
|
|
176
|
+
tui.renderNow();
|
|
177
|
+
const deletionFrame = terminal.writes.join("");
|
|
178
|
+
checkTrue("Windows 删除行绘制关闭并恢复 autowrap", deletionFrame.includes("\x1b[?2026h\x1b[?7l") && deletionFrame.includes("\x1b[?7h\x1b[?2026l"));
|
|
179
|
+
|
|
126
180
|
if (failed > 0) {
|
|
127
181
|
console.error(`\n渲染回归测试失败:${failed} 项。多半是 pi 升级后补丁没跟上,或扩展渲染被改动。`);
|
|
128
|
-
console.error("对照 patches/@earendil-works__pi-
|
|
182
|
+
console.error("对照 patches/@earendil-works__pi-*.patch、patch-pi.js 和 agent-setup.ts 里的 writeCompactUiExtension 检查。");
|
|
129
183
|
process.exit(1);
|
|
130
184
|
}
|
|
131
185
|
console.log("\n渲染回归测试全部通过 ✓");
|