@sendevia/pi-ask-author 1.0.6 → 1.1.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/README.md CHANGED
@@ -4,28 +4,30 @@
4
4
  [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
6
6
 
7
- 专为作家、轻小说创作者与 AI 协同写作设计的 Pi 终端交互式提问请示扩展。
7
+ 专为小说/文字创作者与 AI 协同写作设计的 Pi 终端交互式提问工具。
8
8
 
9
- 通过在问题中提供带有 Markdown 渲染的正文草稿预览、剧情因果推演与灵活的批注系统,让创作分歧在同一次询问内得到确认。
9
+ 通过在问题中提供正文草稿预览、剧情因果推演与灵活的批注系统,让创作分歧在同一次询问内得到确认。
10
10
 
11
11
  ## 核心特性
12
12
 
13
- - **双栏响应式 TUI 交互**
13
+ - **自然的 TUI 用户交互**
14
14
  - **左栏选项决策**:直观展示单选/多选状态、完成度指示徽标、选项标签与专属批注标识。
15
15
  - **右栏实时预览**:光标移动时即时联动渲染当前选项的**剧情因果推导**与 **Markdown 正文草稿预览**。
16
- - **自适应视口**:终端宽度不足时启用双栏,窄屏下改为单栏内联展开与折叠。
17
- - **小说级富文本与草稿高亮**
18
- - 内置紧凑轻量的终端 Markdown 渲染与小说语法高亮,完整支持粗体、斜体、删除线、行内代码、引用区块。
19
- - 对中文小说台词(引号内对话高亮)与分段排版做了专门处理,作者可以在终端内直接检视行文节奏与语感。
20
- - **题目与选项两级批注体系**
16
+ - **自适应视口**:终端宽度足够且选项含富内容时启用双栏;窄屏改为单栏内联展开与折叠。
17
+ - **富文本与草稿高亮**
18
+ - **Markdown 语法**:内置轻量的 Markdown 渲染与语法高亮,完整支持粗体、斜体、删除线、行内代码、引用区块。
19
+ - **台词高亮**:对中文小说台词(引号内对话高亮)与分段排版做了专门处理,作者可以在终端内直接检视行文节奏与语感。
20
+ - **题目与选项均可批注**
21
21
  - **题目级补充说明**:对该题整体提出全局创作指令或氛围补充。
22
22
  - **选项专属补充说明**:光标位于选项行时按 `n` 即可为特定选项追加细化批注(**未勾选的选项同样可以填写补充说明**,便于模型借鉴未选方案中的可取之处)。
23
- - **单题平铺与批量问卷双模式**
23
+ - **灵活的问卷模式**
24
24
  - **单题平铺模式**:快速发起单点决策(如重要剧情分歧、结局走向)。
25
- - **多题批量问卷模式**:支持在单次弹窗内汇集多个关联决策(如脱离路线 + 战利品搜刮 + 下章基调),配有顶部 Tab 标签栏与完成度总览页(Summary Tab),作者在一处完成全部确认。
25
+ - **多题批量问卷模式**:支持在单次弹窗内汇集多个关联决策。
26
+ - **自适应题目与选项**:支持引用更早题目的选项或作答存在性,按作者的实际选择逐步展开分支。
27
+ - **多语言支持**
26
28
  - **高性能与极简依赖**
27
- - 严格遵守 Pi 官方扩展生态规范,不引入其他第三方依赖。
28
- - 资源回收:生命周期严格绑定 AbortSignal,定时器与事件监听幂等清理,渲染缓存按尺寸与状态键失效。
29
+ - **遵守开发规范**:严格遵守 Pi 官方扩展生态规范,不引入其他第三方依赖。
30
+ - **性能优秀**:严格的性能优化,能够处理与展示超长的内容。
29
31
 
30
32
  ## 工具预览
31
33
 
@@ -73,8 +75,8 @@ pi -e ./path/to/pi-ask-author
73
75
  | `N` | 直接编辑题目整体补充说明 |
74
76
  | `x` / `Delete` | 清空当前聚焦选项的补充说明,其次清空题目补充说明 |
75
77
  | `a` | 多选模式下全选或清空已选项 |
76
- | `PgUp` / `[` / `Alt+↑` | 作答页向上滚动预览区;总览页向上滚动 |
77
- | `PgDn` / `]` / `Alt+↓` | 作答页向下滚动预览区;总览页向下滚动 |
78
+ | `[` / `Alt+↑` | 作答页向上滚动预览区;总览页向上滚动 |
79
+ | `]` / `Alt+↓` | 作答页向下滚动预览区;总览页向下滚动 |
78
80
  | `Esc` / `Ctrl+C` | 取消本次请示(编辑器中首次按下需再次确认丢弃修改) |
79
81
 
80
82
  ## 开发
@@ -3,22 +3,25 @@
3
3
  * @description `ask_author` 交互式 TUI 主组件(双栏问卷视图与答卷总览)
4
4
  *
5
5
  * 核心架构特性:
6
- * - 宽屏双栏 / 窄屏单栏自适应:`renderWidth >= LAYOUT_CONFIG.splitViewMinWidth` 且题目含富内容时走双栏,否则降级单栏紧凑列表;预览与总览视口行数随终端高度收缩
7
- * - 多级缓存失效:整屏(尺寸键 + 状态突变清空)、总览 / Tab 栏 / 左栏 / 菜单项(`revision` 键)、预览池(内容键);视口切片 O(1);`render()` 无 I/O,Markdown 渲染仅发生在派生缓存未命中时
6
+ * - 本组件只负责交互状态机、按键路由与整屏帧缓存;题目页、总览页与编辑器页的排版分别由 `view-question` / `view-summary` / `view-editor` 构建
7
+ * - 宽屏双栏 / 窄屏单栏自适应由 `QuestionView` 判定;预览与总览视口行数随终端高度收缩
8
+ * - 多级缓存失效:整屏(尺寸键 + 状态突变清空)、Tab 栏 / 菜单项(`revision` 键)、视图层内容缓存(由 `touchDerived` 统一清空)
8
9
  * - 逐题独立光标记忆与 `1~9` 跳转题目;内置编辑器透传 Focusable 以定位 IME 候选框
10
+ * - 无输入框页面把输入法全角字符收敛为半角,`【`/`】` 与全角字母数字一样命中快捷键
11
+ * - 自适应导航:Tab 循环 / 数字跳转 / 确认推进一律沿可见题目序列行走,被条件隐藏的题目自动跳过
9
12
  * - Esc / Ctrl+C 双通道取消(输入模式二次确认);定时器、Abort 监听与缓存池全量回收
10
13
  *
11
14
  * 渲染纯度不变量:`render()` 及其可达被调函数禁止写入语义状态(`currentTab`、光标与各 `*ScrollOffset`、`inputMode`、`editTarget`、`editorEscArmed`、`statusMessage`、`isFinished`);滚动位移归一化一律归属输入路径
12
- * 可写入透明备忘:`cached*`、`previewCache`、`tabCache`、`leftColCache`、`summaryCachedLines`、`menuCache`、`previewViewportObservation` 均为「渲染输入的纯函数」缓存
15
+ * 可写入透明备忘:`cached*`、`tabCache`、`menuCache`、`previewViewportObservation` 与视图层内部缓存均为「渲染输入的纯函数」缓存
13
16
  *
14
- * 依赖方向:component.ts → theme / texts / format / model / novel-markdown / view-rows / view-summary;本组件只做按键路由、视口切片与整屏缓存编排
17
+ * 依赖方向:component.ts → theme / texts / format / model / view-editor / view-question / view-rows / view-summary;本组件只做按键路由与整屏缓存编排
15
18
  */
16
19
  import { type Component, type Focusable, type TUI } from "@earendil-works/pi-tui";
17
20
  import { AskAuthorFormModel, type AskAuthorResult } from "./model.js";
18
21
  import type { Theme } from "./theme.js";
19
22
  /**
20
23
  * `ask_author` 交互式 TUI 组件(问卷作答与答卷总览)
21
- * 状态机:作答突变经 `AskAuthorFormModel`,`revision` 驱动两级缓存失效;`render()` 只写入透明备忘 / 观察缓存,绝不写回滚动位移等语义状态;`dispose()` 幂等且只设置 `disposed`,`finish()` 以 `isFinished` 保证 `done` 至多投递一次
24
+ * 状态机:作答突变经 `AskAuthorFormModel`,`revision` 驱动多级缓存失效;`render()` 只写入透明备忘 / 观察缓存,绝不写回滚动位移等语义状态;`dispose()` 幂等且只设置 `disposed`,`finish()` 以 `isFinished` 保证 `done` 至多投递一次
22
25
  */
23
26
  export declare class AskAuthorComponent implements Component, Focusable {
24
27
  /** Focusable 接口要求:输入模式焦点指示 */
@@ -72,14 +75,8 @@ export declare class AskAuthorComponent implements Component, Focusable {
72
75
  private cachedHeight?;
73
76
  /** 整屏已格式化终端行缓存 */
74
77
  private cachedLines?;
75
- /** 预览框完整渲染行缓存池(键:题号+条目身份+列宽+补充说明;超 {@link PREVIEW_CACHE_CAPACITY} 按插入序淘汰) */
76
- private readonly previewCache;
77
- /** 总览页未切片内容行缓存(键:列宽 + `revision`) */
78
- private summaryCachedLines?;
79
78
  /** Tab 栏行缓存(键:当前页 + `revision` + 列宽) */
80
79
  private tabCache?;
81
- /** 左栏菜单行缓存(键:题号 + 光标 + 位移 + `revision` + 左栏宽) */
82
- private leftColCache?;
83
80
  /** 菜单项派生缓存(键:题号 + `revision`,消除 render/输入路径重复构建) */
84
81
  private menuCache?;
85
82
  /** 上一帧预览视口的观察元数据(渲染写入、输入路径读取以归一化位移) */
@@ -88,6 +85,10 @@ export declare class AskAuthorComponent implements Component, Focusable {
88
85
  private editTarget?;
89
86
  /** 内置补充说明编辑器实例 */
90
87
  private readonly editor;
88
+ /** 题目作答页视图构建器(持有预览与左栏缓存) */
89
+ private readonly questionView;
90
+ /** 答卷总览页视图构建器(持有内容行缓存) */
91
+ private readonly summaryView;
91
92
  /**
92
93
  * 构造交互式 TUI 主组件
93
94
  * @param tui - TUI 上下文
@@ -108,7 +109,7 @@ export declare class AskAuthorComponent implements Component, Focusable {
108
109
  dispose(): void;
109
110
  /** 清空整屏帧缓存(列宽 / 行数键;不触发重绘) */
110
111
  private clearFrameCaches;
111
- /** 清空全部派生缓存(`revision` 键与内容键:预览池 / 总览 / Tab 栏 / 左栏 / 菜单项;不触发重绘) */
112
+ /** 清空全部派生缓存(`revision` 键与视图层内容缓存;不触发重绘) */
112
113
  private clearDerivedCaches;
113
114
  /** 状态突变时失效整屏缓存并请求重绘(保留派生缓存池复用) */
114
115
  private touch;
@@ -132,18 +133,14 @@ export declare class AskAuthorComponent implements Component, Focusable {
132
133
  * @param cancelled - 是否用户主动取消
133
134
  */
134
135
  finish(cancelled: boolean): void;
135
- /** Tab 总页数(题目页 + 总览提交页) */
136
- private get totalTabs();
136
+ /** 可见页序列:可见题目索引升序 + 总览提交页(`questionCount`);被条件隐藏的题目不出现在序列中 */
137
+ private get tabOrder();
137
138
  /** 当前 Tab 是否为答卷总览提交页 */
138
139
  private get isSubmitTab();
139
140
  /** 终端可视行数(`tui.terminal.rows` 不可用或非正数时兜底 24) */
140
141
  private get terminalRows();
141
142
  /** 总览页视口行数上限(随终端高度自适应;区间 `[4, LAYOUT_CONFIG.summaryPageSize]`) */
142
- private get summaryPageSize();
143
- /** 双栏右侧预览视口行数上限(区间 `[6, LAYOUT_CONFIG.splitPreviewHeight]`) */
144
- private get splitPreviewHeight();
145
- /** 单栏内联预览视口行数上限(区间 `[3, LAYOUT_CONFIG.singlePreviewHeight]`) */
146
- private get singlePreviewHeight();
143
+ private get summaryViewportHeight();
147
144
  /**
148
145
  * 当前题目的菜单项
149
146
  * @returns 菜单项数组;按 `(题号, revision)` 记忆化,命中时零分配复用
@@ -159,6 +156,11 @@ export declare class AskAuthorComponent implements Component, Focusable {
159
156
  * @param newTab - 目标 Tab 索引(0-based;questionCount 为总览页;同页为无操作)
160
157
  */
161
158
  private switchTab;
159
+ /**
160
+ * 按可见页序列循环切换 Tab(跳过被条件隐藏的题目)
161
+ * @param delta - 相对位移(正数下一项,负数上一项)
162
+ */
163
+ private switchRelativeTab;
162
164
  /** 收敛光标至合法区间并同步左栏滚动窗口,保证聚焦项可见(菜单项为空时直接返回) */
163
165
  private adjustScrollForCurrentOption;
164
166
  /**
@@ -203,7 +205,7 @@ export declare class AskAuthorComponent implements Component, Focusable {
203
205
  */
204
206
  private handleNoteShortcut;
205
207
  /**
206
- * 预览区垂直滚动键(PgUp / PgDn / 方括号 / Alt+上下)
208
+ * 预览区垂直滚动键(方括号 / Alt+上下)
207
209
  * 滚动位移归一化归属输入路径:以渲染帧写入的 {@link previewViewportObservation} 为界经 `clampWindowOffset` 收敛(缺观察元数据时按单栏预览高度退化)
208
210
  * @param data - 按键序列
209
211
  * @returns 是否已消费该按键
@@ -230,7 +232,7 @@ export declare class AskAuthorComponent implements Component, Focusable {
230
232
  private activateMenuItem;
231
233
  /**
232
234
  * 总览页键路由(滚动与回车提交;`Ctrl+S` 同义提交)
233
- * 空格更改为向下滚动(防止习惯性翻页误提交);位移归一化归属本路径,上界获取自 `summaryCachedLines` 总行数与 `summaryPageSize`,`End` 仅设置超大值,由后续输入收敛
235
+ * 空格更改为向下滚动(防止习惯性翻页误提交);位移归一化归属本路径,上界获取自总览视图缓存的内容行数,`End` 仅设置超大值,由后续输入收敛
234
236
  * @param data - 按键序列
235
237
  */
236
238
  private handleSummaryInput;
@@ -247,76 +249,4 @@ export declare class AskAuthorComponent implements Component, Focusable {
247
249
  * @returns 不超过 width 的终端行数组(命中缓存时直接返回)
248
250
  */
249
251
  render(width: number): string[];
250
- /**
251
- * 原地追加底部状态通知行(无文案时不追加,仅包含通知行与前面的空行)
252
- * @param lines - 输出行数组(原地追加)
253
- */
254
- private pushStatusMessage;
255
- /**
256
- * 渲染答卷总览与确认提交页(完成状态 / 已选项 / 补充说明 / 提交指引)
257
- * 内容行由 `view-summary.ts` 构建并按 `(列宽, revision)` 缓存于 `summaryCachedLines`,本方法只做 O(1) 切片;位移在本地收敛,绝不写回 `summaryScrollOffset`(写入只发生在输入路径)
258
- * @param lines - 输出行数组
259
- * @param renderWidth - 终端布局列宽
260
- */
261
- private renderSubmitSummary;
262
- /**
263
- * 渲染补充说明编辑器全屏模式(标题 / 上下文背景 / 编辑框)
264
- * @param lines - 输出行数组
265
- * @param renderWidth - 终端可视宽度
266
- */
267
- private renderInputMode;
268
- /**
269
- * 渲染常规题目作答视图(自适应双栏分屏或单栏紧凑列表)
270
- * @param lines - 输出行数组
271
- * @param renderWidth - 终端可视宽度
272
- */
273
- private renderQuestionView;
274
- /**
275
- * 获取聚焦条目的预渲染完整内容行(`previewCache` 透明备忘缓存,跨条目 / 切换题目零重复解析)
276
- * @param item - 目标菜单项
277
- * @param customText - 题目整体补充说明
278
- * @param optionNote - 选项专属补充说明
279
- * @param targetWidth - 预览可用列宽
280
- * @returns 未切片的完整内容行数组;`item` 缺省时返回空数组
281
- */
282
- private previewContentLines;
283
- /**
284
- * 计算并切片本帧预览框行(纯计算,不写回语义状态)
285
- * 位移由 `previewScrollOffset` 与本帧总行数在本地收敛;本帧观察元数据写入 {@link previewViewportObservation} 供输入路径归一化位移
286
- * @param item - 目标菜单项
287
- * @param customText - 题目整体补充说明
288
- * @param optionNote - 选项专属补充说明
289
- * @param targetWidth - 预览可用列宽
290
- * @param viewHeight - 视口最大行数
291
- * @returns 本帧可视内容行(含底部滚动信息行)
292
- */
293
- private buildPreviewBox;
294
- /**
295
- * 对预渲染完整内容行做 O(1) 滑动窗口切片并附加底部行号信息
296
- * @param fullLines - 已折行完整内容行
297
- * @param scrollOffset - 已由 {@link clampWindowOffset} 收敛的起始行位移
298
- * @param viewHeight - 视口最大行数
299
- * @param targetWidth - 预览可用列宽
300
- * @returns 视口内容行与滚动信息行
301
- */
302
- private slicePreviewBoxLines;
303
- /**
304
- * 渲染双栏视图(左选项菜单 + 右推演 / 分镜预览框)
305
- * @param lines - 输出行数组
306
- * @param q - 当前题目
307
- * @param st - 作答状态
308
- * @param items - 菜单项
309
- * @param renderWidth - 终端可用宽度(不足侧补空行至两栏等高)
310
- */
311
- private renderSplitView;
312
- /**
313
- * 遍历输出菜单项行,单栏模式下在聚焦项下方内联展开预览
314
- * @param lines - 输出行数组
315
- * @param items - 菜单项
316
- * @param q - 当前题目
317
- * @param st - 作答状态
318
- * @param rowWidth - 选项行可用列宽
319
- * @param expandFocused - 是否展开聚焦项预览(窗口外条目不访问)
320
- */
321
- private pushListItems;
322
252
  }
package/dist/format.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * - 全部函数无副作用、不依赖运行时可变状态,供 sanitize / model / novel-markdown / view-* / component / index 复用
7
7
  * - 终端宽度一律经 `visibleWidth` / `truncateToWidth` / `wrapTextWithAnsi`,禁止使用 `string.length` 直接计算
8
8
  *
9
- * 依赖方向:format.ts → texts.ts(布局常量与文案字典)
9
+ * 依赖方向:format.ts → layout.ts(布局常量);不装载语言资源,保持纯算法层可在任意执行模式下复用
10
10
  */
11
11
  /**
12
12
  * 将数值收敛至闭区间 `[min, max]`
@@ -83,11 +83,11 @@ export declare function padToVisibleWidth(str: string, targetWidth: number): str
83
83
  */
84
84
  export declare function divider(width: number, char?: string): string;
85
85
  /**
86
- * 极限压缩正文草稿/分镜头为高密度单行 LLM 指令:逐行剥离行首 Markdown 标题标识与引用符,折叠连续空白,各片段以 `TEXTS.markdown.draftSegmentSeparator` 连接
87
- * @param preview - 原始草稿/分镜头文本;缺省或全空白时返回 undefined
88
- * @returns 单行分镜头指令;无有效片段时返回 undefined
86
+ * 把输入法全角字符收敛为半角按键字符,使快捷键在中文输入法开启时仍然命中
87
+ * @param data - 终端按键序列
88
+ * @returns 单个全角字符被替换后的序列;长度非 1 的序列(转义序列、粘贴文本)原样返回
89
89
  */
90
- export declare function compressDraftPreview(preview: string | undefined): string | undefined;
90
+ export declare function normalizeImeKey(data: string): string;
91
91
  /**
92
92
  * 折叠不可信文本的换行与连续空白为单空格:LLM 生成的描述/批注含换行会破坏返回封套的行结构
93
93
  * @param text - 原始文本
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @file i18n.ts
3
+ * @description 语言资源注册、系统语言探测与模板字典编译的完整实现
4
+ *
5
+ * 资源约定:
6
+ * - 语言文件位于仓库根 `lang/`,文件名即 locale 标识(`en_US`、`zh_CN`),新增语言只需在 {@link LOCALE_PACKAGES} 追加一项导入
7
+ * - 纯字符串即原样文案;字符串数组即文案列表;`{ "template": "…", "params": ["a", "b"] }` 为带插值的模板对象,声明形参时编译为定参函数,省略 `params` 时在装载期求值为常量文案
8
+ * - 模板占位符 `{name}` 取 `params` 同名形参,`{icon.<键>}` 取调用方传入的共享常量解析器结果
9
+ * - `en_US` 为规范语言:键集合、参数个数与取值种类以它为准;其余语言缺失的键回退 `en_US`,结构与规范语言不符时立即抛出异常
10
+ *
11
+ * 语言探测:按 GNU gettext 优先级读取 `LANGUAGE`(冒号分隔的候选列表)→ `LC_ALL` → `LC_MESSAGES` → `LANG`,再以 `Intl` 解析出的系统语言兜底;候选值先精确匹配 locale,再按主语言匹配同族语言,全部落空时回退规范语言
12
+ *
13
+ * 依赖方向:i18n.ts 仅导入 `lang/` 下的语言文件,不导入项目内任何模块;由 `texts.ts` 装载并组装为全局文案字典
14
+ */
15
+ import enUS from "../lang/en_US.json";
16
+ /** 带插值的模板声明(`params` 为规范语言声明的形参名,决定编译后函数的形参个数) */
17
+ export interface TemplateSpec {
18
+ /** 模板原文,占位符写作 `{形参名}` 或 `{icon.<ICONS 键>}` */
19
+ readonly template: string;
20
+ /** 形参名列表(省略表示无形参) */
21
+ readonly params?: readonly string[];
22
+ }
23
+ /** 语言资源树节点:文案、文案列表、模板声明或嵌套分组 */
24
+ export type TreeNode = string | readonly string[] | TemplateSpec | {
25
+ readonly [key: string]: TreeNode;
26
+ };
27
+ /** 编译结果类型:声明形参的模板转为定参函数,无形参模板折叠为常量文案,数组转为只读列表,分组递归展开 */
28
+ export type CompiledTree<T> = T extends {
29
+ readonly template: string;
30
+ readonly params: readonly string[];
31
+ } ? (...args: Array<string | number>) => string : T extends {
32
+ readonly template: string;
33
+ } ? string : T extends string ? string : T extends readonly string[] ? readonly string[] : T extends object ? {
34
+ readonly [key in keyof T]: CompiledTree<T[key]>;
35
+ } : never;
36
+ /** 占位符常量解析器:返回 undefined 表示该名字交给形参处理 */
37
+ export type ResolveTemplateConstant = (name: string) => string | undefined;
38
+ /**
39
+ * 依据系统语言环境选择界面语言
40
+ * @param environment - 环境变量表(生产传入 `process.env`,测试可传入任意白名单对象)
41
+ * @param systemLocale - `Intl` 解析出的系统 locale(可选,作为最后一位候选)
42
+ * @returns 注册表中命中的 locale 键;无任何命中时为 {@link CANONICAL_LOCALE}
43
+ */
44
+ export declare function detectLocale(environment: Record<string, string | undefined>, systemLocale?: string): string;
45
+ /** 本次进程解析出的界面语言键 */
46
+ export declare const ACTIVE_LOCALE: string;
47
+ /**
48
+ * 装载当前界面语言的文案字典
49
+ * @param resolveConstant - 占位符常量解析器(`{icon.<键>}` 等共享符号)
50
+ * @returns 以规范语言结构为准、按当前语言填充文案的函数字典
51
+ */
52
+ export declare function loadTexts(resolveConstant: ResolveTemplateConstant): CompiledTree<typeof enUS>;
package/dist/index.d.ts CHANGED
@@ -1,52 +1,28 @@
1
1
  /**
2
2
  * @file index.ts
3
- * @description 作者请示插件入口:注册 `ask_author` 工具与 `/ask-author` 命令
3
+ * @description 作者请示插件入口:注册 `ask_author` 工具与 `/ask-author` 命令,并编排工具执行生命周期
4
4
  *
5
5
  * 核心架构特性:
6
- * - 四类出口(取消 / 结构化错误 / 运行期异常 / 正常完成)由四个结果工厂统一产出;其中取消、结构化错误与运行期异常三类共享 `ResultContext` 的缺省收敛,完成结果由表单模型直接供给
7
- * - `prepareArguments` 防御性清洗兼容批量问卷与单题平铺双模式;返回文本恒以 `TEXTS.markdown` 的固定封套前缀开头(`[Author Decision Finalized]` / `[Author Decision Cancelled]` / `[Author Consultation Error]`),前缀字节稳定利于 Prompt Cache 命中
8
- * - 性能与生命周期:调用卡片按 args 身份记忆化派生、结果卡片复用槽位 `lastComponent`;AbortSignal 经组件级联取消;本文件不注册任何监听器与定时器,`AskAuthorComponent.dispose()`(幂等)负责回收定时器、Abort 监听与全部缓存池
6
+ * - 执行流程只做守卫与分支:信号已中止 → 取消;非 TUI → 结构化错误;模型构建失败 → 条件错误或运行期异常;无题目 → 空题目错误;UI 返回 → 取消或完成
7
+ * - 结果构造、卡片渲染与执行编排分属 `tool-result.ts` / `tool-card.ts` / 本文件,宿主边界代码保持可独立测试
8
+ * - 工具声明为 `model-only`(交互式 UI 只由模型直接发起),并标注只读、封闭世界,供权限扩展判断
9
+ * - 本文件不注册监听器与定时器;组件级 AbortSignal 级联取消与 `dispose()` 幂等回收由 `AskAuthorComponent` 负责
9
10
  *
10
- * 依赖方向:index.ts 为 11 模块单向依赖图(theme → texts → {format, schema} → {model, sanitize} → {novel-markdown, view-rows, view-summary} → component → index)的入口;旁支 `sanitize.ts` 仅由本文件装配(`prepareArguments` 与 `renderCall` 两处)
11
+ * 依赖方向:index.ts 为模块依赖图的入口;`component` / `model` / `schema` / `sanitize` / `texts` / `tool-card` / `tool-result` 均为其直接依赖
11
12
  */
12
- import { type AgentToolResult, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
- import { type AskAuthorResult, type NormalizedQuestion } from "./model.js";
14
- /** `ask_author` 工具执行结果结构(基于 Pi 官方 `AgentToolResult` 结构特化) */
15
- export type AskAuthorToolResult = AgentToolResult<AskAuthorResult>;
16
- /** 结果构造上下文:题目快照与标题/描述的可选覆盖(缺省值统一由 {@link resolveResultContext} 收敛) */
17
- export interface ResultContext {
18
- /** 题目快照(缺省空数组:取消/错误可能发生在模型构建前) */
19
- questions?: NormalizedQuestion[];
20
- /** 问卷总标题(缺省 `TEXTS.fallbacks.defaultFormTitle`) */
21
- formTitle?: string;
22
- /** 问卷全局描述(缺省 undefined,不携带该字段内容) */
23
- formDescription?: string;
24
- }
25
- /** 调用卡片派生数据:与主题无关的纯计算结果(颜色合成每帧按当前主题现场计算) */
26
- export interface CallCardData {
27
- /** 卡片标题(已清洗的 `formTitle`) */
28
- title: string;
29
- /** 计数标签(多题模式为 `2 题 • 6 选项`,单题模式仅 `6 选项`) */
30
- countLabel: string;
31
- }
32
- /**
33
- * `renderCall` 记忆化状态载荷:宿主按工具执行行创建一次、跨帧复用的可变对象(`ToolRenderContext.state`,同一行的 `renderCall` 与 `renderResult` 共享该实例)
34
- * 以 **args 对象身份**为键(`cachedArgs` 记录身份、`cachedCard` 记录派生数据),依赖宿主「流式补全整体替换 `args` 引用」的不变量(宿主经 `ToolExecutionComponent.updateArgs` 赋入新对象):身份变化即失效重新计算;若宿主原地改写同一对象,缓存将滞留旧数据
35
- */
36
- export interface CallRenderState {
37
- /** 上一次派生所用的 args 对象身份(仅作 `===` 引用比对,不读取任何属性) */
38
- cachedArgs?: object;
39
- /** 与 `cachedArgs` 配对的派生数据;两者身份同时命中时直接复用 */
40
- cachedCard?: CallCardData;
41
- }
13
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
42
14
  /**
43
15
  * `ask-author` 扩展插件主入口工厂(仅完成注册,启动期不执行任何交互)
44
16
  * @param pi - Pi 扩展运行时 API 实例
45
17
  */
46
18
  export default function askAuthorExtension(pi: ExtensionAPI): void;
47
19
  export { askAuthorExtension };
48
- export { AskAuthorFormModel, type AskAuthorResult, type AuthorAnswerItem, type NormalizedQuestion } from "./model.js";
49
- export { AskAuthorParams, type RawParams } from "./schema.js";
20
+ export { AdaptiveConditionError, AskAuthorFormModel, type AskAuthorResult, type AuthorAnswerItem, type NormalizedOption, type NormalizedQuestion, type VisibilityCondition, } from "./model.js";
21
+ export { AskAuthorParams, OptionSchema, QuestionSchema, ShowIfConditionSchema, type RawParams } from "./schema.js";
50
22
  export { sanitizeAskAuthorArgs } from "./sanitize.js";
51
- export { TEXTS, LAYOUT_CONFIG, ICONS } from "./texts.js";
52
- export { cleanOptional, cleanText, padToVisibleWidth, safeLine } from "./format.js";
23
+ export { TEXTS } from "./texts.js";
24
+ export { ICONS, LAYOUT_CONFIG, MAX_OPTIONS, MAX_QUESTIONS } from "./layout.js";
25
+ export { ACTIVE_LOCALE, detectLocale } from "./i18n.js";
26
+ export { cleanOptional, cleanText, normalizeImeKey, padToVisibleWidth, safeLine } from "./format.js";
27
+ export type { AskAuthorToolResult, ResultContext } from "./tool-result.js";
28
+ export type { CallCardData, CallRenderState } from "./tool-card.js";