mocode-ai 0.4.7 → 0.4.9

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.
Files changed (118) hide show
  1. package/dist/agent/core.d.ts +112 -0
  2. package/dist/agent/core.js +51 -11
  3. package/dist/agent/index.d.ts +19 -0
  4. package/dist/agent/mode.d.ts +29 -0
  5. package/dist/agent/spawn.d.ts +36 -0
  6. package/dist/attachments/image.d.ts +22 -0
  7. package/dist/commands/config.d.ts +8 -0
  8. package/dist/config/file.d.ts +23 -0
  9. package/dist/config/index.d.ts +114 -0
  10. package/dist/config/index.js +48 -1
  11. package/dist/config/presets.d.ts +57 -0
  12. package/dist/context/budget.d.ts +79 -0
  13. package/dist/context/classifier.d.ts +14 -0
  14. package/dist/context/encoders/_util.d.ts +11 -0
  15. package/dist/context/encoders/code.d.ts +2 -0
  16. package/dist/context/encoders/doc.d.ts +14 -0
  17. package/dist/context/encoders/graph.d.ts +11 -0
  18. package/dist/context/encoders/index.d.ts +2 -0
  19. package/dist/context/encoders/log.d.ts +2 -0
  20. package/dist/context/encoders/memory.d.ts +2 -0
  21. package/dist/context/encoders/passthrough.d.ts +11 -0
  22. package/dist/context/encoders/search.d.ts +2 -0
  23. package/dist/context/encoders/summary.d.ts +12 -0
  24. package/dist/context/encoders/table.d.ts +2 -0
  25. package/dist/context/encoders/tree.d.ts +2 -0
  26. package/dist/context/index.d.ts +6 -0
  27. package/dist/context/lifecycle.d.ts +51 -0
  28. package/dist/context/pipeline.d.ts +9 -0
  29. package/dist/context/registry.d.ts +9 -0
  30. package/dist/context/relevance.d.ts +65 -0
  31. package/dist/context/types.d.ts +36 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/llm/capabilities.d.ts +19 -0
  34. package/dist/llm/index.d.ts +111 -0
  35. package/dist/memory/discover.d.ts +15 -0
  36. package/dist/memory/index.d.ts +13 -0
  37. package/dist/memory/reflect.d.ts +34 -0
  38. package/dist/memory/store.d.ts +124 -0
  39. package/dist/permissions/index.js +138 -0
  40. package/dist/pet/bridge.d.ts +73 -0
  41. package/dist/pet/protocol.d.ts +83 -0
  42. package/dist/pet/state.d.ts +28 -0
  43. package/dist/plan/active.d.ts +23 -0
  44. package/dist/plan/index.d.ts +13 -0
  45. package/dist/plan/state.d.ts +2 -0
  46. package/dist/plan/store.d.ts +78 -0
  47. package/dist/project-skill/index.js +129 -0
  48. package/dist/project-skill/initializer.js +179 -0
  49. package/dist/project-snapshot/index.js +177 -0
  50. package/dist/project-snapshot/static-files.js +87 -0
  51. package/dist/repl/index.d.ts +22 -0
  52. package/dist/repl/index.js +159 -1
  53. package/dist/rollback/index.d.ts +79 -0
  54. package/dist/sandbox/command.d.ts +3 -0
  55. package/dist/sandbox/index.d.ts +4 -0
  56. package/dist/sandbox/jail.d.ts +17 -0
  57. package/dist/sandbox/policy.d.ts +21 -0
  58. package/dist/sandbox/root.d.ts +5 -0
  59. package/dist/session/compact.d.ts +92 -0
  60. package/dist/session/drop.d.ts +19 -0
  61. package/dist/session/index.d.ts +13 -0
  62. package/dist/session/persist.d.ts +30 -0
  63. package/dist/session/scheduler.d.ts +53 -0
  64. package/dist/skills/discover.d.ts +33 -0
  65. package/dist/skills/index.d.ts +13 -0
  66. package/dist/tools/builtins/ask-human.d.ts +9 -0
  67. package/dist/tools/builtins/codegraph.d.ts +2 -0
  68. package/dist/tools/builtins/codegraph.js +17 -4
  69. package/dist/tools/builtins/drop-context.d.ts +18 -0
  70. package/dist/tools/builtins/edit-file.d.ts +2 -0
  71. package/dist/tools/builtins/edit-file.js +1 -0
  72. package/dist/tools/builtins/glob.d.ts +2 -0
  73. package/dist/tools/builtins/grep.d.ts +2 -0
  74. package/dist/tools/builtins/index.d.ts +2 -0
  75. package/dist/tools/builtins/index.js +7 -0
  76. package/dist/tools/builtins/memory-forget.d.ts +2 -0
  77. package/dist/tools/builtins/memory-forget.js +1 -0
  78. package/dist/tools/builtins/memory-list.d.ts +2 -0
  79. package/dist/tools/builtins/memory-save.d.ts +2 -0
  80. package/dist/tools/builtins/memory-save.js +1 -0
  81. package/dist/tools/builtins/memory-search.d.ts +2 -0
  82. package/dist/tools/builtins/memory-update.d.ts +2 -0
  83. package/dist/tools/builtins/memory-update.js +1 -0
  84. package/dist/tools/builtins/project-skill-update.js +73 -0
  85. package/dist/tools/builtins/read-file.d.ts +2 -0
  86. package/dist/tools/builtins/read-file.js +17 -1
  87. package/dist/tools/builtins/run-command.d.ts +2 -0
  88. package/dist/tools/builtins/run-command.js +1 -0
  89. package/dist/tools/builtins/switch-mode.d.ts +2 -0
  90. package/dist/tools/builtins/task.d.ts +2 -0
  91. package/dist/tools/builtins/task.js +1 -0
  92. package/dist/tools/builtins/todolist.d.ts +2 -0
  93. package/dist/tools/builtins/use-skill.d.ts +2 -0
  94. package/dist/tools/builtins/web-fetch.d.ts +2 -0
  95. package/dist/tools/builtins/web-search.d.ts +2 -0
  96. package/dist/tools/builtins/web-search.js +12 -1
  97. package/dist/tools/builtins/write-file.d.ts +2 -0
  98. package/dist/tools/builtins/write-file.js +1 -0
  99. package/dist/tools/constants.d.ts +52 -0
  100. package/dist/tools/registry.d.ts +18 -0
  101. package/dist/tools/types.d.ts +42 -0
  102. package/dist/ui/batch.d.ts +67 -0
  103. package/dist/ui/clipboard.d.ts +9 -0
  104. package/dist/ui/content.d.ts +55 -0
  105. package/dist/ui/content.js +14 -3
  106. package/dist/ui/diff.d.ts +34 -0
  107. package/dist/ui/diff.js +15 -24
  108. package/dist/ui/intervention.d.ts +46 -0
  109. package/dist/ui/layout.d.ts +225 -0
  110. package/dist/ui/layout.js +10 -0
  111. package/dist/ui/markdown.d.ts +46 -0
  112. package/dist/ui/mouse.d.ts +29 -0
  113. package/dist/ui/prompt.d.ts +61 -0
  114. package/dist/ui/render.d.ts +60 -0
  115. package/dist/ui/spinner.d.ts +26 -0
  116. package/dist/ui/theme.d.ts +47 -0
  117. package/dist/updater/index.d.ts +22 -0
  118. package/package.json +1 -1
