u1s1-cli 1.2.5 → 1.2.7
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/agent-setup.d.ts +7 -1
- package/dist/agent-setup.js +11 -9
- package/dist/embed.d.ts +1 -1
- package/dist/embed.js +1 -1
- package/dist/index.js +1 -0
- package/dist/web.d.ts +6 -0
- package/dist/web.js +33 -2
- package/package.json +1 -1
- package/scripts/patch-pi.js +97 -48
- package/scripts/render-regression.mjs +56 -2
package/dist/agent-setup.d.ts
CHANGED
|
@@ -89,4 +89,10 @@ export declare function ensureWorkflowPromptTemplate(): void;
|
|
|
89
89
|
export declare function scrubForeignProviderEnv(): void;
|
|
90
90
|
/** 把各端点密钥放进环境(TUI 直接 process.env;web 传给子进程)。 */
|
|
91
91
|
export declare function endpointKeyEnv(): Record<string, string>;
|
|
92
|
-
export
|
|
92
|
+
export interface ProviderModelsFileOptions {
|
|
93
|
+
/** Destination for the composed provider config. Defaults to the shared agent models file. */
|
|
94
|
+
outputPath?: string;
|
|
95
|
+
/** Existing config to preserve. Defaults to outputPath. */
|
|
96
|
+
sourcePath?: string;
|
|
97
|
+
}
|
|
98
|
+
export declare function ensureProviderModels(cfg: CliConfig, options?: ProviderModelsFileOptions): void;
|
package/dist/agent-setup.js
CHANGED
|
@@ -206,10 +206,10 @@ export function writeWebToolsExtension(cfg, features) {
|
|
|
206
206
|
mkdirSync(dir, { recursive: true });
|
|
207
207
|
const toolsUrl = new URL("./tools.js", import.meta.url).href;
|
|
208
208
|
const searchLine = features.webSearch
|
|
209
|
-
? ` pi.registerTool(tools.createSearchTool({ baseUrl
|
|
209
|
+
? ` pi.registerTool(tools.createSearchTool({ baseUrl, apiKey: process.env.U1S1_API_KEY }));\n`
|
|
210
210
|
: "";
|
|
211
211
|
const imageLine = features.imageGen
|
|
212
|
-
? ` pi.registerTool(tools.createImageTool({ baseUrl
|
|
212
|
+
? ` pi.registerTool(tools.createImageTool({ baseUrl, apiKey: process.env.U1S1_API_KEY }));\n`
|
|
213
213
|
: "";
|
|
214
214
|
// spawn_subagent / run_workflow 只给主会话注册;子 agent 环境里跳过,杜绝孙 agent 递归 spawn
|
|
215
215
|
const subagentBlock = ` if (process.env.U1S1_IN_SUBAGENT !== "1") {\n` +
|
|
@@ -227,9 +227,10 @@ export function writeWebToolsExtension(cfg, features) {
|
|
|
227
227
|
writeFileSync(join(dir, "u1s1-tools.js"), `// 由 u1s1 每次启动自动生成,请勿手改\n` +
|
|
228
228
|
`export default async function (pi) {\n` +
|
|
229
229
|
` if (process.env.U1S1_TOOLS_VIA_EXTENSION !== "1") return;\n` +
|
|
230
|
+
` const baseUrl = process.env.U1S1_SIGNING_PROXY_URL || ${JSON.stringify(cfg.baseUrl)};\n` +
|
|
230
231
|
` const tools = await import(${JSON.stringify(toolsUrl)});\n` +
|
|
231
232
|
searchLine +
|
|
232
|
-
` pi.registerTool(tools.createFetchTool({ baseUrl
|
|
233
|
+
` pi.registerTool(tools.createFetchTool({ baseUrl, apiKey: process.env.U1S1_API_KEY, renderFallback: ${features.webFetchRender} }));\n` +
|
|
233
234
|
imageLine +
|
|
234
235
|
subagentBlock +
|
|
235
236
|
`}\n`);
|
|
@@ -528,13 +529,14 @@ export function endpointKeyEnv() {
|
|
|
528
529
|
}
|
|
529
530
|
return env;
|
|
530
531
|
}
|
|
531
|
-
export function ensureProviderModels(cfg) {
|
|
532
|
-
|
|
533
|
-
const
|
|
532
|
+
export function ensureProviderModels(cfg, options = {}) {
|
|
533
|
+
const outputPath = options.outputPath ?? join(agentDir, "models.json");
|
|
534
|
+
const sourcePath = options.sourcePath ?? outputPath;
|
|
535
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
534
536
|
let root = {};
|
|
535
|
-
if (existsSync(
|
|
537
|
+
if (existsSync(sourcePath)) {
|
|
536
538
|
try {
|
|
537
|
-
root = JSON.parse(readFileSync(
|
|
539
|
+
root = JSON.parse(readFileSync(sourcePath, "utf8"));
|
|
538
540
|
}
|
|
539
541
|
catch {
|
|
540
542
|
root = {};
|
|
@@ -563,5 +565,5 @@ export function ensureProviderModels(cfg) {
|
|
|
563
565
|
providers[ep.id] = endpointProviderEntry(ep);
|
|
564
566
|
}
|
|
565
567
|
root["providers"] = providers;
|
|
566
|
-
writeFileSync(
|
|
568
|
+
writeFileSync(outputPath, JSON.stringify(root, null, 2) + "\n", { mode: 0o600 });
|
|
567
569
|
}
|
package/dist/embed.d.ts
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
export { agentDir, loadConfig, saveConfig, u1s1Dir, VERSION, type CliConfig, } from "./config.js";
|
|
7
7
|
export { fetchMe, fetchModels } from "./api.js";
|
|
8
8
|
export { apiOrigin, pollDeviceLogin, startDeviceLogin, type DeviceStart } from "./login.js";
|
|
9
|
-
export { prepareWebEnv } from "./web.js";
|
|
9
|
+
export { prepareWebEnv, refreshWebModels } from "./web.js";
|
|
10
10
|
export { applyWebUiBranding } from "./webui-brand.js";
|
|
11
11
|
export { DASHBOARD_URL } from "./brand.js";
|
package/dist/embed.js
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
export { agentDir, loadConfig, saveConfig, u1s1Dir, VERSION, } from "./config.js";
|
|
7
7
|
export { fetchMe, fetchModels } from "./api.js";
|
|
8
8
|
export { apiOrigin, pollDeviceLogin, startDeviceLogin } from "./login.js";
|
|
9
|
-
export { prepareWebEnv } from "./web.js";
|
|
9
|
+
export { prepareWebEnv, refreshWebModels } from "./web.js";
|
|
10
10
|
export { applyWebUiBranding } from "./webui-brand.js";
|
|
11
11
|
export { DASHBOARD_URL } from "./brand.js";
|
package/dist/index.js
CHANGED
|
@@ -212,6 +212,7 @@ async function runAgent(cfg, args) {
|
|
|
212
212
|
// must be set before pi reads them (getAgentDir() reads at call time, env at import is fine too)
|
|
213
213
|
process.env["PI_CODING_AGENT_DIR"] = agentDir;
|
|
214
214
|
process.env["U1S1_API_KEY"] = officialCfg.apiKey;
|
|
215
|
+
process.env["U1S1_SIGNING_PROXY_URL"] = officialCfg.baseUrl;
|
|
215
216
|
process.env["U1S1_TOOLS_VIA_EXTENSION"] = "1";
|
|
216
217
|
// 自定义端点的密钥走环境变量引用(models.json 里只有 $VAR,不落明文)
|
|
217
218
|
Object.assign(process.env, endpointKeyEnv());
|
package/dist/web.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { type CliConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Refresh the web/Desktop process-local model snapshot while preserving user
|
|
4
|
+
* providers from the shared agent config. The u1s1 provider always remains
|
|
5
|
+
* pinned to this process's signing proxy.
|
|
6
|
+
*/
|
|
7
|
+
export declare function refreshWebModels(): string | undefined;
|
|
2
8
|
/**
|
|
3
9
|
* Desktop App 启动 agent server 前的公共准备:品牌 prompt、模型列表、
|
|
4
10
|
* auth.json 凭据、联网工具扩展、默认模型,并返回子进程需要的环境变量。
|
package/dist/web.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { cleanupBrandThemes, endpointKeyEnv, ensureAuthCredential, ensureBrandPrompt, ensureWorkflowPromptTemplate, ensureDefaultSettings, ensureProviderModels, scrubForeignProviderEnv, writeAttributionExtension, writeWebToolsExtension, } from "./agent-setup.js";
|
|
4
5
|
import { agentDir, apiModelToDef, MODELS, resolvePreferredModel, setModelsFromApi, u1s1Dir, writeAgentDefaultModel, } from "./config.js";
|
|
@@ -6,6 +7,33 @@ import { ensureSearchTools } from "./search-tools.js";
|
|
|
6
7
|
import { ensureUsableShell } from "./shell-doctor.js";
|
|
7
8
|
import { fetchModels, loadCustomEndpoints } from "./api.js";
|
|
8
9
|
import { ensureSigningProxy } from "./device-auth.js";
|
|
10
|
+
let webModelsDir;
|
|
11
|
+
let webOfficialCfg;
|
|
12
|
+
function processModelsPath() {
|
|
13
|
+
if (!webModelsDir) {
|
|
14
|
+
webModelsDir = mkdtempSync(join(tmpdir(), "u1s1-web-"));
|
|
15
|
+
process.once("exit", () => {
|
|
16
|
+
if (webModelsDir)
|
|
17
|
+
rmSync(webModelsDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return join(webModelsDir, "models.json");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Refresh the web/Desktop process-local model snapshot while preserving user
|
|
24
|
+
* providers from the shared agent config. The u1s1 provider always remains
|
|
25
|
+
* pinned to this process's signing proxy.
|
|
26
|
+
*/
|
|
27
|
+
export function refreshWebModels() {
|
|
28
|
+
if (!webOfficialCfg)
|
|
29
|
+
return undefined;
|
|
30
|
+
const outputPath = processModelsPath();
|
|
31
|
+
ensureProviderModels(webOfficialCfg, {
|
|
32
|
+
outputPath,
|
|
33
|
+
sourcePath: join(agentDir, "models.json"),
|
|
34
|
+
});
|
|
35
|
+
return outputPath;
|
|
36
|
+
}
|
|
9
37
|
/**
|
|
10
38
|
* Desktop App 启动 agent server 前的公共准备:品牌 prompt、模型列表、
|
|
11
39
|
* auth.json 凭据、联网工具扩展、默认模型,并返回子进程需要的环境变量。
|
|
@@ -41,10 +69,11 @@ export async function prepareWebEnv(cfg) {
|
|
|
41
69
|
ensureDefaultSettings(MODELS);
|
|
42
70
|
const signing = await ensureSigningProxy(cfg, "desktop");
|
|
43
71
|
const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
|
|
72
|
+
webOfficialCfg = officialCfg;
|
|
73
|
+
const modelsPath = refreshWebModels();
|
|
44
74
|
ensureBrandPrompt(await shellReady);
|
|
45
75
|
// /workflow 提示词模板与 TUI 同源,Desktop App 也要有
|
|
46
76
|
ensureWorkflowPromptTemplate();
|
|
47
|
-
ensureProviderModels(officialCfg);
|
|
48
77
|
// pi-web-ui 靠 auth.json 判断「已配置」,否则网页会弹 pi 安装引导
|
|
49
78
|
ensureAuthCredential();
|
|
50
79
|
// 联网工具经 agentDir/extensions 投影,和 TUI 共用一份注册
|
|
@@ -67,6 +96,8 @@ export async function prepareWebEnv(cfg) {
|
|
|
67
96
|
return {
|
|
68
97
|
PI_CODING_AGENT_DIR: agentDir,
|
|
69
98
|
U1S1_API_KEY: officialCfg.apiKey,
|
|
99
|
+
U1S1_MODELS_PATH: modelsPath,
|
|
100
|
+
U1S1_SIGNING_PROXY_URL: officialCfg.baseUrl,
|
|
70
101
|
U1S1_TOOLS_VIA_EXTENSION: "1",
|
|
71
102
|
PI_WEB_DATA_DIR: dataDir,
|
|
72
103
|
// 自定义端点的密钥经环境变量传给 App 子进程(models.json 里只有 $VAR 引用)
|
package/package.json
CHANGED
package/scripts/patch-pi.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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
15
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
@@ -18,17 +19,17 @@ 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 findPackageDir(packageName) {
|
|
24
25
|
try {
|
|
25
26
|
const req = createRequire(join(pkgRoot, "package.json"));
|
|
26
|
-
return dirname(req.resolve(
|
|
27
|
+
return dirname(req.resolve(`${packageName}/package.json`));
|
|
27
28
|
} catch {
|
|
28
|
-
|
|
29
|
+
const [scope, name] = packageName.split("/");
|
|
29
30
|
let dir = pkgRoot;
|
|
30
31
|
while (true) {
|
|
31
|
-
const candidate = join(dir, "node_modules",
|
|
32
|
+
const candidate = join(dir, "node_modules", scope, name);
|
|
32
33
|
if (existsSync(candidate)) return candidate;
|
|
33
34
|
const parent = dirname(dir);
|
|
34
35
|
if (parent === dir) return null;
|
|
@@ -37,67 +38,115 @@ function findPiDir() {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
: null;
|
|
44
|
-
|
|
45
|
-
// 每条 [原文, 替换后];与 patches/@earendil-works__pi-coding-agent@0.84.2.patch 等效
|
|
46
|
-
const REPLACEMENTS = [
|
|
47
|
-
// 隐藏的思考块不算「可见内容」(消息开头不再加空行)
|
|
41
|
+
// Each tuple is [original, replacement], matching the pnpm coding-agent patch.
|
|
42
|
+
const COMPACT_UI_REPLACEMENTS = [
|
|
43
|
+
// Hidden thinking does not count as visible content.
|
|
48
44
|
[
|
|
49
45
|
'|| (c.type === "thinking" && c.thinking.trim())',
|
|
50
46
|
'|| (c.type === "thinking" && !this.hideThinkingBlock && c.thinking.trim())',
|
|
51
47
|
],
|
|
52
|
-
//
|
|
48
|
+
// Skip an empty collapsed-thinking label instead of rendering an ANSI-only row.
|
|
53
49
|
[
|
|
54
50
|
'this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", this.hiddenThinkingLabel)), this.outputPad, 0));',
|
|
55
51
|
'if (this.hiddenThinkingLabel !== "") { this.contentContainer.addChild(new Text(theme.italic(theme.fg("thinkingText", this.hiddenThinkingLabel)), this.outputPad, 0)); }',
|
|
56
52
|
],
|
|
57
|
-
//
|
|
53
|
+
// Do not add spacing after a fully hidden thinking block.
|
|
58
54
|
[
|
|
59
55
|
"if (hasVisibleContentAfter) {",
|
|
60
56
|
'if (hasVisibleContentAfter && !(this.hideThinkingBlock && this.hiddenThinkingLabel === "")) {',
|
|
61
57
|
],
|
|
62
58
|
];
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
function patchCompactUi() {
|
|
61
|
+
const piDir = findPackageDir("@earendil-works/pi-coding-agent");
|
|
62
|
+
const target = piDir
|
|
63
|
+
? join(piDir, "dist", "modes", "interactive", "components", "assistant-message.js")
|
|
64
|
+
: null;
|
|
65
65
|
if (!target) {
|
|
66
66
|
console.log("[u1s1] 提示: 未找到 @earendil-works/pi-coding-agent,精简 UI 空行补丁未应用(不影响使用)");
|
|
67
|
-
|
|
67
|
+
return;
|
|
68
68
|
}
|
|
69
|
-
let text;
|
|
70
69
|
try {
|
|
71
|
-
text = readFileSync(target, "utf8");
|
|
70
|
+
let text = readFileSync(target, "utf8");
|
|
71
|
+
if (text.includes("!this.hideThinkingBlock && c.thinking.trim()")) return;
|
|
72
|
+
|
|
73
|
+
let applied = 0;
|
|
74
|
+
for (const [from, to] of COMPACT_UI_REPLACEMENTS) {
|
|
75
|
+
if (text.includes(to)) {
|
|
76
|
+
applied++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!text.includes(from)) continue;
|
|
80
|
+
text = text.split(from).join(to);
|
|
81
|
+
applied++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (applied < COMPACT_UI_REPLACEMENTS.length) {
|
|
85
|
+
console.log(
|
|
86
|
+
`[u1s1] 提示: pi 版本可能已更新,精简 UI 空行补丁只应用了 ${applied}/${COMPACT_UI_REPLACEMENTS.length} 处(不影响使用)`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
writeFileSync(target, text);
|
|
72
90
|
} catch {
|
|
73
|
-
|
|
74
|
-
process.exit(0);
|
|
91
|
+
return;
|
|
75
92
|
}
|
|
93
|
+
}
|
|
76
94
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
function patchMainScreenAutowrap() {
|
|
96
|
+
const tuiDir = findPackageDir("@earendil-works/pi-tui");
|
|
97
|
+
const target = tuiDir ? join(tuiDir, "dist", "tui-main-screen.js") : null;
|
|
98
|
+
if (!target) {
|
|
99
|
+
console.log("[u1s1] 提示: 未找到 @earendil-works/pi-tui,Windows 重绘补丁未应用(不影响安装)");
|
|
100
|
+
return;
|
|
80
101
|
}
|
|
102
|
+
try {
|
|
103
|
+
let text = readFileSync(target, "utf8");
|
|
104
|
+
if (text.includes('const DISABLE_AUTOWRAP = "\\x1b[?7l";')) return;
|
|
81
105
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
106
|
+
const rules = [
|
|
107
|
+
{
|
|
108
|
+
from: 'const KITTY_SEQUENCE_PREFIX = "\\x1b_G";',
|
|
109
|
+
to: `const KITTY_SEQUENCE_PREFIX = "\\x1b_G";
|
|
110
|
+
// Windows ConPTY eagerly wraps full-width lines, which desynchronizes cursor-row tracking.
|
|
111
|
+
const DISABLE_AUTOWRAP = "\\x1b[?7l";
|
|
112
|
+
const ENABLE_AUTOWRAP = "\\x1b[?7h";`,
|
|
113
|
+
expected: 1,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
from: 'let buffer = "\\x1b[?2026h";',
|
|
117
|
+
to: 'let buffer = "\\x1b[?2026h" + DISABLE_AUTOWRAP;',
|
|
118
|
+
expected: 3,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
from: 'buffer += "\\x1b[?2026l";',
|
|
122
|
+
to: 'buffer += ENABLE_AUTOWRAP + "\\x1b[?2026l";',
|
|
123
|
+
expected: 3,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
let applied = 0;
|
|
127
|
+
let expected = 0;
|
|
128
|
+
for (const rule of rules) {
|
|
129
|
+
expected += rule.expected;
|
|
130
|
+
const occurrences = text.split(rule.from).length - 1;
|
|
131
|
+
if (occurrences !== rule.expected) continue;
|
|
132
|
+
text = text.split(rule.from).join(rule.to);
|
|
133
|
+
applied += occurrences;
|
|
87
134
|
}
|
|
88
|
-
if (!text.includes(from)) continue;
|
|
89
|
-
text = text.split(from).join(to);
|
|
90
|
-
applied++;
|
|
91
|
-
}
|
|
92
135
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
136
|
+
if (applied !== expected) {
|
|
137
|
+
console.log(`[u1s1] 提示: pi-tui 版本可能已更新,Windows 重绘补丁只应用了 ${applied}/${expected} 处(不影响安装)`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
writeFileSync(target, text);
|
|
141
|
+
} catch {
|
|
142
|
+
return;
|
|
97
143
|
}
|
|
144
|
+
}
|
|
98
145
|
|
|
99
|
-
|
|
146
|
+
try {
|
|
147
|
+
patchCompactUi();
|
|
148
|
+
patchMainScreenAutowrap();
|
|
100
149
|
} catch (err) {
|
|
101
|
-
console.log(`[u1s1] 提示:
|
|
150
|
+
console.log(`[u1s1] 提示: pi 运行时补丁未生效(${err?.message ?? err}),不影响安装`);
|
|
102
151
|
}
|
|
103
152
|
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渲染回归测试全部通过 ✓");
|