@aiwayds/dsh-tui-pi 2.6.0 → 2.7.1

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
@@ -2,11 +2,11 @@
2
2
 
3
3
  # dsh-tui-pi
4
4
 
5
- pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — a plugin suite that turns dsh into a pi-like coding-agent experience: pi-tui look & feel, dsh slash commands, GitHub light/dark themes and a powerline footer.
5
+ A fully-featured pi-style terminal UI for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) — a plugin suite that turns dsh into a pi-like coding-agent experience: /history look-back & fork-at-turn, guided preset switching, live subagent steering, model profiles, GitHub light/dark themes and a powerline footer.
6
6
 
7
7
  **Requires dsh >= 0.1.2-rc.1** — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the `latest`/`next` dist-tags at runtime). **The alpha line is no longer supported.** A startup guard logs a one-line warning and exits cleanly when the host is older than the floor (opt out with `DSH_TUI_SKIP_HOST_CHECK=1`). See [ADR 0002](docs/adr/0002-target-dsh-0.1.2-alpha.3-single-target.md) for the now-superseded alpha single-target decision.
8
8
 
9
- https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
9
+ https://github.com/user-attachments/assets/67a7c6ca-ff42-4005-b543-437ba61771bb
10
10
 
11
11
  *A live recording of a session (MP4, 1.5× speed) — todos, running subagents, think/tool panels and the powerline footer in action.*
12
12
 
@@ -22,7 +22,7 @@ https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
22
22
  - [**Dynamic context pruning (DCP)**](docs/features/dcp.md) — context stays within limits automatically, with zero LLM calls.
23
23
  - [**Persistent context**](docs/features/persistent-context.md) — your ground rules ride along on every request, hot-applied with no restart.
24
24
  - [**Model profiles & favorites**](docs/features/model-profiles.md) — switch a whole model setup per project and keep the picker small.
25
- - [**Agent preset switching**](docs/features/preset-switch.md) — `Tab` / `/preset` between the shipped agent compositions (`standard`, `minimal`, …); what a preset really gates, and exactly when a switch takes effect.
25
+ - [**Agent preset switching**](docs/features/preset-switch.md) — `/preset` between the shipped agent compositions (`standard`, `minimal`, …); a switch is confirmed and starts a NEW session on the preset (the current one stays resumable); what a preset really gates.
26
26
  - [**Sessions & resume**](docs/features/sessions-resume.md) — sessions stay tidy automatically and resume in a few keystrokes; a cross-process writer guard keeps the log single-writer.
27
27
  - [**History browser**](docs/features/history.md) — `/history` opens a fixed two-pane look-back over the session: completed turns on the left, the selected turn's replies on the right; copy a prompt back to the editor, or cold-read any stored session without resuming it (read-only, no writer lock).
28
28
  - [**Themes**](docs/features/themes.md) — GitHub light/dark palettes, hot-switchable; `auto` follows your terminal.
@@ -50,6 +50,33 @@ node scripts/dev-upgrade.mjs 1.0.5 --dry-run # preview the plan first
50
50
 
51
51
  ---
52
52
 
53
+ ## Uninstall
54
+
55
+ ```sh
56
+ dsh plugin --profile <name> remove @aiwayds/dsh-tui-pi
57
+ ```
58
+
59
+ The `dsh-tui-pi` bin shim is global and can stay; if you installed the package globally and want it gone too: `npm -g rm @aiwayds/dsh-tui-pi`.
60
+
61
+ The host cleans up the profile automatically: the `dsh.profile.bundles` entry is spliced and the whole patch layer goes away with the package — the stock `session-projection-cache` row re-enables, and the projcache wrapper, the `tool-ask-user` insert and its disable row all vanish.
62
+
63
+ What stays on disk on purpose (deleting user data is destructive; a reinstall reuses all of it):
64
+
65
+ - `~/.dsh/APPEND_SYSTEM.md` — auto-seeded system-prompt appendix (plugin-owned; delete by hand if unwanted)
66
+ - `~/.dsh/tui-command-usage.json` — slash-command usage ranking
67
+ - `~/.dsh/model-profiles.json` — model profiles (SHARED with other plugins — dsh-subagent-registry reads it)
68
+ - `~/.dsh/keybindings.json` — the dsh-tui app-key rows (host-shared file)
69
+ - `~/.dsh/agents/*.md` and `~/.dsh/skills/` — user-editable agents/skills (shared with other plugins)
70
+ - `.dsh-profile` pin files in project workspaces (written by /model profile pinning)
71
+ - the `dsh-tui:` section of `~/.dsh/settings.yaml` — theme/panel/footer/retention/subagent limits
72
+ - `~/.dsh/storages/session_projcache/` — the session projection cache, incl. `.bak-preflight-*` migration backups
73
+
74
+ While the plugin runs, the retention janitor (default `maxCount: 100` / `maxAgeDays: 7`, configurable in the `dsh-tui` settings) deletes old session logs — uninstalling stops that, but already-deleted logs are gone.
75
+
76
+ `scripts/install-font.mjs` mutates OS font/terminal state and has a documented backup; uninstall doesn't touch it.
77
+
78
+ ---
79
+
53
80
  ## Companion plugins