@@ -143,8 +143,8 @@ export function lineAt(abs) {
143
143
  *
144
144
  * 行须自洽(每行带行末 \x1B[0m),不经 feedChar/feedSgr,直接入 rows。
145
145
  * after < 0 视为在所有已 commit 行之前插入;after ≥ 已 commit 行数则追加到末尾。
146
- * 不动 segMark——此函数用于非 md 路径(BatchRenderer 展开/折叠);
147
- * md 段切回 layout.contentWriteMd setLines 仍按 segMark 截断。
146
+ * segMark 活跃且插入点在 segMark.rowIdx 之前,则 segMark.rowIdx 跟着平移(否则
147
+ * 流式 md 段活跃期间展开/折叠 batch 后,下一个 md chunk setLines 截断到错误位置)。
148
148
  *
149
149
  * 后置条件:插入后 hasCurrent=false(新空行不由本函数建立);调用方须自行决定续写位
150
150
  * (BatchRenderer 在插入后调 layout.contentWrite 续写,新 \n 自然在详情块后建新行)。
@@ -162,6 +162,11 @@ export function insertAfter(after, lines) {
162
162
  // after 是绝对行索引;若超过 committed(例如快照时 hasCurrent=true),钳到末尾
163
163
  const target = after < 0 ? 0 : Math.min(after + 1, committed);
164
164
  rows.splice(target, 0, ...lines);
165
+ // 流式 md 段活跃期间:插入点在段起点之前 → segMark.rowIdx 平移,
166
+ // 否则下一个 md chunk 的 setLines 截断到旧位置,展开行被砍掉。
167
+ if (segMark && target <= segMark.rowIdx) {
168
+ segMark.rowIdx += lines.length;
169
+ }
165
170
  if (rows.length > MAX_ROWS + 512)
166
171
  rows.splice(0, rows.length - MAX_ROWS);
167
172
  }
@@ -185,5 +190,11 @@ export function deleteFrom(startIdx, n) {
185
190
  if (startIdx >= committed)
186
191
  return;
187
192
  const end = Math.min(startIdx + n, committed);
188
- rows.splice(startIdx, end - startIdx);
193
+ const actual = end - startIdx;
194
+ rows.splice(startIdx, actual);
195
+ // 流式 md 段活跃期间:删除区间全部在段起点之前 → segMark.rowIdx 回退,
196
+ // 否则下一个 md chunk 的 setLines 截断到旧位置,段内容错位。
197
+ if (segMark && startIdx + actual <= segMark.rowIdx) {
198
+ segMark.rowIdx -= actual;
199
+ }
189
200
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 文件改动 diff 渲染(仿 Claude Code):
3
+ * Update(path) / Create(path) + Added N lines, removed M lines
4
+ * 正文带文件行号 + `-` 红 / `+` 绿 gutter + 代码经 cli-highlight 语法高亮。
5
+ *
6
+ * 纯渲染叶子模块——不依赖 config / 业务;颜色经 theme 非 TTY 退化为空串,
7
+ * 输出供 layout.contentWrite 写入(区域滚动 / 底栏保护 / 续写位跟踪均由其内置)。
8
+ * 高亮逐行做(每行自成 SGR 闭区间,末尾补 reset 防跨行泄漏);多行构造(块注释 / 模板串)的内部行可能不上色,可接受。
9
+ */
10
+ /** 行级 diff 单元:ctx 不变 / add 新增 / del 删除。 */
11
+ export type DiffOp = 'ctx' | 'add' | 'del';
12
+ export interface DiffLine {
13
+ op: DiffOp;
14
+ text: string;
15
+ }
16
+ /**
17
+ * 行级 LCS diff。返回 op 序列(ctx/add/del)。
18
+ * 任一边超 MAX_FULL_DIFF_LINES 返回 null(降级,由调用方走「省略 diff」分支)。
19
+ */
20
+ export declare function lineDiff(oldS: string, newS: string): DiffLine[] | null;
21
+ /**
22
+ * 渲染文件改动块(供 layout.contentWrite 写入)。
23
+ * - kind:'edit' 头行用 Update;kind:'write' 用 Update(覆盖)/ Create(oldStr===null 新建)。
24
+ * - oldStr===null 表示新建:正文只显 + 行,行号从 1。
25
+ * - startLine:diff 首行对应的文件行号(edit_file 由调用方定位 old_string 起始行;write_file 传 1)。
26
+ * - diff 过大(超 MAX_FULL_DIFF_LINES)时仅显头行 + 「file too large, diff omitted」。
27
+ */
28
+ export declare function renderFileChange(opts: {
29
+ path: string;
30
+ kind: 'edit' | 'write';
31
+ oldStr: string | null;
32
+ newStr: string;
33
+ startLine?: number;
34
+ }): string;
package/dist/ui/diff.js CHANGED
@@ -191,14 +191,11 @@ function compactCtx(ops) {
191
191
  }
192
192
  return items;
193
193
  }
194
- function gutterOf(op, restoreBg = '') {
195
- // restoreBg:add/del 行在 pushBody 处已铺底色,gutter 的 `${ui.reset}` 会清掉底色,所以
196
- // reset 后必须重发 bg SGR,让该行后续字符继续带底色(否则 gutter 后一小段会变无色)。
197
- const rb = restoreBg;
194
+ function gutterOf(op) {
198
195
  if (op === 'del')
199
- return `${ui.red}-${ui.reset}${rb}`;
196
+ return `${ui.red}-${ui.reset}`;
200
197
  if (op === 'add')
201
- return `${ui.green}+${ui.reset}${rb}`;
198
+ return `${ui.green}+${ui.reset}`;
202
199
  return `${ui.dim} ${ui.reset}`;
203
200
  }
204
201
  function lineWord(n) {
@@ -264,41 +261,35 @@ function renderBody(head, counts, items, padW, startLine, lang) {
264
261
  let overflow = 0;
265
262
  const pushBody = (num, op, text) => {
266
263
  const numStr = String(num).padStart(padW);
267
- // 行级底色:add/del 整行包裹主题底色(addBg/delBg),行末由 codeText 内置 reset 闭合 →
268
- // bg 不污染下一行。ctx 行不加底色(避免整个 diff 块被背景化,符合 GitHub / VSCode 视觉)
269
- // 注意 gutterOf 与 numStr 的 `${ui.reset}` 会清除之前累加的 bg,所以要紧跟一个 bg 恢复 SGR
270
- // 才能让该行后续字符(代码区)继续带底色。
264
+ // 行级底色:仅代码区包裹主题底色(addBg/delBg),行号与 gutter 不着底色
265
+ // (避免整行被背景化,符合 GitHub / VSCode 视觉)。行末由 codeText 内置 reset 闭合 →
266
+ // bg 不污染下一行。
271
267
  // 同时,代码区由 cli-highlight 渲染,内部会反复 ${fg}tok${reset}tok${fg}tok${reset}…
272
268
  // 每个 reset 都会清掉外层 bg → 后面的 token 变无色。所以要在每个 reset 后重发 bg SGR
273
- // (行末那枚 reset 是收尾的,后面紧跟换行而非字符,不需补 bg)。用行末 reset 之外的 reset
274
- // 计数 = 内部重发点。
269
+ // (行末那枚 reset 是收尾的,后面紧跟换行而非字符,不需补 bg)
275
270
  const bg = op === 'add' ? ui.addBg : op === 'del' ? ui.delBg : '';
271
+ const rawCode = codeText(text, lang);
276
272
  if (bg === '') {
277
- lines.push(`${BODY_INDENT}${ui.gray}${numStr}${ui.reset} ${gutterOf(op)} ${codeText(text, lang)}`);
273
+ lines.push(`${BODY_INDENT}${ui.gray}${numStr}${ui.reset} ${gutterOf(op)} ${rawCode}`);
278
274
  return;
279
275
  }
280
- const raw = `${bg}${BODY_INDENT}${ui.gray}${numStr}${ui.reset}${bg} ${gutterOf(op, bg)} ${codeText(text, lang)}${ui.reset}`;
276
+ const codeWithBg = `${bg}${rawCode}${ui.reset}`;
281
277
  // 在每个非行末的 ${ui.reset} 后重发 bg。行末 reset 紧跟行尾或换行,无需补。
282
- // 简单做法:把除最后一个 reset 外的所有 reset 后都补 bg——但要注意 cli-highlight
283
- // 输出的代码区段内部还有"省略号截断"${ui.dim}…${ui.reset},它的 reset 也需补 bg。
284
278
  // 用 split 走一遍:找出所有 reset 位置(除最后那个),在其后插入 bg。
285
279
  const resetStr = ui.reset;
286
- const lastResetIdx = raw.lastIndexOf(resetStr);
280
+ const lastResetIdx = codeWithBg.lastIndexOf(resetStr);
287
281
  if (lastResetIdx < 0) {
288
- lines.push(raw);
282
+ lines.push(`${BODY_INDENT}${ui.gray}${numStr}${ui.reset} ${gutterOf(op)} ${codeWithBg}`);
289
283
  return;
290
284
  }
291
- // 把 raw 切成 [prefix + 末 reset] + (中间所有 reset 替换为 reset+bg)
292
- const prefix = raw.slice(0, lastResetIdx);
293
- const tail = raw.slice(lastResetIdx);
294
- // 头部所有 reset 之后插 bg(注:用 split 重建)
285
+ const prefix = codeWithBg.slice(0, lastResetIdx);
286
+ const tail = codeWithBg.slice(lastResetIdx);
295
287
  const parts = prefix.split(resetStr);
296
- // parts[i] 是第 i 段,紧跟一段 reset(最后一段后无 reset,故少一个元素)
297
288
  let rebuilt = parts[0];
298
289
  for (let i = 1; i < parts.length; i++) {
299
290
  rebuilt += resetStr + bg + parts[i];
300
291
  }
301
- lines.push(rebuilt + tail);
292
+ lines.push(`${BODY_INDENT}${ui.gray}${numStr}${ui.reset} ${gutterOf(op)} ${rebuilt + tail}`);
302
293
  };
303
294
  for (const it of items) {
304
295
  if (shown >= MAX_BODY_LINES) {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 介入面板:agent 执行中遇到需要用户决策的岔路时,弹问题 + 选项让用户选择(ask_human 工具调用)。
3
+ * 仿 Claude Code 的 ask / AskUserQuestion:用户可挑预设选项,也可选"自定义输入"自由作答。
4
+ *
5
+ * 渲染复用 promptTurnPicker 的模式(layout.paintInput:菜单向上展开进内容区底、提示/输入放底栏),
6
+ * 不碰固定边框、不用 DECSC/DECRC、逐行 \x1B[2K 擦除(符合 TUI 不变量)。
7
+ *
8
+ * 两个冲突由本函数自行处理(因 ask_human 在 executeTool 内被调,此时 agent 处 RUNNING 态):
9
+ * 1. spinner 在转(agent 在 await executeTool 前调了 spinner.start('执行 ask_human'),onFrame 每 80ms
10
+ * paintLiveAtCursor 画帧会覆盖面板)→ 进入前 Spinner.pauseCurrent() 停转。
11
+ * 2. repl 的 onRunningKey 在整个 runAgent 期间已挂载(↑/↓ 当滚动、Ctrl+C 当中断、可打印键当 typeahead
12
+ * 回显)→ 快照并摘掉现有 keypress 监听、挂自己的 onKey,退出 finally 按原序恢复。
13
+ * 滚轮:面板内滚轮只滚动查看 agent 输出内容(↑↓ 才选菜单),与 onRunningKey 一致;菜单经 scrollBy 末尾
14
+ * repaint()→paintInput(lastView) 重画,不被 viewport 滚动覆盖。进入时 resetScroll 回尾,退出时若滚过再回尾。
15
+ * 退出时 paintInput(dim,menu:null) 擦菜单 + 复位 lastMenuRows,再 resetScroll(面板内滚过)/repaintViewport
16
+ * 从缓冲重画内容区(恢复被菜单覆盖的 ● ask_human 行);顺序不可反——反了 stale lastMenuRows 会擦掉已恢复的内容。
17
+ *
18
+ * 非 TTY(管道/CI):不弹面板,choice 自动选第一项、input 返回 seed/空,打一行 stderr 日志,不阻塞。
19
+ */
20
+ export type InterventionType = 'choice' | 'input';
21
+ /** choice 单个选项:label 是选项本身(选中后回传的值),detail 是选它意味着什么/取舍(可选,渲染时与 label 同行、用全角括号()包裹)。 */
22
+ export interface ChoiceOption {
23
+ label: string;
24
+ detail?: string;
25
+ }
26
+ export interface InterventionRequest {
27
+ type: InterventionType;
28
+ /** 问题/标题,显示在面板顶部。 */
29
+ title: string;
30
+ /** choice 的选项列表(2~6 个);为空则降级 input。纯字符串 = 无说明,等价 {label}。 */
31
+ options?: Array<string | ChoiceOption>;
32
+ /** 背景说明,显示在标题下(可多行)。 */
33
+ detail?: string;
34
+ /** input 模式预填文本。 */
35
+ seed?: string;
36
+ /** choice 末尾是否追加「其他(自定义输入)」项。默认 true;只有调用方确实想关掉才传 false。 */
37
+ allowCustom?: boolean;
38
+ }
39
+ export interface InterventionResult {
40
+ /** selected=选中某选项;submitted=提交自定义文本;cancelled=用户取消(Esc/Ctrl+C)。 */
41
+ action: 'selected' | 'submitted' | 'cancelled';
42
+ /** selected=选项文本;submitted=输入文本;cancelled 时无。 */
43
+ value?: string;
44
+ }
45
+ /** 弹出介入面板,阻塞直到用户完成选择。 */
46
+ export declare function promptIntervention(req: InterventionRequest): Promise<InterventionResult>;
@@ -0,0 +1,225 @@
1
+ /**
2
+ * 全屏 TUI 布局(参考 Claude Code):alt screen + 单滚动区域(内容区)+ 区域外固定底栏(状态行 + 输入框)。
3
+ *
4
+ * 只依赖最成熟、Windows(WT / conhost)最稳的 ANSI 子集:
5
+ * - alt screen \x1B[?1049h / \x1B[?1049l
6
+ * - DECSTBM 滚动区域 \x1B[<top>;<bottom>r (区域内写满自动在区域内滚动,区域外底栏不被顶)
7
+ * - 逐行擦除 \x1B[2K / \x1B[K (菜单 / 清屏,绝不用 ED \x1B[J 做边界擦除)
8
+ * - CUP 绝对定位 \x1B[<row>;<col>H
9
+ * 不用原点模式(\x1B[?6h)、不依赖 ED 边界、不用 DECSC/DECRC、不用 IL/DL/SU/SD——这些在 conhost 上不可靠。
10
+ *
11
+ * 续写位(contentRow / contentCol)由 contentWrite 按字符宽度 + 折行模拟跟踪;CUP 到它即可随时回到内容续写位,
12
+ * 故底栏刷新(状态行 / spinner)无需保存/恢复光标——CUP 回续写位即可。
13
+ *
14
+ * 关键洞察:RUNNING 态也支持 typeahead 输入与滚动回看。INPUT 态光标常驻输入框;RUNNING 态光标在续写位,
15
+ * 运行中打的字经 paintRunningInputEcho 作 dim 回显(占位行换成已打文本,光标仍留续写位,不与流式争用);
16
+ * 运行中可滚动回看——contentWrite 在 scrollOffset>0 时只喂缓冲不物理写(否则新流式覆盖 viewport 历史行),
17
+ * 回尾(scrollOffset===0)才 cup 续写位写出。两态切换不重设区域(底栏高度恒 = 2),仅多行输入换行撑高时才 setRegion。
18
+ *
19
+ * 非 TTY:全部空操作,contentWrite 退化为 stdout.write,与改造前内联行为一致。
20
+ */
21
+ export interface Geo {
22
+ rows: number;
23
+ cols: number;
24
+ footerH: number;
25
+ contentTop: number;
26
+ contentBottom: number;
27
+ }
28
+ export interface StatusBarData {
29
+ model: string;
30
+ contextBar: string;
31
+ cwd: string;
32
+ status: string;
33
+ spinnerFrame?: string;
34
+ modeTag?: string;
35
+ /** 活跃 plan 短摘要(无 plan=undefined,空串=有 plan 但 chip 不显)。repl 透传 getActivePlanSummary()。 */
36
+ planSummary?: string;
37
+ /** 本轮最后一条 token 用量(modeTag chip 右边显示)。后端不开 include_usage 时 undefined。
38
+ * cachedTokens 可选 —— 后端未报或老 ChatUsage 字面量不带此字段时 UI 不会显示缓存标记。 */
39
+ lastTurnUsage?: {
40
+ promptTokens: number;
41
+ completionTokens: number;
42
+ totalTokens: number;
43
+ cachedTokens?: number;
44
+ };
45
+ }
46
+ export interface InputView {
47
+ prompt: string;
48
+ lines: string[];
49
+ cursorLine: number;
50
+ cursorCol: number;
51
+ menu: {
52
+ lines: string[];
53
+ } | null;
54
+ dim?: boolean;
55
+ placeholder?: string;
56
+ }
57
+ export declare function getGeo(fh?: number): Geo;
58
+ /** (重)设滚动区域 [1, rows-footerH];设完 \x1B[H 归位(conhost 设 DECSTBM 时光标留旧位置行为未定义)。 */
59
+ export declare function setRegion(fh: number): Geo;
60
+ export declare function contentMode(): void;
61
+ /** 运行态用户是否在打字(近期有按键)——是则暂停流式物理写(contentWrite/drawStatusBar/paintLiveAtCursor 跳过物理写),光标留输入框,IME 候选窗稳定不跟流式跑。 */
62
+ export declare function isStreamingPaused(): boolean;
63
+ /**
64
+ * 运行态用户打字时调(repl onRunningKey 每次按键):标记活跃 + 重置 flush 定时器。
65
+ * 活跃期间流式只喂缓冲不物理写(光标不入内容区);用户停手 USER_ACTIVE_PAUSE_MS 后 flush:
66
+ * repaintViewport 重画缓冲内容 + drawStatusBar 刷状态行 + 光标归输入框。
67
+ * 解决 IME 候选窗跟流式输出跑:流式写必须 cup 到 contentRow,IME 逐光标跟踪→气泡跟跑;打字时暂停写则光标不动。
68
+ */
69
+ export declare function setUserActive(): void;
70
+ /**
71
+ * 内容区正文写:CUP 到续写位 + 写出 + 按字符宽度/折行更新续写位 + 同步入 content 缓冲(供滚动回看)。
72
+ * 滚动区域内写满自动在区域内滚动,底栏在区域外不被顶。非 TTY / 未激活:直接 stdout.write(内联退化)。
73
+ *
74
+ * 逐字符分类(按码点取字符,SGR 序列整段识别):SGR 码 → 0 宽跟踪 + content.feedSgr;其他 CSI/OSC → 跳过
75
+ * (callers 理论上不传);\n / 折行 / tab 换行 → 行推进 + content.breakRow;可见字符 → charWidth 跟踪 +
76
+ * content.feedChar。SGR 在终端 0 宽,故续写位只按可见字符推进(与终端实际光标一致)。
77
+ */
78
+ export declare function contentWrite(s: string): void;
79
+ /**
80
+ * markdown 正文写(替代 contentWrite 用于 agent onText):累积 chunk 到 mdBuf,每 chunk 把整段
81
+ * mdBuf 经 renderMarkdown 渲成自洽 ANSI 行,replace 缓冲段(content.setLines 截旧 + 写新),
82
+ * repaintViewport 重画(仅尾 offset=0 且未暂停)。流式安全:未闭合 fence 照常 emit 进行中代码块;
83
+ * renderMarkdown 内部 memo by text 使重复渲染命中缓存。非 TTY / 未激活:直接 stdout.write(与
84
+ * contentWrite 一致,管道流式可见)。
85
+ *
86
+ * 续写位 = 段末下一行(段占 lines.length 行从 segmentStartRow 起);超可视区则滚动留 contentBottom。
87
+ * 物理重画用 repaintViewport(全内容区,原子一次 write 无闪烁)— md 段是缓冲尾,viewport 显尾即显段。
88
+ * 滚动回看(scrollOffset>0)只更新缓冲不物理写(回尾时显);打字中照常物理写——单次 write 结尾 cup 回输入框,IME 锚定不动。
89
+ */
90
+ export declare function contentWriteMd(s: string): void;
91
+ /**
92
+ * 把一段完整(非流式)assistant 文本一次性渲染成 markdown 写入(供 renderHistory 回显
93
+ * /resume / /rollback 复显上下文):渲染一次 → join('\n') → 经 contentWrite 增量写
94
+ * (无段 erase / 无 repaintViewport,无闪烁)。与流式 contentWriteMd 的区别:不累积 chunk、
95
+ * 不走段替换——一次性把渲染结果当普通带色文本喂入(渲染行各自 ≤ cols、自洽带色,contentWrite
96
+ * 的 SGR/折行状态机原样接纳)。非 TTY:退化为 stdout.write 原文(同 contentWrite)。
97
+ */
98
+ export declare function contentWriteMdOnce(s: string): void;
99
+ /** 清空内容区(保留底栏):全屏清 + 重设区域 + 续写位归 (1,1) + 清缓冲 + 回尾。底栏由调用方随后重画。 */
100
+ export declare function clearContent(): void;
101
+ /**
102
+ * 撤销(rewind)内容区末尾 n 物理行 —— 供 "刚 echoInput 写下用户气泡、用户撤回"
103
+ * 把气泡从可视区与 buffer 一起拿掉。content.rewind 弹出 buffer 末 n 行;layout 这边
104
+ * 同步上溯 contentRow(contentTop 钳位,防下溢)、scrollOffset 钳到新 totalRows、
105
+ * 最后 repaintViewport 重画可视区(running 态回 runningCaretPos,input 态回续写位)。
106
+ *
107
+ * 与 clearContent 不同:只擦最近一段、保留更早内容与 buffer;光标归内容区底,
108
+ * 不是 (1,1)。常发生在 RUNNING 态的 pending 窗口内 recall —— recall 后调用方
109
+ * 通常再 enterInputMode 切回 INPUT 视觉,光标由后续 paintInput 重画。
110
+ */
111
+ export declare function rewindContent(rowsToRewind: number): void;
112
+ /**
113
+ * 在绝对行索引 after 后插入自洽行(详情展开用)。
114
+ * 滚动回看冻结:scrollOffset>0 时 offset += delta 把窗口冻在原绝对行,
115
+ * 与 contentWrite/contentWriteMd 同模式。插入后 repaintViewport 重画 viewport。
116
+ *
117
+ * 续写位(contentRow):若原写头在插入点之后,前移 lines.length,保持相对位置;
118
+ * 若原写头 ≤ after,不变(新行在写头之后)。非 TTY 直接调 content.insertAfter。
119
+ */
120
+ export declare function contentInsertAfter(after: number, lines: string[]): void;
121
+ /**
122
+ * 从绝对行索引 startIdx 起删 n 行(折叠回退用)。
123
+ * 滚动回看冻结同 insertAfter:offset -= delta,钳 ≥ 0。续写位若在删区后前移 delta。
124
+ */
125
+ export declare function contentDeleteFrom(startIdx: number, n: number): void;
126
+ /** 当前内容物理行总数(含当前未提交行)。供 batch 渲染器在 endBatch 时定位摘要行索引。 */
127
+ export declare function totalRows(): number;
128
+ /** 清空内容区时通知 batch 渲染器重置(摘要行映射与展开态)。 */
129
+ export declare function notifyContentReset(): void;
130
+ /** 是否处于滚动回看态(offset>0,内容区显历史)。prompt 据此在非滚动键时回尾。 */
131
+ export declare function isScrolled(): boolean;
132
+ /**
133
+ * 重画内容区 viewport:按 scrollOffset 取缓冲尾窗,逐行 cup+clearline+rowtext 映射到屏 1..contentBottom。
134
+ * offset=0 即尾窗(== 实时屏,resize / 回尾时用)。清行含 contentBottom——顺带擦 WT 边距漏影(状态行重复)。
135
+ * 有活跃选区(鼠标拖拽中)时,对选中范围套反白——纯视觉,不影响缓冲内容。
136
+ */
137
+ export declare function repaintViewport(): void;
138
+ /** 滚动 delta 行(正=往新、负=往旧);钳 [0, max(0, total-contentBottom)];变则重画 + 刷底栏(显指示、光标回输入框)。 */
139
+ export declare function scrollBy(delta: number): void;
140
+ /** 清活跃选区(不复制),若原有选区则重画去掉反白。供 ESC / 点击别处 / 退出滚动态等场景调。 */
141
+ export declare function clearSelection(): void;
142
+ /**
143
+ * 注册"点击输入框粘贴"回调:当前持有文本输入的模块(prompt.ts 的 promptWithSlashMenu / repl 运行态
144
+ * typeahead)在挂键盘监听时调,退出时传 null 注销。点击输入行(未拖动)时 layout 读剪贴板后回调此函数,
145
+ * 由回调方完成插入(各输入状态的行/光标结构不同,layout 不掺和文本编辑逻辑,只管"贴入"这个动作触发)。
146
+ */
147
+ export declare function setPasteHandler(fn: ((text: string) => void) | null): void;
148
+ /** 注册"点击输入框 → 改光标"回调:prompt.ts 等文本所有者挂此回调后,
149
+ * 鼠标左键落输入行算出的新 (line, col) 会在 prompt 下次 redraw 前喂入此回调
150
+ * (prompt 用它写自己的 cl / cc,真正改 source of truth)。
151
+ * 传 null 注销(在 cleanup / 退出输入态时调,防下个 prompt 实例被旧回调污染)。 */
152
+ export declare function setCursorChangeHandler(fn: ((line: number, col: number) => void) | null): void;
153
+ /** picker / 介入面板期间禁用鼠标选区与拖拽(避免 viewport 重画覆盖菜单);滚轮仍可用。面板退出后恢复。 */
154
+ export declare function setMouseEnabled(v: boolean): void;
155
+ /** 回尾(offset=0);仅当原本滚动过才重画(避免每轮 enterRunningMode 闪烁)。 */
156
+ export declare function resetScroll(): void;
157
+ /** 发消息轮首短时锁住滚动:吸收 stdin 残留滚轮事件(发消息前后的滚轮惯性 / OS 缓冲延迟到达),
158
+ * 防 resetScroll 回尾后被 onRunningKey 接到的残留事件重新滚上去——致 agent 输出进缓冲、显示在历史区。
159
+ * 锁只挡 scrollBy(滚轮 / PgUp-PgDn);不影响 contentWrite 写屏(offset=0 时照常物理写,agent 输出从底部开始)。
160
+ * 默认 SCROLL_LOCK_MS 后自动解锁;enterInputMode(轮末)也清锁。用户轮中后段仍可上滑(满足"输出时能看历史")。 */
161
+ export declare function lockScrollToBottom(ms?: number): void;
162
+ /** 解锁(轮末 enterInputMode / 测试用)。 */
163
+ export declare function unlockScroll(): void;
164
+ /** 轮首滚动锁是否生效(测试用)。 */
165
+ export declare function isScrollLocked(): boolean;
166
+ /** 画状态行(plan 行 + spinner 行 + model 行,三行)。RUNNING 态 spinner 频繁调。
167
+ * 行号(footerH=6):
168
+ * plan 行 = contentBottom+1 (活跃 plan 时显 chip;无则空)
169
+ * spinner 行 = contentBottom+2 (◆ 空闲 / ⠹ 思考中… / etc)
170
+ * 上线 = contentBottom+3 (画在 paintInput)
171
+ * 输入行 = contentBottom+4
172
+ * 下线 = contentBottom+5
173
+ * model 行 = rows (屏底:auto + ctx + cwd) */
174
+ export declare function drawStatusBar(status?: StatusBarData): void;
175
+ /** 更新易变状态(状态文字 + spinner 帧)并重画状态行。spinner / agent 用。 */
176
+ export declare function setStatus(status: string, frame?: string): void;
177
+ /**
178
+ * 在续写位画一行瞬时活动文本(spinner 帧):不进缓冲、不推进续写位,逐行 clearLine 重画。
179
+ * 仅 TTY + offset=0(实时尾)+ 非打字暂停态时物理写屏;滚动态跳过(由状态行 spinner 兜底,且避免覆盖 viewport 历史行)。
180
+ * 配合 clearLiveAtCursor 在 spinner 停时清掉,随后 contentWrite 的结果即写在该行(spinner 不入历史缓冲)。
181
+ *
182
+ * 记 frameRow/frameCol = 实际画帧位置;clearLiveAtCursor 清"这行"而非当前续写位,防续写位漂移时清错行。
183
+ * 续写位漂移时(运行期间被某次 contentWrite 推进,罕见但 557e678 后可能)先清旧 frameRow 再画新位,避免旧帧残留。
184
+ * 打字暂停态(isStreamingPaused)spinner 不画帧——恢复 557e678 前的 spinner 隐形行为(只关 spinner,不动 contentWrite)。
185
+ */
186
+ export declare function paintLiveAtCursor(text: string): void;
187
+ /** 清掉 paintLiveAtCursor 画过的那行瞬时活动文本。清"实际画过的位置"(frameRow),非当前续写位。
188
+ * 不加 isStreamingPaused 守卫——stop 时必须无条件清(只写一次、结尾 cup 回输入框,不扰 IME);否则打字中 stop 会跳过清帧、制造泄漏。
189
+ * 滚动态(scrollOffset≠0)也必须清残留帧——否则旧 spinner 帧卡在历史视图某行(「思考中在消息堆里」根因)。 */
190
+ export declare function clearLiveAtCursor(): void;
191
+ /** 更新状态行基线(模型 / context / cwd / 模式标识 / 活跃 plan chip / 本轮 token chip)。repl 在轮次边界与切模式时调。 */
192
+ export declare function setStatusBase(b: {
193
+ model: string;
194
+ contextBar: string;
195
+ cwd: string;
196
+ modeTag?: string;
197
+ planSummary?: string;
198
+ lastTurnUsage?: {
199
+ promptTokens: number;
200
+ completionTokens: number;
201
+ totalTokens: number;
202
+ };
203
+ }): void;
204
+ /**
205
+ * 画输入区:擦旧菜单 → (必要时)setRegion → 画状态行 + 输入行 + 向上菜单,光标留输入框(dim 时回续写位)。
206
+ * prompt.ts 每次按键调;enterInputMode / enterRunningMode 也调(空 / dim)。
207
+ */
208
+ export declare function paintInput(view: InputView): void;
209
+ /**
210
+ * 运行态 typeahead 回显:定向写输入行(底栏输入框),把 dim 占位换成已打字文本 + 反白块状光标(无打字时光标在起点)。
211
+ * 只 cup 输入行 + clearLine + dim 文本 + 归位——不调 setRegion(运行中禁多行,避免 DECSTBM 抖动)、
212
+ * 不重画状态行/contentBottom、不用 ED。同步 lastView 为 dim 视图(text 与 placeholder 拆开存),使 scrollBy/resize
213
+ * 的 repaint 仍显当前回显 + 光标。真光标归续写位(滚动回看时归内容区底)——不入输入框,假光标已画在行内。
214
+ */
215
+ export declare function paintRunningInputEcho(text: string, placeholder: string): void;
216
+ /** 重画当前视图(resize / 内部用)。 */
217
+ export declare function repaint(): void;
218
+ /** 进入输入态:画空输入框 + 状态行,光标入输入框。 */
219
+ export declare function enterInputMode(status?: string): void;
220
+ /** 进入运行态:底栏输入行改 dim 占位,光标回续写位。footerH 恒 6(虚拟空+spinner行+上线+输入+下线+model行)。新轮回尾(确保新内容可见)。 */
221
+ export declare function enterRunningMode(status: string, placeholder: string): void;
222
+ export declare function enterAltScreen(): void;
223
+ /** 复原:复位 margins + 显光标 + 退 alt + 还原 raw。幂等。 */
224
+ export declare function exitAltScreen(): void;
225
+ export declare function isActive(): boolean;
package/dist/ui/layout.js CHANGED
@@ -443,6 +443,11 @@ export function contentInsertAfter(after, lines) {
443
443
  const delta = content.totalRows() - totalBefore;
444
444
  if (delta === 0)
445
445
  return;
446
+ // 流式 md 段活跃期间:插入点在段起点之前 → segmentStartRow 平移,
447
+ // 否则 contentWriteMd 的续写位算错,展开行被下一个 setLines 砍掉。
448
+ if (mdActive && after + 1 < segmentStartRow) {
449
+ segmentStartRow += delta;
450
+ }
446
451
  // 续写位:原写头绝对行 = viewport 起点 + (contentRow-1) [offset=0];偏移后才一致
447
452
  if (scrollOffset === 0 && contentRow > after + 1) {
448
453
  contentRow = Math.min(contentRow + delta, g.contentBottom);
@@ -469,6 +474,11 @@ export function contentDeleteFrom(startIdx, n) {
469
474
  const delta = totalBefore - content.totalRows();
470
475
  if (delta === 0)
471
476
  return;
477
+ // 流式 md 段活跃期间:删除区间全部在段起点之前 → segmentStartRow 回退,
478
+ // 否则 contentWriteMd 的续写位算错,段内容错位。
479
+ if (mdActive && startIdx + delta <= segmentStartRow) {
480
+ segmentStartRow = Math.max(1, segmentStartRow - delta);
481
+ }
472
482
  if (scrollOffset === 0 && contentRow > startIdx + 1) {
473
483
  contentRow = Math.max(1, contentRow - delta);
474
484
  }
@@ -0,0 +1,46 @@
1
+ /** 内联 token:可见文本 + 开头要套的 SGR(可叠加嵌套前缀;'' = 默认色)。 */
2
+ interface Seg {
3
+ sgr: string;
4
+ text: string;
5
+ }
6
+ /**
7
+ * ANSI 感知的硬切(**不补 …**):保留串内 SGR 码,按可见宽度截到 ≤ width,末尾补 reset
8
+ * 防断色。用于 markdown 代码行 / 超长单行带色块——绝不能补 …('…' 在中文终端算 2 宽会
9
+ * 触发 auto-wrap 整屏左移)。宽度未超直接原样返回。
10
+ */
11
+ declare function clipAnsiLine(str: string, width: number): string;
12
+ /**
13
+ * 内联 markdown → Seg[](有序 token 流)。支持:行内 code、bold、italic、strike、link。
14
+ * _italic_ 带词边界 flank 检查(避免 snake_case 被斜体)。emphasis 递归一层(粗体内可含
15
+ * 斜体/代码);code/link 内部当字面量(不再递归)。未配对的标记按字面量透传;反斜杠转义下一字符。
16
+ */
17
+ declare function renderInline(text: string): Seg[];
18
+ /**
19
+ * Seg[] → 自洽 ANSI 串(每个 seg 前 reset+开 sgr,行末 reset):样式不跨 seg 泄漏,
20
+ * 交给 wrapAnsiString 折行时由 activeSgr 跨断行重开。
21
+ */
22
+ declare function segsToAnsi(segs: Seg[]): string;
23
+ /**
24
+ * 把含 SGR / \n 的串按可见宽度软折成多行(每行可见宽 ≤ width)。等价 wrap-ansi 语义,零新依赖。
25
+ * - SGR 码 `\x1b[…m` 0 宽原样保留;`\x1b[0m`(reset)清 activeSgr,其余 SGR 累加到 activeSgr。
26
+ * - \n 为硬断行:flush 当前行(末尾补 reset),保留 activeSgr → 下一行重开同款样式
27
+ * (cli-highlight 多行串/块注释跨行着色不丢)。
28
+ * - 软折行断点:可见字符累加超 width → 当前行补 reset 收尾、新行首重发 activeSgr + 该字符。
29
+ * - 宽字符(CJK=2)在剩余放不下时整字折下行(同 wrapByDisplayWidth);零宽符(组合符)不计宽度。
30
+ * - 每行末尾补 reset(各自直出不串色);空行(源 \n\n 间)返 ''。
31
+ */
32
+ declare function wrapAnsiString(str: string, width: number): string[];
33
+ /**
34
+ * 把 markdown 文本渲染成 ANSI 物理行数组(每行 ansiDisplayWidth ≤ cols,自洽带色)。
35
+ * 纯函数 + LRU memo(key=text,值 {cols, lines, themeVersion}):同一 text 同一 cols 同一主题版本直接
36
+ * 命中(MRU 提升),cols 变(resize)或切主题(themeVersion 变)则重算覆写。供 layout.contentWriteMd
37
+ * 每 chunk 调用(memo 使重复渲染命中缓存)。themeVersion 使 setTheme 后全量重绘不命中旧主题缓存行。
38
+ */
39
+ export declare function renderMarkdown(text: string, cols: number): string[];
40
+ export declare const __test: {
41
+ clipAnsiLine: typeof clipAnsiLine;
42
+ renderInline: typeof renderInline;
43
+ wrapAnsiString: typeof wrapAnsiString;
44
+ segsToAnsi: typeof segsToAnsi;
45
+ };
46
+ export {};
@@ -0,0 +1,29 @@
1
+ export type MouseEvent = {
2
+ type: 'wheel';
3
+ dir: number;
4
+ } | {
5
+ type: 'press';
6
+ col: number;
7
+ row: number;
8
+ button: number;
9
+ } | {
10
+ type: 'drag';
11
+ col: number;
12
+ row: number;
13
+ button: number;
14
+ } | {
15
+ type: 'release';
16
+ col: number;
17
+ row: number;
18
+ button: number;
19
+ };
20
+ /** 注册事件回调(layout 进 alt 屏时注册)。 */
21
+ export declare function setHandler(fn: ((e: MouseEvent) => void) | null): void;
22
+ /** 复位内部收集状态(退出 alt 屏 / 测试间清污染)。 */
23
+ export declare function resetMouse(): void;
24
+ /**
25
+ * 喂入一个 keypress 的 key.sequence。
26
+ * 返回 true=本 keypress 是鼠标 fragment(或完整报表),调用方须 return 吞掉;
27
+ * 返回 false=非鼠标,放行正常处理。完整报表拼齐时同步派发 MouseEvent 给 handler。
28
+ */
29
+ export declare function swallow(seq: string): boolean;
@@ -0,0 +1,61 @@
1
+ export interface SlashCommand {
2
+ name: string;
3
+ desc: string;
4
+ }
5
+ export interface PromptOpts {
6
+ /** 纯文本 prompt(无 ANSI),如 '❯ '。 */
7
+ prompt: string;
8
+ /** 斜杠命令列表,仅用于菜单显示与过滤。 */
9
+ commands: SlashCommand[];
10
+ /** 预填初始行(运行中 typeahead 缓冲 → 下一轮 INPUT 态预填);光标置末行末尾。 */
11
+ initialLines?: string[];
12
+ /** Shift+Tab 循环切换 agent 模式(auto ↔ plan)的回调;repl 注入(翻 agentMode + 重写 history[0] + 设状态行 modeTag)。回调后 prompt 自调 redraw() 刷新底栏。 */
13
+ onCycleMode?: () => void;
14
+ }
15
+ /**
16
+ * 读多行输入;TUI 下经 layout.paintInput 把输入框画在固定底栏、斜杠菜单向上展开进内容区底。
17
+ * 换行:Ctrl+J / Alt+Enter / Shift+Enter(终端区分时)/ 粘贴的 LF。Enter 提交。返回行数组;null=空缓冲 Ctrl+D。
18
+ * 非 TTY 退化为 readline.question(单行,返回 [answer])。raw 模式下 Ctrl+C 先恢复终端再 reject('SIGINT')。
19
+ *
20
+ * 渲染全归 layout(prompt 只持有编辑状态:lines / 光标 / 菜单),prompt 不直接发 ANSI 区域控制,
21
+ * 避免 readline 光标错位与区域越界——颜色仅用在菜单行字符串里(由 layout 原样贴入)。
22
+ */
23
+ export declare function promptWithSlashMenu(opts: PromptOpts): Promise<string[] | null>;
24
+ /**
25
+ * 轮次选择菜单(供 /rollback 菜单化选择):↑/↓ 导航、Enter 选中、Esc/Ctrl+D 取消。
26
+ * 把 items 画成向上展开的菜单(经 layout.paintInput,与斜杠菜单同套渲染),输入框行作操作提示。
27
+ * 返回选中的 0-based 下标;null=取消 / 非 TTY / 空列表。纯导航(不收文本输入)。
28
+ * 长列表(超屏高)自动开窗保光标可见;默认聚焦末项(最新轮次,靠近输入框)。
29
+ */
30
+ export declare function promptTurnPicker(items: {
31
+ firstLine: string;
32
+ }[]): Promise<number | null>;
33
+ /**
34
+ * 会话选择菜单(供 /resume 菜单化选择):↑/↓ 导航、Enter 续接、Esc/Ctrl+D 取消、a 切换「仅最近 N / 全部」。
35
+ * 把 items 画成向上展开的菜单(经 layout.paintInput,与斜杠菜单 / 轮次菜单同套渲染),输入框行作操作提示。
36
+ * 默认仅显示最近 N 条(默认 N=10,取 items 前缀——调用方按 createdAt 降序传入则前缀=最新 N);
37
+ * items 多于 N 时按 a 展开全部,再按 a 折回最近 N。选中项 cyan+bold + ▸ 高亮(同 promptTurnPicker)。
38
+ * 返回选中的 item(含 id);null=取消 / 非 TTY / 空列表。纯导航(不收文本输入)。
39
+ * 长列表(超屏高)自动开窗保光标可见;默认聚焦首项(最新会话,Enter 即续接最近一条)。
40
+ */
41
+ export interface SessionPickerItem {
42
+ id: string;
43
+ title: string;
44
+ subtitle?: string;
45
+ }
46
+ export declare function promptSessionPicker(items: SessionPickerItem[], recentCap?: number): Promise<SessionPickerItem | null>;
47
+ /**
48
+ * 主题选择菜单(供 /theme):↑/↓ 导航、Enter 切换、Esc/Ctrl+D 取消、Ctrl+C 抛 SIGINT(调用方 try/catch)。
49
+ * 复用 SessionPickerItem 形状({id,title,subtitle?});选中项 cyan+bold + ▸ 高亮(同 /resume 菜单)。
50
+ * 返回选中的 item(含 id);null=取消 / 非 TTY / 空列表。纯导航(不收文本输入)。
51
+ * 长列表超屏高自动开窗保光标可见;默认聚焦首项。
52
+ */
53
+ export declare function promptThemePicker(items: SessionPickerItem[]): Promise<SessionPickerItem | null>;
54
+ /**
55
+ * 回滚方式二选一菜单(供 /rollback 选完轮次后):↑/↓ 导航、Enter 确认、Esc/Ctrl+D 保留文件。
56
+ * 两个选项:1=撤销文件改动(恢复到回滚前);2=只撤销消息,保留文件改动。
57
+ * 复用轮次/会话菜单的渲染骨架(raw mode + layout.paintInput 的 menu),选中项 cyan+bold + ▸ 高亮。
58
+ * 返回 true=撤销文件 / false=只撤销消息 / null=取消(调用方按 false=保留文件处理)。
59
+ * 默认聚焦首项(撤销文件)。无文件改动时调用方不应调此菜单(直接走只撤销消息)。
60
+ */
61
+ export declare function promptRevertChoice(fileCount: number): Promise<boolean | null>;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 清空整屏 + 滚动缓冲(向上滚动可见的历史输出),光标归位。
3
+ * 进入会话时调用,让终端只剩当前 agent 对话。非 TTY 时空操作。
4
+ */
5
+ export declare function clearScreen(): void;
6
+ export declare function charWidth(cp: number): number;
7
+ export declare function displayWidth(str: string): number;
8
+ /**
9
+ * 毫秒 → Claude Code 式耗时串:运行中状态行走时与轮次结束摘要行共用。
10
+ * <10s 显 1 位小数(3.2s);10-59s 整数(12s);≥60s m+s(3m 3s);≥1h h+m(1h 2m)。
11
+ */
12
+ export declare function fmtElapsed(ms: number): string;
13
+ /** 去除 SGR 颜色转义(\x1B[…m),用于度量带色串的真实可见宽度。 */
14
+ export declare function stripAnsi(s: string): string;
15
+ /** 按显示列范围 [start, end) 截取纯文本(不含 ANSI,按 charWidth 计位,CJK/宽字符占 2)。
16
+ * 跨宽字符边界时整字符归入(w+cw>start 即含入),供鼠标选区文本提取用。 */
17
+ export declare function sliceByDisplayCol(str: string, start: number, end: number): string;
18
+ /** 把可视列(0-based)反推为 str 内的字符偏移(JS 字符串下标,UTF-16 code unit)。
19
+ * 点击落在宽字符的"列中段"时归到该字符之前的偏移(停在它左侧边界)——与终端光标行为一致。
20
+ * visCol 大于串的显示宽度时返回 str.length(点行末的"右边")。
21
+ * 输入框点击定位专用;与 sliceByDisplayCol 互为逆,但语义只关心单点、不关心区间。 */
22
+ export declare function visColToCharCol(str: string, visCol: number): number;
23
+ /** 带色串的可见显示宽度(先去 ANSI 再按 displayWidth 度量)。 */
24
+ export declare function ansiDisplayWidth(s: string): number;
25
+ /** 按显示宽度右补空格。 */
26
+ export declare function padEndDisplay(str: string, width: number): string;
27
+ /** 带色串按可见宽度右补空格(先剥离 ANSI 算真实宽度,空格补在串末,不破坏颜色码)。 */
28
+ export declare function padEndAnsi(str: string, width: number): string;
29
+ /** 带色串按可见宽度截断(保留中间 ANSI 码,超出末尾加 …,补 reset 防 … 继承颜色)。 */
30
+ export declare function truncateAnsi(str: string, width: number): string;
31
+ /** 按显示宽度截断,超出加 …。 */
32
+ export declare function truncateDisplay(str: string, width: number): string;
33
+ /**
34
+ * 按显示宽度从头部截断(保留尾部),超出前缀加 …。
35
+ * 用于单行只读预览需要始终看到最新内容的场景(如运行态输入回显:光标恒在文本末尾,
36
+ * 超长时应保留刚打的字,而非 truncateDisplay 那样保留开头、把刚打的内容截没)。
37
+ */
38
+ export declare function truncateDisplayHead(str: string, width: number): string;
39
+ /**
40
+ * 按显示宽度把文本软折行为多行(供输入框软换行):每行可见宽度 ≤ width。
41
+ * 宽字符(CJK / emoji = 2)在剩余宽度放不下时整字折到下行(留尾部空格,与终端自动折行一致),
42
+ * 而非劈开半个字。零宽字符(组合符等)不计宽度、附在当前行。空串返回 [''](占一行)。
43
+ * 输入文本应全为可见字符(无控制码 / SGR)。
44
+ */
45
+ export declare function wrapByDisplayWidth(text: string, width: number): string[];
46
+ export interface BannerInfo {
47
+ model: string;
48
+ baseURL: string;
49
+ cwd: string;
50
+ tools: string;
51
+ }
52
+ /** 横幅纯文本(带 ANSI 颜色,不写出)——供 TUI 经 contentWrite 写入内容区以跟踪续写位。
53
+ * 无边框:左侧实心小熊 + 右侧标题/信息(模型/目录),末尾一行提示。 */
54
+ export declare function bannerString(info: BannerInfo): string;
55
+ /** 启动横幅:小熊 logo + 标题/信息 + 一行提示。纯渲染,不依赖 config / 业务。 */
56
+ export declare function printBanner(info: BannerInfo): void;
57
+ /** 把工具调用的 JSON 参数提炼成人可读的一行(路径 / 模式 / 命令)。 */
58
+ export declare function summarizeToolCall(name: string, argsRaw: string): string;
59
+ /** 工具结果的人可读一行预览(行数 / 匹配数 / 首行);喂回 LLM 的全文不变。 */
60
+ export declare function summarizeToolResult(name: string, output: string): string;