@aiwayds/dsh-tui-pi 2.5.0 → 2.7.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
@@ -22,8 +22,9 @@ 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
+ - [**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).
27
28
  - [**Themes**](docs/features/themes.md) — GitHub light/dark palettes, hot-switchable; `auto` follows your terminal.
28
29
  - [**Search, selection & images**](docs/features/search-selection-images.md) — `Ctrl+Shift+F` over the whole transcript, drag-select copies to the OS clipboard, attachments from web/Feishu render inline, LaTeX replies draw as Unicode math.
29
30
  - [**Slash commands**](docs/features/slash-commands.md) — `/model`, `/resume`, `/btw`, `/profile-switch`, … plus everything dsh-native.
@@ -84,7 +85,6 @@ dsh plugin --profile tui add @aiwayds/dsh-topics-memory
84
85
  | `Ctrl+G` | Open the subagent picker (viewer `Enter` opens steer) |
85
86
  | `Ctrl+O` | Pending-message queue (s steer now · d remove) |
86
87
  | `Ctrl+Shift+F` | Transcript search (`Enter`/`Ctrl+G` next · `Shift+Enter`/`Ctrl+Shift+G` previous · `Esc` close) |
87
- | `Tab` | Cycle agent presets |
88
88
  | `↑` / `↓` | Browse submitted-message history |
89
89
 
90
90
  Remap any app key through `~/.dsh/keybindings.json` (a partial JSON map, live-applied) or interactively with `/hotkeys`.
package/README.zh-CN.md CHANGED
@@ -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 数学。
@@ -82,7 +82,6 @@ dsh plugin --profile tui add @aiwayds/dsh-topics-memory
82
82
  | `Ctrl+G` | 打开 subagent 选择器(查看器内 `Enter` 打开 steer) |
83
83
  | `Ctrl+O` | 待发消息队列(`s` 立即 steer · `d` 移除) |
84
84
  | `Ctrl+Shift+F` | 全文搜索(`Enter`/`Ctrl+G` 下一个 · `Shift+Enter`/`Ctrl+Shift+G` 上一个 · `Esc` 关闭) |
85
- | `Tab` | 循环切换 agent preset |
86
85
  | `↑` / `↓` | 浏览已提交消息历史 |
87
86
 
88
87
  通过 `~/.dsh/keybindings.json`(部分 JSON 映射,实时应用)或 `/hotkeys` 交互式重映射任意 app 按键。