54
81
 
55
82
  **Default dependencies** — the eight plugins below ship with this package (installed into the profile's `node_modules`); activation still follows the profile's `bundles` list — list each one there to activate it.
@@ -85,7 +112,6 @@ dsh plugin --profile tui add @aiwayds/dsh-topics-memory
85
112
  | `Ctrl+G` | Open the subagent picker (viewer `Enter` opens steer) |
86
113
  | `Ctrl+O` | Pending-message queue (s steer now · d remove) |
87
114
  | `Ctrl+Shift+F` | Transcript search (`Enter`/`Ctrl+G` next · `Shift+Enter`/`Ctrl+Shift+G` previous · `Esc` close) |
88
- | `Tab` | Cycle agent presets |
89
115
  | `↑` / `↓` | Browse submitted-message history |
90
116
 
91
117
  Remap any app key through `~/.dsh/keybindings.json` (a partial JSON map, live-applied) or interactively with `/hotkeys`.
@@ -116,7 +142,7 @@ Other knobs: `dsh-tui.panelHeight` (think/tool panel height), `dsh-tui.iconSet`
116
142
  ```sh
117
143
  pnpm check # tsc --noEmit
118
144
  pnpm build # emit lib/
119
- pnpm test # unit tests, node --test against lib/ (pretest builds; 1,100+ tests across 60+ files — see HANDOFF.md for the current baseline)
145
+ pnpm test # unit tests, node --test against lib/ (pretest builds; 1,100+ tests across 60+ files — the current baseline lives in AGENTS.md)
120
146
  ```
121
147
 
122
148
  `pi-tui` runs pristine from npm — no patches, no fork. See [AGENTS.md](AGENTS.md) for the iron rules and quality gates.
@@ -127,7 +153,6 @@ pnpm test # unit tests, node --test against lib/ (pretest builds; 1,100+ tes
127
153
 
128
154
  - [docs/features/](docs/features/) — one doc per feature, with demo videos.
129
155
  - [ARCHITECTURE.md](ARCHITECTURE.md) — full design: process model, layers, data flow.
130
- - [HANDOFF.md](HANDOFF.md) — session history and current state (Chinese).
131
156
  - [CHANGELOG.md](CHANGELOG.md) — release history.
132
157
  - [AGENTS.md](AGENTS.md) — working conventions and quality gates for contributors.
133
158
  - [docs/](docs/) — design notes (steer/follow-up flow, showcase drafts, …).
package/README.zh-CN.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  # dsh-tui-pi
4
4
 
5
- 面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的 pi 风格终端 UI——一套把 dsh 变成 pi 式编码代理体验的插件套件:pi-tui 的外观与交互、dsh 斜杠命令、GitHub 明/暗主题和 powerline 状态栏。
5
+ 面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的功能齐全的 pi 风格终端 UI——一套把 dsh 变成 pi 式编码代理体验的插件套件:/history 回看与任意轮分叉、确认式 preset 切换、subagent 实时操控、模型档案、GitHub 明/暗主题和 powerline 状态栏。
6
6
 
7
7
  **要求 dsh >= 0.1.2-rc.1** —— 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。**不再支持 alpha 线。**宿主低于下限时,启动守卫会打一行 warning 并干净退出(可用 `DSH_TUI_SKIP_HOST_CHECK=1` 跳过)。alpha 单目标决策的历史见 [ADR 0002](docs/adr/0002-target-dsh-0.1.2-alpha.3-single-target.md)(已被取代)。
8
8
 
9
- https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
9
+ https://github.com/user-attachments/assets/67a7c6ca-ff42-4005-b543-437ba61771bb
10
10
 
11
11
  *一次真实 session 的实况录制(MP4,1.5× 速度)——todos、运行中的 subagents、think/tool 面板和 powerline footer 的实际效果。*
12
12
 
@@ -22,7 +22,7 @@ https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
22
22
  - [**动态上下文修剪(DCP)**](docs/features/dcp.md) —— 上下文自动保持在窗口内,零 LLM 调用。
23
23
  - [**持久上下文**](docs/features/persistent-context.md) —— 你的基本规则随每个请求生效,热应用无需重启。
24
24
  - [**模型 profile 与收藏**](docs/features/model-profiles.md) —— 按项目切换整套模型配置,选择器保持精简。
25
- - [**Agent preset 切换**](docs/features/preset-switch.md) —— `Tab` / `/preset` 在内置 agent 组合(`standard`、`minimal`……)间切换;preset 到底管什么、切换何时生效。
25
+ - [**Agent preset 切换**](docs/features/preset-switch.md) —— `/preset` 在内置 agent 组合(`standard`、`minimal`……)间切换;切换需确认并会开启新会话(当前会话仍可 /resume 恢复);preset 到底管什么。
26
26
  - [**Sessions 会话与恢复**](docs/features/sessions-resume.md) —— 会话自动保持整洁、几次按键恢复;跨进程写者守卫保证日志单写者。
27
27
  - [**Themes 主题**](docs/features/themes.md) —— GitHub 明/暗配色热切换;`auto` 跟随终端。
28
28
  - [**搜索、选择与图片**](docs/features/search-selection-images.md) —— `Ctrl+Shift+F` 全文搜索、划选复制到系统剪贴板、web/飞书附件内联渲染、LaTeX 转 Unicode 数学。
@@ -47,6 +47,33 @@ node scripts/dev-upgrade.mjs 1.0.5 --dry-run # 先预览执行计划
47
47
 
48
48
  ---
49
49
 
50
+ ## 卸载
51
+
52
+ ```sh
53
+ dsh plugin --profile <name> remove @aiwayds/dsh-tui-pi
54
+ ```
55
+
56
+ `dsh-tui-pi` 启动器是全局 bin shim,留着无妨;若还全局安装过本包,用 `npm -g rm @aiwayds/dsh-tui-pi` 一并移除。
57
+
58
+ 宿主会自动清理 profile:`dsh.profile.bundles` 条目被剪除,整层 patch 随包一起消失——原生的 `session-projection-cache` 行恢复启用,projcache wrapper、`tool-ask-user` 插入及其禁用行全部随之消失。
59
+
60
+ 以下内容刻意留在磁盘上(删用户数据是破坏性的;重装会全部复用):
61
+
62
+ - `~/.dsh/APPEND_SYSTEM.md` —— 自动播种的系统提示词附录(插件所有;不想要就手动删除)
63
+ - `~/.dsh/tui-command-usage.json` —— 斜杠命令使用排行
64
+ - `~/.dsh/model-profiles.json` —— 模型档案(与其他插件共享——dsh-subagent-registry 也读它)
65
+ - `~/.dsh/keybindings.json` —— dsh-tui 的 app 按键行(宿主共享文件)
66
+ - `~/.dsh/agents/*.md` 与 `~/.dsh/skills/` —— 用户可编辑的 agents/skills(与其他插件共享)
67
+ - 项目工作区里的 `.dsh-profile` 固定文件(由 /model profile 固定时写入)
68
+ - `~/.dsh/settings.yaml` 的 `dsh-tui:` 段 —— 主题/面板/footer/保留策略/subagent 限制
69
+ - `~/.dsh/storages/session_projcache/` —— 会话投影缓存,含 `.bak-preflight-*` 迁移备份
70
+
71
+ 插件运行期间,保留清理器(默认 `maxCount: 100` / `maxAgeDays: 7`,可在 `dsh-tui` 设置中调整)会删除旧会话日志——卸载后即停止,但已删除的日志找不回来。
72
+
73
+ `scripts/install-font.mjs` 会改动 OS 字体/终端状态且有文档化的备份;卸载不会碰它。
74
+
75
+ ---
76
+
50
77
  ## Companion plugins 伴生插件
51
78
 
52
79
  **默认依赖** —— 以下 8 个插件随本包自带(安装进 profile 的 `node_modules`);激活仍以 profile 的 `bundles` 列表为准——把要用的逐个列进去即可。
@@ -82,7 +109,6 @@ dsh plugin --profile tui add @aiwayds/dsh-topics-memory
82
109
  | `Ctrl+G` | 打开 subagent 选择器(查看器内 `Enter` 打开 steer) |
83
110
  | `Ctrl+O` | 待发消息队列(`s` 立即 steer · `d` 移除) |
84
111
  | `Ctrl+Shift+F` | 全文搜索(`Enter`/`Ctrl+G` 下一个 · `Shift+Enter`/`Ctrl+Shift+G` 上一个 · `Esc` 关闭) |
85
- | `Tab` | 循环切换 agent preset |
86
112
  | `↑` / `↓` | 浏览已提交消息历史 |
87
113
 
88
114
  通过 `~/.dsh/keybindings.json`(部分 JSON 映射,实时应用)或 `/hotkeys` 交互式重映射任意 app 按键。
@@ -113,7 +139,7 @@ dsh-tui:
113
139
  ```sh
114
140
  pnpm check # tsc --noEmit
115
141
  pnpm build # 输出 lib/
116
- pnpm test # 单元测试,node --test 对 lib/ 执行(pretest 构建;1100+ 测试、60+ 文件——当前基数见 HANDOFF.md)
142
+ pnpm test # 单元测试,node --test 对 lib/ 执行(pretest 构建;1100+ 测试、60+ 文件——当前基数见 AGENTS.md)
117
143
  ```
118
144
 
119
145
  `pi-tui` 从 npm 原样运行——无补丁、无 fork。铁律与质量门禁见 [AGENTS.md](AGENTS.md)。
@@ -124,7 +150,6 @@ pnpm test # 单元测试,node --test 对 lib/ 执行(pretest 构建;11
124
150
 
125
151
  - [docs/features/](docs/features/) —— 每个功能一篇独立文档,附演示视频(英文)。
126
152
  - [ARCHITECTURE.md](ARCHITECTURE.md) —— 完整设计:进程模型、分层、数据流。
127
- - [HANDOFF.md](HANDOFF.md) —— 会话历史与当前状态(中文)。
128
153
  - [CHANGELOG.md](CHANGELOG.md) —— 发布历史。
129
154
  - [AGENTS.md](AGENTS.md) —— 贡献者的工作约定与质量门禁。
130
155
  - [docs/](docs/) —— 设计笔记(steer/follow-up 流程、showcase 草稿……)。
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The fork-at-turn confirmation dialog — the /history browser's `f` key.
3
+ *
4
+ * Forking at a turn starts a NEW session seeded with the browsed session's
5
+ * events through the selected turn's `turn/end` (inclusive) and switches to
6
+ * it; the current session stays untouched and resumable via /resume. That is
7
+ * a user-visible, hard-to-reverse-looking action, so it is gated behind this
8
+ * confirmation (mirroring the /resume repair dialog's pure-reducer split,
9
+ * src/repair-dialog.ts, so the decision matrix stays unit-testable without a
10
+ * terminal). Two options — Fork now / Cancel — Esc = cancel.
11
+ *
12
+ * The dialog mounts as its own overlay ON TOP of the open browser (the
13
+ * browser stays mounted underneath and keeps the keyboard after the dialog
14
+ * resolves; the switch itself runs after confirmation).
15
+ */
16
+ import { type Component, type TUI } from '@earendil-works/pi-tui';
17
+ import { type TuiTheme } from './theme/index.ts';
18
+ /** The two choices in display order; index 0 is preselected. */
19
+ export declare const FORK_AT_TURN_OPTION_IDS: ReadonlyArray<'fork' | 'cancel'>;
20
+ /** Accent-BOLD dialog title (`N` = the selected turn's number). */
21
+ export declare function forkAtTurnTitle(turnLabel: string): string;
22
+ /**
23
+ * The fixed body points, one entry per bullet — each wrapped to the terminal
24
+ * width by the panel before painting. `N` is the selected turn's number,
25
+ * `M` the session's total turn count (as listed). `detachedLiveId` is set
26
+ * when the browsed session is NOT the live one (a cold read): forking then
27
+ * detaches the LIVE session, and the body must say so — "the current
28
+ * session" alone would read as the browsed one and hide the live session's
29
+ * fate. Undefined = live browse (or nothing live at all), where the generic
30
+ * wording is exact.
31
+ */
32
+ export declare function forkAtTurnBody(turnLabel: string, totalTurns: number, detachedLiveId?: string): readonly string[];
33
+ /** The fixed option rows for one target turn. */
34
+ export declare function forkAtTurnOptions(turnLabel: string): ReadonlyArray<{
35
+ id: 'fork' | 'cancel';
36
+ text: string;
37
+ }>;
38
+ /** Footer hint — hardcoded like every other panel footer (English-only). */
39
+ export declare const FORK_AT_TURN_FOOTER = "\u2191\u2193 select \u00B7 1/2 pick \u00B7 Enter confirm \u00B7 Esc cancel";
40
+ /** Pure dialog state: which row is highlighted, and the terminal outcome. */
41
+ export interface ForkAtTurnState {
42
+ selected: number;
43
+ /**
44
+ * Set once by a terminal key: `'confirm'` (Enter on a selection) or
45
+ * `'cancel'` (Esc). Further input is ignored afterwards.
46
+ */
47
+ settled?: 'confirm' | 'cancel';
48
+ }
49
+ export declare function initialForkAtTurnState(): ForkAtTurnState;
50
+ /**
51
+ * Apply one raw key sequence to the dialog state. Unknown keys are no-ops;
52
+ * anything after a settle is ignored (single terminal outcome guard).
53
+ */
54
+ export declare function updateForkAtTurn(state: ForkAtTurnState, data: string): ForkAtTurnState;
55
+ /** Resolved dialog outcome: the chosen action, or undefined on cancel. */
56
+ export declare function forkAtTurnOutcome(state: ForkAtTurnState): 'fork' | 'cancel' | undefined;
57
+ /**
58
+ * The framed overlay component. Renders the title, the wrapped body points
59
+ * and the two option rows; every key goes through {@link updateForkAtTurn},
60
+ * and the first terminal key fires `onFinish` exactly once.
61
+ */
62
+ export declare class ForkAtTurnPanel implements Component {
63
+ private readonly theme;
64
+ private readonly turnLabel;
65
+ private readonly totalTurns;
66
+ private readonly detachedLiveId;
67
+ private readonly onFinish;
68
+ private readonly requestRenderFn;
69
+ private state;
70
+ constructor(theme: TuiTheme, turnLabel: string, totalTurns: number, detachedLiveId: string | undefined, onFinish: (outcome: 'fork' | 'cancel' | undefined) => void, requestRender: () => void);
71
+ invalidate(): void;
72
+ render(width: number): string[];
73
+ handleInput(data: string): void;
74
+ }
75
+ /**
76
+ * Open the fork-at-turn confirmation dialog over the OPEN browser (its own
77
+ * overlay — the browser stays mounted underneath and regains the keyboard
78
+ * when the dialog resolves). Resolves `'fork'` when the user explicitly
79
+ * confirmed, `'cancelled'` otherwise (Esc, or an overlay that failed to
80
+ * mount — treated as cancel so a half-mounted dialog can never imply
81
+ * consent). Closing hands focus back through `restoreFocus` first.
82
+ */
83
+ export declare function openForkAtTurnDialog(tui: TUI, theme: TuiTheme, turnLabel: string, totalTurns: number, detachedLiveId: string | undefined, restoreFocus: () => void): Promise<'fork' | 'cancelled'>;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * The fork-at-turn confirmation dialog — the /history browser's `f` key.
3
+ *
4
+ * Forking at a turn starts a NEW session seeded with the browsed session's
5
+ * events through the selected turn's `turn/end` (inclusive) and switches to
6
+ * it; the current session stays untouched and resumable via /resume. That is
7
+ * a user-visible, hard-to-reverse-looking action, so it is gated behind this
8
+ * confirmation (mirroring the /resume repair dialog's pure-reducer split,
9
+ * src/repair-dialog.ts, so the decision matrix stays unit-testable without a
10
+ * terminal). Two options — Fork now / Cancel — Esc = cancel.
11
+ *
12
+ * The dialog mounts as its own overlay ON TOP of the open browser (the
13
+ * browser stays mounted underneath and keeps the keyboard after the dialog
14
+ * resolves; the switch itself runs after confirmation).
15
+ */
16
+ import { getKeybindings } from '@earendil-works/pi-tui';
17
+ import { PanelHost, panelThemeFns } from "./panels.js";
18
+ import { BOLD, RESET, ansiFg } from "./theme/index.js";
19
+ import { clipToWidth, wrapText } from "./text.js";
20
+ /** The two choices in display order; index 0 is preselected. */
21
+ export const FORK_AT_TURN_OPTION_IDS = ['fork', 'cancel'];
22
+ /** Accent-BOLD dialog title (`N` = the selected turn's number). */
23
+ export function forkAtTurnTitle(turnLabel) {
24
+ return `● Fork at turn ${turnLabel}?`;
25
+ }
26
+ /**
27
+ * The fixed body points, one entry per bullet — each wrapped to the terminal
28
+ * width by the panel before painting. `N` is the selected turn's number,
29
+ * `M` the session's total turn count (as listed). `detachedLiveId` is set
30
+ * when the browsed session is NOT the live one (a cold read): forking then
31
+ * detaches the LIVE session, and the body must say so — "the current
32
+ * session" alone would read as the browsed one and hide the live session's
33
+ * fate. Undefined = live browse (or nothing live at all), where the generic
34
+ * wording is exact.
35
+ */
36
+ export function forkAtTurnBody(turnLabel, totalTurns, detachedLiveId) {
37
+ return [
38
+ `The new session carries turns through ${turnLabel} (of ${totalTurns}); later turns stay in the current session.`,
39
+ detachedLiveId === undefined
40
+ ? 'The current session stays resumable via /resume.'
41
+ : `Your live session ${detachedLiveId} will be detached — it stays resumable via /resume.`,
42
+ ];
43
+ }
44
+ /** The fixed option rows for one target turn. */
45
+ export function forkAtTurnOptions(turnLabel) {
46
+ return [
47
+ { id: 'fork', text: `Fork now — new session through turn ${turnLabel}` },
48
+ { id: 'cancel', text: 'Cancel' },
49
+ ];
50
+ }
51
+ /** Footer hint — hardcoded like every other panel footer (English-only). */
52
+ export const FORK_AT_TURN_FOOTER = '↑↓ select · 1/2 pick · Enter confirm · Esc cancel';
53
+ export function initialForkAtTurnState() {
54
+ return { selected: 0 };
55
+ }
56
+ /**
57
+ * Apply one raw key sequence to the dialog state. Unknown keys are no-ops;
58
+ * anything after a settle is ignored (single terminal outcome guard).
59
+ */
60
+ export function updateForkAtTurn(state, data) {
61
+ if (state.settled !== undefined)
62
+ return state;
63
+ const kb = getKeybindings();
64
+ if (kb.matches(data, 'tui.select.cancel'))
65
+ return { ...state, settled: 'cancel' };
66
+ if (kb.matches(data, 'tui.input.submit'))
67
+ return { ...state, settled: 'confirm' };
68
+ if (kb.matches(data, 'tui.select.up')) {
69
+ return { ...state, selected: Math.max(0, state.selected - 1) };
70
+ }
71
+ if (kb.matches(data, 'tui.select.down')) {
72
+ return { ...state, selected: Math.min(FORK_AT_TURN_OPTION_IDS.length - 1, state.selected + 1) };
73
+ }
74
+ // Digit direct-select (1-based): selects the row, Enter still confirms —
75
+ // same select-then-confirm split as the routing dialog.
76
+ const digit = /^([1-9])$/.exec(data);
77
+ if (digit !== null) {
78
+ const index = Number(digit[1]) - 1;
79
+ if (index < FORK_AT_TURN_OPTION_IDS.length)
80
+ return { ...state, selected: index };
81
+ }
82
+ return state;
83
+ }
84
+ /** Resolved dialog outcome: the chosen action, or undefined on cancel. */
85
+ export function forkAtTurnOutcome(state) {
86
+ if (state.settled !== 'confirm')
87
+ return undefined;
88
+ return FORK_AT_TURN_OPTION_IDS[state.selected];
89
+ }
90
+ /**
91
+ * The framed overlay component. Renders the title, the wrapped body points
92
+ * and the two option rows; every key goes through {@link updateForkAtTurn},
93
+ * and the first terminal key fires `onFinish` exactly once.
94
+ */
95
+ export class ForkAtTurnPanel {
96
+ theme;
97
+ turnLabel;
98
+ totalTurns;
99
+ detachedLiveId;
100
+ onFinish;
101
+ requestRenderFn;
102
+ state = initialForkAtTurnState();
103
+ constructor(theme, turnLabel, totalTurns, detachedLiveId, onFinish, requestRender) {
104
+ this.theme = theme;
105
+ this.turnLabel = turnLabel;
106
+ this.totalTurns = totalTurns;
107
+ this.detachedLiveId = detachedLiveId;
108
+ this.onFinish = onFinish;
109
+ this.requestRenderFn = requestRender;
110
+ }
111
+ invalidate() { }
112
+ render(width) {
113
+ const fns = panelThemeFns(this.theme);
114
+ const wrap = Math.max(2, width - 2);
115
+ // Word-wrap the body FIRST, then paint (iron rule: width math runs on
116
+ // plain text, ANSI goes on after clipping).
117
+ const lines = [
118
+ fns.accent(BOLD + clipToWidth(forkAtTurnTitle(this.turnLabel), wrap) + RESET),
119
+ ...forkAtTurnBody(this.turnLabel, this.totalTurns, this.detachedLiveId).flatMap(point => wrapText(point, wrap).map(segment => fns.muted(clipToWidth(segment, wrap)))),
120
+ '',
121
+ ];
122
+ const options = forkAtTurnOptions(this.turnLabel);
123
+ for (let i = 0; i < options.length; i++) {
124
+ const option = options[i];
125
+ const marker = i === this.state.selected ? '▸' : ' ';
126
+ const row = clipToWidth(`${marker} ${i + 1}. ${option.text}`, wrap);
127
+ lines.push(i === this.state.selected
128
+ ? ansiFg(this.theme.palette.accent) + BOLD + row + RESET
129
+ : fns.muted(row));
130
+ }
131
+ lines.push('');
132
+ lines.push(fns.subtle(clipToWidth(FORK_AT_TURN_FOOTER, wrap)));
133
+ return lines;
134
+ }
135
+ handleInput(data) {
136
+ const previous = this.state;
137
+ this.state = updateForkAtTurn(this.state, data);
138
+ if (this.state === previous)
139
+ return;
140
+ if (this.state.settled === undefined) {
141
+ this.requestRenderFn();
142
+ return;
143
+ }
144
+ this.onFinish(forkAtTurnOutcome(this.state));
145
+ }
146
+ }
147
+ /**
148
+ * Open the fork-at-turn confirmation dialog over the OPEN browser (its own
149
+ * overlay — the browser stays mounted underneath and regains the keyboard
150
+ * when the dialog resolves). Resolves `'fork'` when the user explicitly
151
+ * confirmed, `'cancelled'` otherwise (Esc, or an overlay that failed to
152
+ * mount — treated as cancel so a half-mounted dialog can never imply
153
+ * consent). Closing hands focus back through `restoreFocus` first.
154
+ */
155
+ export function openForkAtTurnDialog(tui, theme, turnLabel, totalTurns, detachedLiveId, restoreFocus) {
156
+ return new Promise(resolve => {
157
+ let settled = false;
158
+ const settle = (outcome) => {
159
+ if (settled)
160
+ return;
161
+ settled = true;
162
+ resolve(outcome);
163
+ };
164
+ // A half-mounted overlay must not strand the keyboard: PanelHost's
165
+ // onError closes + calls restoreFocus, then we settle as cancelled.
166
+ const host = new PanelHost(tui, theme, () => {
167
+ restoreFocus();
168
+ settle('cancelled');
169
+ });
170
+ const finish = (outcome) => {
171
+ host.close();
172
+ restoreFocus();
173
+ settle(outcome === 'fork' ? 'fork' : 'cancelled');
174
+ };
175
+ const panel = new ForkAtTurnPanel(theme, turnLabel, totalTurns, detachedLiveId, outcome => finish(outcome), () => tui.requestRender());
176
+ // maxHeight is a hard slice in pi-tui: title + 2 wrapped body points +
177
+ // 2 options + footer ≈ 8 content rows + 4 frame rows; 75% of the 24-row
178
+ // e2e floor is 18 (the /resume picker's headroom).
179
+ host.open(panel, '70%', '75%');
180
+ });
181
+ }
182
+ //# sourceMappingURL=history-fork.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-fork.js","sourceRoot":"","sources":["../src/history-fork.ts"],"names":[],"mappings":"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"}
@@ -83,3 +83,17 @@ export declare function matchesTurnFilter(turn: HistoryTurn, query: string): boo
83
83
  * in first-appearance order. Empty string for a tool-less turn.
84
84
  */
85
85
  export declare function toolCallSummary(names: readonly string[]): string;
86
+ /**
87
+ * The fork-at-turn seed: the session events from seq 0 through the selected
88
+ * turn's `turn/end` (inclusive) — the state as that turn finished; later
89
+ * turns stay out of the new session. The boundary comes from the
90
+ * HistoryTurn's OWN `seqEnd` (captured while folding) — never from a
91
+ * re-match on the turn NUMBER, which would silently cut at an earlier
92
+ * same-numbered turn in a damaged log with duplicate turn ids.
93
+ * `seq === array index` holds for live snapshots and persisted `inspect`
94
+ * events alike (restore validates the same contiguity), so a plain slice is
95
+ * exact. Only completed turns are forkable (a fold never produces an open
96
+ * one). The same balanced-prefix shape the host's fork subagent backend
97
+ * seeds with.
98
+ */
99
+ export declare function turnSeedSlice(events: readonly SessionEvent[], turn: HistoryTurn): readonly SessionEvent[];
@@ -167,4 +167,20 @@ export function toolCallSummary(names) {
167
167
  const total = names.length;
168
168
  return `${total} tool call${total === 1 ? '' : 's'}: ${parts.join(', ')}`;
169
169
  }
170
+ /**
171
+ * The fork-at-turn seed: the session events from seq 0 through the selected
172
+ * turn's `turn/end` (inclusive) — the state as that turn finished; later
173
+ * turns stay out of the new session. The boundary comes from the
174
+ * HistoryTurn's OWN `seqEnd` (captured while folding) — never from a
175
+ * re-match on the turn NUMBER, which would silently cut at an earlier
176
+ * same-numbered turn in a damaged log with duplicate turn ids.
177
+ * `seq === array index` holds for live snapshots and persisted `inspect`
178
+ * events alike (restore validates the same contiguity), so a plain slice is
179
+ * exact. Only completed turns are forkable (a fold never produces an open
180
+ * one). The same balanced-prefix shape the host's fork subagent backend
181
+ * seeds with.
182
+ */
183
+ export function turnSeedSlice(events, turn) {
184
+ return events.slice(0, Math.min(turn.seqEnd + 1, events.length));
185
+ }
170
186
  //# sourceMappingURL=history-turns.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"history-turns.js","sourceRoot":"","sources":["../src/history-turns.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"history-turns.js","sourceRoot":"","sources":["../src/history-turns.ts"],"names":[],"mappings":"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"}
package/lib/history.d.ts CHANGED
@@ -67,6 +67,18 @@ export declare const DUAL_PANE_MIN_COLUMNS = 100;
67
67
  export declare const LEFT_PANE_MIN_COLUMNS = 30;
68
68
  /** Rendered-line cap of one user bubble in the detail pane (spec: truncated). */
69
69
  export declare const MAX_USER_BUBBLE_LINES = 40;
70
+ /**
71
+ * Source-line budget for the ASSISTANT Markdown of one turn's detail pane
72
+ * (user bubbles carry their own per-bubble cap; the tool summary and end
73
+ * notice are one-liners). A pathological multi-thousand-line reply parses
74
+ * once at rebuild (an explicit action) and would otherwise dominate the
75
+ * width-cache line arrays for every frame — past the budget the tail is
76
+ * replaced by one dim truncation marker pointing at /resume. Source lines,
77
+ * not wrapped lines: the wrap factor is bounded by the terminal width and
78
+ * the budget exists to kill the parse, not to police layout. Within the
79
+ * budget nothing changes.
80
+ */
81
+ export declare const MAX_DETAIL_LINES = 4000;
70
82
  /**
71
83
  * Content-row budget inside the framed overlay: showOverlay slices the
72
84
  * component's lines at maxHeight ('85%' of the terminal — pi-tui floors the
@@ -107,9 +119,13 @@ export declare function historyRows(turns: readonly HistoryTurn[], query: string
107
119
  * The detail pane's content for one turn, as a fresh Container of the same
108
120
  * primitives the main transcript renders: user prompts as canvasSubtle
109
121
  * bubbles (Text + bg), replies as Markdown parsed once per rebuild, then the
110
- * `⚙` tool-count summary line and any turn-end notice. Exported for tests.
122
+ * `⚙` tool-count summary line and any turn-end notice. Assistant source
123
+ * lines are capped at MAX_DETAIL_LINES — the tail is replaced by one dim
124
+ * marker pointing at /resume (the truncation happens HERE, on the explicit
125
+ * rebuild path, never inside render()). `sessionId` (optional) only flavors
126
+ * that marker's short id. Exported for tests.
111
127
  */
112
- export declare function buildTurnDetailContainer(turn: HistoryTurn, theme: TuiTheme): Container;
128
+ export declare function buildTurnDetailContainer(turn: HistoryTurn, theme: TuiTheme, sessionId?: string): Container;
113
129
  /** Structural TUI/session seam of the browser — keeps the flow testable. */
114
130
  export interface HistoryBrowserDeps {
115
131
  readonly ctx: Context;
@@ -121,6 +137,23 @@ export interface HistoryBrowserDeps {
121
137
  getLiveEvents(): readonly SessionEvent[] | undefined;
122
138
  /** Copy target: a plain editor setText (never submitted). */
123
139
  copyToEditor(text: string): void;
140
+ /**
141
+ * Show the fork-at-turn confirmation over the open browser; resolves true
142
+ * = fork now. `turnLabel` is the selected turn's number (as displayed),
143
+ * `totalTurns` the session's listed turn count; `cold` is true when the
144
+ * browsed session is not the live one — the fork then detaches the LIVE
145
+ * session, and the dialog must say so instead of hiding it.
146
+ */
147
+ confirmForkAtTurn(turnLabel: string, totalTurns: number, cold: boolean): Promise<boolean>;
148
+ /**
149
+ * Fork-and-switch at the turn boundary: start a new session on the CURRENT
150
+ * preset selection seeded with `seed` (the browsed session's prefix), and
151
+ * switch to it. Resolves when the new session is live; a rejection leaves
152
+ * every existing binding untouched (the browser stays open).
153
+ */
154
+ forkAtTurn(seed: readonly SessionEvent[], parentSessionId: string): Promise<unknown>;
155
+ /** Buffered channel for fork failures (a transcript notice). */
156
+ reportError(message: string): void;
124
157
  restoreFocus(): void;
125
158
  requestRender(): void;
126
159
  }
@@ -167,11 +200,14 @@ export declare class HistoryBrowserPanel implements Component {
167
200
  private sessionId;
168
201
  private live;
169
202
  private turns;
203
+ /** The browsed session's raw events — the fork-at-turn slice source. */
204
+ private events;
170
205
  private query;
171
206
  private rows;
172
207
  private status;
173
208
  private closed;
174
209
  private pickerLoading;
210
+ private forkInProgress;
175
211
  /**
176
212
  * Where the keyboard lives: the left list (default) or the right detail
177
213
  * pane. `→` hands focus to the detail pane, `←`/Esc step back; only the
@@ -219,6 +255,15 @@ export declare class HistoryBrowserPanel implements Component {
219
255
  * picker-local — reset on every `s` (CONTEXT.md "Filter").
220
256
  */
221
257
  buildSessionPickerPanel(rows: readonly SessionPickRow[]): TablePanel<SessionPickRow>;
258
+ /**
259
+ * Fork at the selected turn (`f`): confirm over the open browser, then
260
+ * hand the turn-bounded seed to the fork-and-switch seam. The browsed
261
+ * session's events are the slice source — live snapshots and cold-read
262
+ * (`inspect`) events fork alike. Cancel or an empty slice (nothing
263
+ * completed to carry) changes nothing; a failed fork keeps the browser
264
+ * open with the failure on the status line plus a buffered notice.
265
+ */
266
+ private forkAtSelectedTurn;
222
267
  /** Open the session picker overlay (the browser stays mounted underneath). */
223
268
  private openSessionPicker;
224
269
  /** Live query swap: rebuild rows, keep the cursor on its turn when visible. */