@bachi/pi-coder 1.0.0
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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/config/AGENTS.md +100 -0
- package/config/pi-statusline.json +140 -0
- package/config/settings.json +38 -0
- package/config/web-search.json +5 -0
- package/docs/README.md +14 -0
- package/docs/configuration.md +123 -0
- package/docs/development.md +177 -0
- package/docs/extensions.md +292 -0
- package/docs/handbook.zh.md +432 -0
- package/docs/installation.md +124 -0
- package/docs/themes.md +107 -0
- package/extensions/ask-user-question/answers.test.ts +104 -0
- package/extensions/ask-user-question/answers.ts +72 -0
- package/extensions/ask-user-question/dialog.test.ts +180 -0
- package/extensions/ask-user-question/dialog.ts +102 -0
- package/extensions/ask-user-question/index.ts +253 -0
- package/extensions/ask-user-question/model.test.ts +275 -0
- package/extensions/ask-user-question/model.ts +259 -0
- package/extensions/ask-user-question/schema.ts +49 -0
- package/extensions/ask-user-question/types.ts +86 -0
- package/extensions/ask-user-question/validate.test.ts +183 -0
- package/extensions/ask-user-question/validate.ts +110 -0
- package/extensions/ask-user-question/view.ts +262 -0
- package/extensions/auto-default-model/default-model.test.ts +268 -0
- package/extensions/auto-default-model/index.ts +87 -0
- package/extensions/bash-command-collapse.ts +1476 -0
- package/extensions/below-editor-after-statusline.ts +118 -0
- package/extensions/clear-command.ts +29 -0
- package/extensions/cwd-statusline.ts +39 -0
- package/extensions/exit-command.ts +59 -0
- package/extensions/fenceless-code-block/index.test.ts +208 -0
- package/extensions/fenceless-code-block/index.ts +28 -0
- package/extensions/fenceless-code-block/render.test.ts +177 -0
- package/extensions/fenceless-code-block/render.ts +142 -0
- package/extensions/folder-history.ts +197 -0
- package/extensions/init-command.ts +163 -0
- package/extensions/prompt-editor/bash-prompt.test.ts +94 -0
- package/extensions/prompt-editor/bash-prompt.ts +59 -0
- package/extensions/prompt-editor/render.test.ts +283 -0
- package/extensions/prompt-editor.ts +212 -0
- package/extensions/read-path-collapse.ts +474 -0
- package/extensions/recap/index.test.ts +348 -0
- package/extensions/recap/index.ts +462 -0
- package/extensions/recap/subagents.test.ts +144 -0
- package/extensions/recap/subagents.ts +128 -0
- package/extensions/rewind/README.md +229 -0
- package/extensions/rewind/checkpoints.test.ts +560 -0
- package/extensions/rewind/checkpoints.ts +820 -0
- package/extensions/rewind/flow.test.ts +756 -0
- package/extensions/rewind/flow.ts +362 -0
- package/extensions/rewind/index.ts +400 -0
- package/extensions/rewind/picker.ts +135 -0
- package/extensions/rewind/viewport.test.ts +76 -0
- package/extensions/rewind/viewport.ts +48 -0
- package/extensions/simple-task/gap.test.ts +147 -0
- package/extensions/simple-task/gap.ts +122 -0
- package/extensions/simple-task/index.ts +439 -0
- package/extensions/simple-task/types.ts +53 -0
- package/extensions/simple-task/widget.ts +86 -0
- package/extensions/startup-logo/header-guard.test.ts +274 -0
- package/extensions/startup-logo/header-guard.ts +166 -0
- package/extensions/startup-logo/index.test.ts +305 -0
- package/extensions/startup-logo/index.ts +194 -0
- package/extensions/startup-logo/loaded-sections.test.ts +257 -0
- package/extensions/startup-logo/loaded-sections.ts +267 -0
- package/extensions/startup-logo/logo.test.ts +124 -0
- package/extensions/startup-logo/logo.ts +124 -0
- package/extensions/statusline/footer-guard.test.ts +273 -0
- package/extensions/statusline/footer-guard.ts +171 -0
- package/extensions/statusline/git.test.ts +174 -0
- package/extensions/statusline/git.ts +142 -0
- package/extensions/statusline/index.ts +294 -0
- package/extensions/statusline/line.test.ts +316 -0
- package/extensions/statusline/line.ts +201 -0
- package/extensions/subagent-log-guard/filter.test.ts +85 -0
- package/extensions/subagent-log-guard/filter.ts +32 -0
- package/extensions/subagent-log-guard/index.ts +112 -0
- package/extensions/theme-command.ts +263 -0
- package/extensions/thinking-collapse/window.test.ts +321 -0
- package/extensions/thinking-collapse/window.ts +354 -0
- package/extensions/thinking-collapse.ts +60 -0
- package/extensions/tool-diff/title-row.test.ts +254 -0
- package/extensions/tool-diff/title-row.ts +191 -0
- package/extensions/tool-diff.ts +1276 -0
- package/extensions/working-indicator/bash-spinner.test.ts +135 -0
- package/extensions/working-indicator/bash-spinner.ts +114 -0
- package/extensions/working-indicator/index.test.ts +579 -0
- package/extensions/working-indicator/index.ts +940 -0
- package/extensions/working-indicator/spinner-frames.test.ts +219 -0
- package/extensions/working-indicator/spinner-frames.ts +156 -0
- package/extensions/working-indicator/summary-request.test.ts +195 -0
- package/extensions/working-indicator/summary-request.ts +207 -0
- package/extensions/working-indicator/working-summary.test.ts +499 -0
- package/extensions/working-indicator/working-summary.ts +375 -0
- package/package.json +71 -0
- package/themes/ayu.json +97 -0
- package/themes/catppuccin.json +103 -0
- package/themes/summer-night.json +87 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* summary-request.ts — 「提示词太长时请模型压成一句话」的纯逻辑:触发决策、请求提示词、回复清洗。
|
|
3
|
+
*
|
|
4
|
+
* 背景见 `working-summary.ts` 头注释:spinner 行尾那一格只能放几十列,长提示词按可见宽度
|
|
5
|
+
* 截断后只剩开头几个字(「帮我看看…」),信息量几乎为零。所以 `working-indicator.ts` 在
|
|
6
|
+
* `input` 事件里除了压平/缓存提示词,还会**异步**发一次模型请求,让它用一句话说清这段
|
|
7
|
+
* 提示词要干什么。请求和主回合并行,互不阻塞;回来了就换上摘要,没回来/失败就一直显示
|
|
8
|
+
* 截断后的原文 —— 摘要永远是增强。
|
|
9
|
+
*
|
|
10
|
+
* ## 触发条件(`planSummaryRequest`)
|
|
11
|
+
*
|
|
12
|
+
* 只有当原文**明显**放不下时才值得花一次请求。三档:
|
|
13
|
+
*
|
|
14
|
+
* 原文宽度 ≤ 可用宽度 → 整段放得下,`layoutPromptSummary` 全量展示,不请求
|
|
15
|
+
* 可用宽度 < 原文 ≤ 可用宽度×倍数 → 截断补 `…` 就够(只丢开头一小截),不请求
|
|
16
|
+
* 原文 > 可用宽度×倍数 → 请求摘要(这就是「超过可显示区域**太多**」的「太多」)
|
|
17
|
+
*
|
|
18
|
+
* 倍数是 `triggerRatio`(`PI_WORKING_SUMMARY_TRIGGER`,默认 1.2):默认口径是「稍微放不下就
|
|
19
|
+
* 压」—— 截断会把近两成原文丢在那一格之外时才值得花这次请求;调到 2 就是「丢掉一半以上才
|
|
20
|
+
* 压」,1 就是「只要放不下就压」。宽度一律是**可见列数**(调用方用 pi-tui 的 `visibleWidth`
|
|
21
|
+
* 量好传进来),不是字符数 —— 一个汉字占 2 列,用字符数判断会把中文提示词全判成「还好」。
|
|
22
|
+
*
|
|
23
|
+
* ## 目标长度(`targetWidth`)
|
|
24
|
+
*
|
|
25
|
+
* 可用宽度(`availableSummaryTextWidth`,与渲染共用同一份数学)乘 `TARGET_WIDTH_RATIO`
|
|
26
|
+
* 得到要求模型输出的列数,并告诉它口径(汉字 2 列 / ASCII 1 列)。留出那一成余量是给
|
|
27
|
+
* 「模型数不准」的:宁可短一点、整句完整显示,也不要卡在边界上被切掉半句。可用宽度太窄
|
|
28
|
+
* (`MIN_REQUEST_BUDGET` 列以下)时干脆不请求 —— 一句话压到十列以内已经没有信息量,
|
|
29
|
+
* 截断原文和它一样难读。
|
|
30
|
+
*
|
|
31
|
+
* **只请求一次(调用方保证,不是这里的逻辑)**:模型回来的摘要超长也**不重试**,由
|
|
32
|
+
* `layoutPromptSummary` 按可见宽度截断补 `…`。重试会让这一格的内容来回跳,而且第二次
|
|
33
|
+
* 大概率还是长 —— 需求原文即「不要再第二次请求了,正常做截断处理就好」。
|
|
34
|
+
*
|
|
35
|
+
* ## 清洗(`cleanSummaryText`)
|
|
36
|
+
*
|
|
37
|
+
* 模型输出是不可信文本:可能多给几行、加个「摘要:」标签、整体包一层引号或 `**`,也可能
|
|
38
|
+
* 跑飞写成几百字。清洗只做展示层面的最小加工:
|
|
39
|
+
*
|
|
40
|
+
* - **只取第一行有效内容**。模型偶尔先写一句「摘要:」再换行写正文,所以逐行清洗、跳过
|
|
41
|
+
* 清洗后为空的行(标签行、代码围栏标记行),取第一行非空的结果。刻意不是整段拼接 ——
|
|
42
|
+
* 模型多给一段解释时,拼接会把解说词也顶进那一格里。
|
|
43
|
+
* - 逐行过 `flattenPrompt`(复用压平那套:剥 ANSI / 零宽字符、折叠空白、剥列表符号与
|
|
44
|
+
* 标题标记),所以模型加 `- ` 前缀或引号都不会带进显示。
|
|
45
|
+
* - 去掉整体包裹的引号(ASCII 与中文成对符号)与 `**粗体**` 包裹,去掉「摘要:」这类
|
|
46
|
+
* 标签前缀。最多迭代三轮,`**"摘要:xx"**` 这种叠着的装饰也能剥干净。
|
|
47
|
+
* - 字符数硬上限 `MAX_SUMMARY_TEXT_CHARS`(按码点切,不切半个代理对)。显示层还会按
|
|
48
|
+
* 可见宽度截一次,这里只是防「模型返回十万字」把内存和宽度计算拖着走。
|
|
49
|
+
*
|
|
50
|
+
* 本文件不 import pi / pi-tui(宽度是调用方量好的列数),所以 `node --test` 直跑
|
|
51
|
+
* (用例见 `summary-request.test.ts`)。
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
import { flattenPrompt } from "./working-summary.ts";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 触发倍数:原文宽度超过可用宽度的这个倍数才值得请求摘要。
|
|
58
|
+
* 1.2 意味着「稍微放不下就压」—— 截断会丢掉近两成原文时才值得花这次请求;
|
|
59
|
+
* 2 是「丢掉一半以上才压」,1 是「只要放不下就压」。
|
|
60
|
+
*/
|
|
61
|
+
export const DEFAULT_TRIGGER_RATIO = 1.2;
|
|
62
|
+
|
|
63
|
+
/** 目标宽度占可用宽度的比例:留一成余量,模型略微超一点也不会被截掉半句。 */
|
|
64
|
+
export const TARGET_WIDTH_RATIO = 0.9;
|
|
65
|
+
|
|
66
|
+
/** 可用宽度低于这个列数就不值得请求:一句话压到十列以内没有信息量,跟截断原文一样难读。 */
|
|
67
|
+
export const MIN_REQUEST_BUDGET = 10;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 喂给模型的提示词上限(字符)。压平后的原文最长 `MAX_PROMPT_SCAN_CHARS`(4000),
|
|
71
|
+
* 再截一道:模型只需要开头那段的语义,后面的细节对「一句话说清干什么」没有增量,
|
|
72
|
+
* 而请求体越大越慢。
|
|
73
|
+
*/
|
|
74
|
+
export const MAX_REQUEST_INPUT_CHARS = 2000;
|
|
75
|
+
|
|
76
|
+
/** 清洗后摘要的字符数上限(按码点计)。显示层另有可见宽度截断,这里只防模型跑飞。 */
|
|
77
|
+
export const MAX_SUMMARY_TEXT_CHARS = 200;
|
|
78
|
+
|
|
79
|
+
export interface SummaryRequestPlan {
|
|
80
|
+
/** 值不值得为这段提示词发一次模型请求。 */
|
|
81
|
+
needed: boolean;
|
|
82
|
+
/** 要求模型输出的目标宽度(可见列数);`needed` 为 false 时无意义。 */
|
|
83
|
+
targetWidth: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface SummaryRequestPlanOptions {
|
|
87
|
+
/** 压平后的提示词宽度(可见列数,调用方用 `visibleWidth` 量)。 */
|
|
88
|
+
promptWidth: number;
|
|
89
|
+
/** 摘要正文的可用宽度(可见列数,来自 `availableSummaryTextWidth`)。 */
|
|
90
|
+
budgetWidth: number;
|
|
91
|
+
/** 触发倍数,默认 `DEFAULT_TRIGGER_RATIO`。 */
|
|
92
|
+
triggerRatio?: number;
|
|
93
|
+
/** 可用宽度下限,默认 `MIN_REQUEST_BUDGET`。 */
|
|
94
|
+
minBudget?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 决定「要不要请求」与「要求模型写多长」。纯函数:传进来的都是量好的列数,不做任何测量。
|
|
99
|
+
* 条件用严格大于(`promptWidth > budget * ratio`):正好等于阈值时不请求,与「放得下就不
|
|
100
|
+
* 请求」的边界保持同一套开闭方向。
|
|
101
|
+
*/
|
|
102
|
+
export function planSummaryRequest(opts: SummaryRequestPlanOptions): SummaryRequestPlan {
|
|
103
|
+
const budget = Math.floor(opts.budgetWidth);
|
|
104
|
+
const minBudget = opts.minBudget ?? MIN_REQUEST_BUDGET;
|
|
105
|
+
const triggerRatio = opts.triggerRatio ?? DEFAULT_TRIGGER_RATIO;
|
|
106
|
+
// 目标宽度只是「可用宽度的一成余量版」:`budget < minBudget` 的窄格子已经被下面挡掉,
|
|
107
|
+
// 所以不需要再单独设一个目标下限(两个阈值只会有一个真正生效)。
|
|
108
|
+
const targetWidth = Math.max(1, Math.floor(budget * TARGET_WIDTH_RATIO));
|
|
109
|
+
if (budget < minBudget) return { needed: false, targetWidth };
|
|
110
|
+
return { needed: opts.promptWidth > budget * triggerRatio, targetWidth };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 按码点截断(不切半个代理对):超长时末尾补 `…` 表示「被截了」。 */
|
|
114
|
+
function clipToPoints(text: string, maxPoints: number): string {
|
|
115
|
+
const points = [...text];
|
|
116
|
+
if (points.length <= maxPoints) return text;
|
|
117
|
+
return `${points.slice(0, maxPoints).join("")}…`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 组装请求提示词(单条 user 消息的文本)。要求模型:
|
|
122
|
+
* 1. 一行、无换行、无引号、无 markdown、无「摘要:」标签、无解释;
|
|
123
|
+
* 2. 不超过 `targetWidth` 个显示列(并给出汉字 2 列 / ASCII 1 列的口径)、宁短勿长;
|
|
124
|
+
* 3. 保留「做什么 + 对象」,丢掉寒暄 / 背景 / 示例 / 重复;
|
|
125
|
+
* 4. 用与原文相同的语言。
|
|
126
|
+
*
|
|
127
|
+
* 第 4 条不指定中文:提示词是英文时给出中文摘要反而别扭,让模型跟随原文更自然。
|
|
128
|
+
*/
|
|
129
|
+
export function buildSummaryRequestPrompt(prompt: string, targetWidth: number): string {
|
|
130
|
+
const input = clipToPoints(prompt, MAX_REQUEST_INPUT_CHARS);
|
|
131
|
+
return [
|
|
132
|
+
"You label user prompts in a terminal status bar: say in one short line what the user is asking for.",
|
|
133
|
+
"",
|
|
134
|
+
"Rules:",
|
|
135
|
+
`- Output a single line, at most about ${targetWidth} display columns wide. A CJK/full-width character counts as 2 columns, an ASCII character as 1. Stay clearly under the limit rather than at it.`,
|
|
136
|
+
'- No line breaks, no quotes, no markdown, no label like "Summary:", no explanation — only that line.',
|
|
137
|
+
"- Keep the action and its main object. Drop greetings, background, examples and repeated detail.",
|
|
138
|
+
"- Write in the same language as the input.",
|
|
139
|
+
"",
|
|
140
|
+
"Input:",
|
|
141
|
+
input,
|
|
142
|
+
].join("\n");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** 「摘要」这类标签词本身。 */
|
|
146
|
+
const LABEL_TEXT = "(?:summary|prompt summary|recap|title|label|摘要|总结|概括|简述|一句话摘要)";
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 标签前缀:`摘要:` / `Summary:`,并容忍两种粗体包裹(`**摘要:**` 与 `**摘要**:`)——
|
|
150
|
+
* 分隔符后面那截粗体收尾也一并吃掉,否则会剩下一串 `**` 混进摘要正文。
|
|
151
|
+
*/
|
|
152
|
+
const LABEL_PREFIX_RE = new RegExp(
|
|
153
|
+
`^(?:\\*\\*|__)?${LABEL_TEXT}(?:\\*\\*|__)?\\s*[::]\\s*(?:\\*\\*|__)?\\s*`,
|
|
154
|
+
"i",
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
/** 整行只有标签本身(`摘要` / `**Summary**`):不是摘要内容,该行跳过。 */
|
|
158
|
+
const LABEL_ONLY_RE = new RegExp(`^(?:\\*\\*|__)?${LABEL_TEXT}(?:\\*\\*|__)?\\s*[::]?$`, "i");
|
|
159
|
+
|
|
160
|
+
/** 成对包裹的引号 / 书名号(ASCII 直引号与中文成对符号)。 */
|
|
161
|
+
const WRAPPING_QUOTES: ReadonlyArray<readonly [string, string]> = [
|
|
162
|
+
["\"", "\""],
|
|
163
|
+
["'", "'"],
|
|
164
|
+
["“", "”"],
|
|
165
|
+
["‘", "’"],
|
|
166
|
+
["「", "」"],
|
|
167
|
+
["『", "』"],
|
|
168
|
+
["《", "》"],
|
|
169
|
+
["`", "`"],
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
/** 剥一层装饰:标签前缀 → 整体包裹的引号 → 整体包裹的 `**`/`__`。 */
|
|
173
|
+
function stripDecoration(text: string): string {
|
|
174
|
+
let out = text.replace(LABEL_PREFIX_RE, "").trim();
|
|
175
|
+
for (const [open, close] of WRAPPING_QUOTES) {
|
|
176
|
+
if (out.length > open.length + close.length && out.startsWith(open) && out.endsWith(close)) {
|
|
177
|
+
const inner = out.slice(open.length, out.length - close.length).trim();
|
|
178
|
+
if (inner !== "") {
|
|
179
|
+
out = inner;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return out.replace(/^(\*\*|__)(.+)\1$/, "$2").trim();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 清洗模型回复(规则见文件头)。返回空串表示这次请求没有可用结果 —— 调用方按「不显示摘要」
|
|
189
|
+
* 处理(继续显示截断后的原文),不重试、不报错。
|
|
190
|
+
*/
|
|
191
|
+
export function cleanSummaryText(raw: string): string {
|
|
192
|
+
// 逐行取第一个「清洗后非空」的行:标签行(「摘要:」)、代码围栏标记行会被清洗成空串,
|
|
193
|
+
// 于是自然落到下一行;模型把正文分成了多行时只取第一行,后面的解说词不进来。
|
|
194
|
+
for (const line of raw.split(/\r\n|[\n\r\u0085\u000b\u000c\u2028\u2029]/)) {
|
|
195
|
+
let text = flattenPrompt(line);
|
|
196
|
+
if (text === "") continue;
|
|
197
|
+
// 装饰可能叠着(`**"摘要:xx"**`):迭代到不再变化为止,上限四轮兜底。
|
|
198
|
+
for (let round = 0; round < 4; round++) {
|
|
199
|
+
const next = stripDecoration(text);
|
|
200
|
+
if (next === text) break;
|
|
201
|
+
text = next;
|
|
202
|
+
}
|
|
203
|
+
if (text === "" || LABEL_ONLY_RE.test(text)) continue;
|
|
204
|
+
return clipToPoints(text, MAX_SUMMARY_TEXT_CHARS);
|
|
205
|
+
}
|
|
206
|
+
return "";
|
|
207
|
+
}
|