@@ -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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"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * /history turn grouping — the pure `SessionEvent[] → HistoryTurn[]` fold
3
+ * behind the history browser (docs/features/history.md, ADR 0003).
4
+ *
5
+ * The session log is a linear seq-keyed append-only stream; turns are the
6
+ * `turn/start … turn/end` brackets over it. One completed bracket becomes one
7
+ * HistoryTurn carrying everything the browser's two panes need:
8
+ *
9
+ * - the turn's user prompts (`user/message`, seq order — claimed steer and
10
+ * follow-up messages land here as ordinary kind-'user' messages; injected
11
+ * context of other source kinds is NOT a prompt and is excluded, matching
12
+ * the /resume preview's vocabulary);
13
+ * - the turn's assembled LLM replies (`assistant/message` text blocks, seq
14
+ * order — a tool-using turn has one per step; the last is the final reply);
15
+ * - the tool-invocation names (`tool/call`, seq order) for the per-tool count
16
+ * summary line.
17
+ *
18
+ * A `turn/start` whose `turn/end` never arrives (the currently streaming /
19
+ * running turn of a live session) is INCOMPLETE and never reaches the output —
20
+ * a cold-read log has every turn closed, a live snapshot legitimately drops
21
+ * its in-flight tail. Streaming chunks (`assistant/chunk`) are skipped
22
+ * outright: the replay-path rule (iron rule 9) — the assembled message carries
23
+ * the full text.
24
+ *
25
+ * Pure and dependency-free apart from the event types, so it is unit-testable
26
+ * without a terminal or a session store.
27
+ */
28
+ import type { SessionEvent } from '@deepseek-ai/dsh-session';
29
+ /** One completed `turn/start … turn/end` bracket of a session log. */
30
+ export interface HistoryTurn {
31
+ /** The log's turn number (`turn/start` data). Display identity of the row. */
32
+ turn: number;
33
+ /** Seq of the `turn/start` event. */
34
+ seqStart: number;
35
+ /** Seq of the closing `turn/end` event. */
36
+ seqEnd: number;
37
+ /** Kind of the closing `turn/end` reason (`completed`, `aborted`, …). */
38
+ endReason: string;
39
+ /** Error message of an `error`-kind turn end, when the log carried one. */
40
+ endError: string | undefined;
41
+ /** True when any assembled message of the turn was flagged `interrupted`. */
42
+ interrupted: boolean;
43
+ /**
44
+ * Text of every human prompt in the turn, seq order — direct prompts and
45
+ * claimed steer/follow-up messages alike (all source kind 'user').
46
+ */
47
+ userTexts: string[];
48
+ /**
49
+ * One-line preview of the turn: the first human prompt's text, falling back
50
+ * to the first text of any user/message kind (an injected-only turn still
51
+ * gets a readable row). Whitespace as logged.
52
+ */
53
+ previewText: string;
54
+ /** Text-bearing assistant message bodies, seq order (one per step). */
55
+ assistantTexts: string[];
56
+ /** Tool invocation names, seq order (repeats included). */
57
+ toolCallNames: string[];
58
+ }
59
+ /**
60
+ * Fold a session event snapshot into the completed turns, in log order.
61
+ * Unclosed brackets (the live turn still streaming when the snapshot was
62
+ * taken) and events outside any bracket are dropped.
63
+ */
64
+ export declare function groupHistoryTurns(events: readonly SessionEvent[]): HistoryTurn[];
65
+ /**
66
+ * The user prompt text a `c`/Enter copy refills the editor with: the turn's
67
+ * first human prompt. `undefined` when the turn carries NO human prompt —
68
+ * an injected-only turn (file-change notices, skill content) must never be
69
+ * refillable into the editor, where one Enter would submit the notice as a
70
+ * prompt. The LEFT-LIST preview keeps its own fallback (`previewText`), so
71
+ * such turns still show a readable row; only the copy path declines.
72
+ */
73
+ export declare function turnPrimaryUserText(turn: HistoryTurn): string | undefined;
74
+ /**
75
+ * Case-insensitive substring filter over the row vocabulary: the preview text
76
+ * and the turn number (typing "3" narrows to turn 3xx too — a feature, the
77
+ * number is the row's identity). Empty query matches everything. Mirrors the
78
+ * /model filter's matching convention (matchesModelFilter).
79
+ */
80
+ export declare function matchesTurnFilter(turn: HistoryTurn, query: string): boolean;
81
+ /**
82
+ * The `⚙ N tool calls: read×2, edit×1` summary of one turn's tool/call names,
83
+ * in first-appearance order. Empty string for a tool-less turn.
84
+ */
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. `seq === array index` holds for live
90
+ * snapshots and persisted `inspect` events alike (restore validates the same
91
+ * contiguity), so a plain slice is exact. Returns [] when the turn is
92
+ * unknown or never closed — only completed turns are forkable. The same
93
+ * balanced-prefix shape the host's fork subagent backend seeds with.
94
+ */
95
+ export declare function turnSeedSlice(events: readonly SessionEvent[], turn: number): readonly SessionEvent[];
@@ -0,0 +1,186 @@
1
+ /**
2
+ * /history turn grouping — the pure `SessionEvent[] → HistoryTurn[]` fold
3
+ * behind the history browser (docs/features/history.md, ADR 0003).
4
+ *
5
+ * The session log is a linear seq-keyed append-only stream; turns are the
6
+ * `turn/start … turn/end` brackets over it. One completed bracket becomes one
7
+ * HistoryTurn carrying everything the browser's two panes need:
8
+ *
9
+ * - the turn's user prompts (`user/message`, seq order — claimed steer and
10
+ * follow-up messages land here as ordinary kind-'user' messages; injected
11
+ * context of other source kinds is NOT a prompt and is excluded, matching
12
+ * the /resume preview's vocabulary);
13
+ * - the turn's assembled LLM replies (`assistant/message` text blocks, seq
14
+ * order — a tool-using turn has one per step; the last is the final reply);
15
+ * - the tool-invocation names (`tool/call`, seq order) for the per-tool count
16
+ * summary line.
17
+ *
18
+ * A `turn/start` whose `turn/end` never arrives (the currently streaming /
19
+ * running turn of a live session) is INCOMPLETE and never reaches the output —
20
+ * a cold-read log has every turn closed, a live snapshot legitimately drops
21
+ * its in-flight tail. Streaming chunks (`assistant/chunk`) are skipped
22
+ * outright: the replay-path rule (iron rule 9) — the assembled message carries
23
+ * the full text.
24
+ *
25
+ * Pure and dependency-free apart from the event types, so it is unit-testable
26
+ * without a terminal or a session store.
27
+ */
28
+ /**
29
+ * Join the `text` blocks of a message content array into one trimmed string
30
+ * (multi-block bodies join with newlines so copy stays faithful). Defensive
31
+ * over the erased shape — a malformed log row yields '' instead of throwing.
32
+ */
33
+ function blocksText(content) {
34
+ if (!Array.isArray(content))
35
+ return '';
36
+ let text = '';
37
+ for (const block of content) {
38
+ const typed = block;
39
+ if (typed?.type === 'text' && typeof typed.text === 'string') {
40
+ text += (text === '' ? '' : '\n') + typed.text;
41
+ }
42
+ }
43
+ return text.trim();
44
+ }
45
+ /**
46
+ * Fold a session event snapshot into the completed turns, in log order.
47
+ * Unclosed brackets (the live turn still streaming when the snapshot was
48
+ * taken) and events outside any bracket are dropped.
49
+ */
50
+ export function groupHistoryTurns(events) {
51
+ const turns = [];
52
+ let open;
53
+ const close = () => {
54
+ if (open === undefined)
55
+ return;
56
+ const userTexts = open.userEntries
57
+ .filter(entry => entry.kind === 'user' && entry.text !== '')
58
+ .map(entry => entry.text);
59
+ const anyText = open.userEntries.find(entry => entry.text !== '')?.text ?? '';
60
+ turns.push({
61
+ turn: open.turn,
62
+ seqStart: open.seqStart,
63
+ seqEnd: open.seqEnd,
64
+ endReason: open.endReason,
65
+ endError: open.endError,
66
+ interrupted: open.interrupted,
67
+ userTexts,
68
+ previewText: userTexts[0] ?? anyText,
69
+ assistantTexts: open.assistantTexts,
70
+ toolCallNames: open.toolCallNames,
71
+ });
72
+ open = undefined;
73
+ };
74
+ for (const event of events) {
75
+ if (event.type === 'turn/start') {
76
+ // A new bracket while one is open means the previous one never closed —
77
+ // drop it (incomplete) and start fresh; well-formed logs never hit this.
78
+ open = {
79
+ turn: Number(event.data.turn),
80
+ seqStart: Number(event.seq),
81
+ seqEnd: Number(event.seq),
82
+ endReason: 'completed',
83
+ endError: undefined,
84
+ interrupted: false,
85
+ userEntries: [],
86
+ assistantTexts: [],
87
+ toolCallNames: [],
88
+ };
89
+ continue;
90
+ }
91
+ if (open === undefined)
92
+ continue;
93
+ switch (event.type) {
94
+ case 'user/message': {
95
+ const message = event.data;
96
+ const kind = message.source?.kind ?? '';
97
+ const text = blocksText(message.content);
98
+ open.userEntries.push({ kind, text });
99
+ break;
100
+ }
101
+ case 'assistant/message': {
102
+ const message = event.data.message;
103
+ const text = blocksText(message.content);
104
+ if (text !== '')
105
+ open.assistantTexts.push(text);
106
+ if (event.data.interrupted === true)
107
+ open.interrupted = true;
108
+ break;
109
+ }
110
+ case 'tool/call':
111
+ open.toolCallNames.push(event.data.name);
112
+ break;
113
+ case 'turn/end': {
114
+ open.seqEnd = Number(event.seq);
115
+ open.endReason = event.data.reason.kind;
116
+ const error = event.data.reason;
117
+ open.endError = typeof error.error?.message === 'string' ? error.error.message : undefined;
118
+ close();
119
+ break;
120
+ }
121
+ default:
122
+ // assistant/chunk (iron rule 9: replay paths never consume chunks),
123
+ // step/start|end, request/*, command/*, todo/write, … — no turn pane
124
+ // needs them.
125
+ break;
126
+ }
127
+ }
128
+ // A trailing unclosed bracket (the live turn mid-flight) is dropped by
129
+ // never closing it.
130
+ return turns;
131
+ }
132
+ /**
133
+ * The user prompt text a `c`/Enter copy refills the editor with: the turn's
134
+ * first human prompt. `undefined` when the turn carries NO human prompt —
135
+ * an injected-only turn (file-change notices, skill content) must never be
136
+ * refillable into the editor, where one Enter would submit the notice as a
137
+ * prompt. The LEFT-LIST preview keeps its own fallback (`previewText`), so
138
+ * such turns still show a readable row; only the copy path declines.
139
+ */
140
+ export function turnPrimaryUserText(turn) {
141
+ return turn.userTexts[0];
142
+ }
143
+ /**
144
+ * Case-insensitive substring filter over the row vocabulary: the preview text
145
+ * and the turn number (typing "3" narrows to turn 3xx too — a feature, the
146
+ * number is the row's identity). Empty query matches everything. Mirrors the
147
+ * /model filter's matching convention (matchesModelFilter).
148
+ */
149
+ export function matchesTurnFilter(turn, query) {
150
+ const needle = query.trim().toLowerCase();
151
+ if (needle === '')
152
+ return true;
153
+ return turn.previewText.toLowerCase().includes(needle)
154
+ || String(turn.turn).includes(needle);
155
+ }
156
+ /**
157
+ * The `⚙ N tool calls: read×2, edit×1` summary of one turn's tool/call names,
158
+ * in first-appearance order. Empty string for a tool-less turn.
159
+ */
160
+ export function toolCallSummary(names) {
161
+ if (names.length === 0)
162
+ return '';
163
+ const counts = new Map();
164
+ for (const name of names)
165
+ counts.set(name, (counts.get(name) ?? 0) + 1);
166
+ const parts = [...counts.entries()].map(([name, count]) => `${name}×${count}`);
167
+ const total = names.length;
168
+ return `${total} tool call${total === 1 ? '' : 's'}: ${parts.join(', ')}`;
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. `seq === array index` holds for live
174
+ * snapshots and persisted `inspect` events alike (restore validates the same
175
+ * contiguity), so a plain slice is exact. Returns [] when the turn is
176
+ * unknown or never closed — only completed turns are forkable. The same
177
+ * balanced-prefix shape the host's fork subagent backend seeds with.
178
+ */
179
+ export function turnSeedSlice(events, turn) {
180
+ for (const grouped of groupHistoryTurns(events)) {
181
+ if (grouped.turn === turn)
182
+ return events.slice(0, grouped.seqEnd + 1);
183
+ }
184
+ return [];
185
+ }
186
+ //# sourceMappingURL=history-turns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-turns.js","sourceRoot":"","sources":["../src/history-turns.ts"],"names":[],"mappings":"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"}