@aiwayds/dsh-tui-pi 2.4.0 → 2.6.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 +5 -4
- package/README.zh-CN.md +3 -3
- package/lib/history-turns.d.ts +85 -0
- package/lib/history-turns.js +170 -0
- package/lib/history-turns.js.map +1 -0
- package/lib/history.d.ts +239 -0
- package/lib/history.js +746 -0
- package/lib/history.js.map +1 -0
- package/lib/host-version.d.ts +5 -4
- package/lib/host-version.js +7 -6
- package/lib/host-version.js.map +1 -1
- package/lib/index.js +42 -6
- package/lib/index.js.map +1 -1
- package/lib/panels.d.ts +15 -0
- package/lib/panels.js +13 -3
- package/lib/panels.js.map +1 -1
- package/lib/sessions.d.ts +43 -0
- package/lib/sessions.js +9 -5
- package/lib/sessions.js.map +1 -1
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
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.
|
|
6
6
|
|
|
7
|
-
**Requires dsh >= 0.1.2-
|
|
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
9
|
https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
|
|
10
10
|
|
|
@@ -24,6 +24,7 @@ https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
|
|
|
24
24
|
- [**Model profiles & favorites**](docs/features/model-profiles.md) — switch a whole model setup per project and keep the picker small.
|
|
25
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.
|
|
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.
|
|
@@ -38,7 +39,7 @@ dsh plugin --profile tui add @aiwayds/dsh-tui-pi
|
|
|
38
39
|
dsh --profile tui # launch (or: dsh-tui-pi)
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
Legacy `session_projcache` records (missing `identity.isSeeded`/`identity.inheritedEventCount`, written before dsh 0.1.2-alpha.4) are migrated at the profile layer: the bundle patch replaces the stock `session-projection-cache` row with a wrapper (`@aiwayds/dsh-tui-pi/projcache`) that backfills the records while its module loads — strictly before the stock plugin could open the domain and crash the boot — so every `dsh --profile tui` start is covered, launcher or not. Migration is idempotent, backs up every rewritten file next to the original, and never blocks startup. The `dsh-tui-pi` launcher additionally runs the same migration as a CLI preflight before `exec dsh`.
|
|
42
|
+
Legacy `session_projcache` records (missing `identity.isSeeded`/`identity.inheritedEventCount`, written before dsh 0.1.2-alpha.4 — predating the rc/stable floor of 0.1.2-rc.1 this plugin now targets) are migrated at the profile layer: the bundle patch replaces the stock `session-projection-cache` row with a wrapper (`@aiwayds/dsh-tui-pi/projcache`) that backfills the records while its module loads — strictly before the stock plugin could open the domain and crash the boot — so every `dsh --profile tui` start is covered, launcher or not. Migration is idempotent, backs up every rewritten file next to the original, and never blocks startup. The `dsh-tui-pi` launcher additionally runs the same migration as a CLI preflight before `exec dsh`.
|
|
42
43
|
|
|
43
44
|
Everything that used to need manual patching — the canvas background, the `@deepseek-ai` module closure, the compaction backend — now happens automatically. Upgrade an existing profile after a release:
|
|
44
45
|
|
|
@@ -62,10 +63,10 @@ node scripts/dev-upgrade.mjs 1.0.5 --dry-run # preview the plan first
|
|
|
62
63
|
- [@aiwayds/dsh-subagent-registry](https://github.com/fan56/dsh-subagent-registry) — registers `~/.dsh/agents/*.md` as `use_agent` subagents.
|
|
63
64
|
- [@aiwayds/dsh-web-search-anysearch](https://github.com/fan56/dsh-web-search-anysearch) — the AnySearch web search provider.
|
|
64
65
|
|
|
65
|
-
**Recommended install** — [@aiwayds/dsh-
|
|
66
|
+
**Recommended install** — [@aiwayds/dsh-topics-memory](https://github.com/fan56/dsh-topics-memory), OKF topic memory for dsh (zero-LLM hot-path injection + a local git-tracked bundle; formerly dsh-llmwiki-memory):
|
|
66
67
|
|
|
67
68
|
```sh
|
|
68
|
-
dsh plugin --profile tui add @aiwayds/dsh-
|
|
69
|
+
dsh plugin --profile tui add @aiwayds/dsh-topics-memory
|
|
69
70
|
```
|
|
70
71
|
|
|
71
72
|
**Optional** — [@aiwayds/dsh-feishu](https://github.com/fan56/dsh-feishu) — drives the same dsh session from Feishu/Lark on your phone ([demo](docs/features/feishu-demo.md)).
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)的 pi 风格终端 UI——一套把 dsh 变成 pi 式编码代理体验的插件套件:pi-tui 的外观与交互、dsh 斜杠命令、GitHub 明/暗主题和 powerline 状态栏。
|
|
6
6
|
|
|
7
|
-
**要求 dsh >= 0.1.2-
|
|
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
9
|
https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
|
|
10
10
|
|
|
@@ -60,10 +60,10 @@ node scripts/dev-upgrade.mjs 1.0.5 --dry-run # 先预览执行计划
|
|
|
60
60
|
- [@aiwayds/dsh-subagent-registry](https://github.com/fan56/dsh-subagent-registry) —— 把 `~/.dsh/agents/*.md` 注册成 `use_agent` 子代理。
|
|
61
61
|
- [@aiwayds/dsh-web-search-anysearch](https://github.com/fan56/dsh-web-search-anysearch) —— AnySearch web 搜索 provider。
|
|
62
62
|
|
|
63
|
-
**推荐安装** —— [@aiwayds/dsh-
|
|
63
|
+
**推荐安装** —— [@aiwayds/dsh-topics-memory](https://github.com/fan56/dsh-topics-memory),dsh 的 OKF topic 记忆插件(零 LLM 热路径注入 + 本地 git 可追溯 bundle;原名 dsh-llmwiki-memory):
|
|
64
64
|
|
|
65
65
|
```sh
|
|
66
|
-
dsh plugin --profile tui add @aiwayds/dsh-
|
|
66
|
+
dsh plugin --profile tui add @aiwayds/dsh-topics-memory
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
**可选** —— [@aiwayds/dsh-feishu](https://github.com/fan56/dsh-feishu) —— 用手机上的飞书/Lark 驱动同一个 dsh session([演示](docs/features/feishu-demo.md))。
|
|
@@ -0,0 +1,85 @@
|
|
|
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;
|
|
@@ -0,0 +1,170 @@
|
|
|
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
|
+
//# 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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"}
|
package/lib/history.d.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /history — the read-only two-pane history browser (CONTEXT.md "History
|
|
3
|
+
* browser", ADR 0003, docs/features/history.md).
|
|
4
|
+
*
|
|
5
|
+
* Left pane: a TablePanel listing the browsed session's COMPLETED turns
|
|
6
|
+
* (turn 序号 + user-message preview, seq order — a list, not a tree; the
|
|
7
|
+
* session log has no message-level branching). Right pane: the selected
|
|
8
|
+
* turn's content — the user prompts in the main transcript's bubble style,
|
|
9
|
+
* the assembled LLM replies as Markdown, and a per-tool call-count summary.
|
|
10
|
+
* View and copy only: Enter/`c` refills the editor with the turn's user
|
|
11
|
+
* prompt (a plain setText — never submitted), `s` swaps the browsed session
|
|
12
|
+
* through a /resume-style picker, Esc closes. No resend, no branch, no
|
|
13
|
+
* transcript jump.
|
|
14
|
+
*
|
|
15
|
+
* Snapshot semantics: the event list is read once per open / session switch
|
|
16
|
+
* (live session → `session.snapshotEvents()`, stored session →
|
|
17
|
+
* `sessionPersistence.inspect()` — a cold read, no writer lock, no agent
|
|
18
|
+
* activation). A session that keeps running while the viewer is open does
|
|
19
|
+
* NOT live-update; reopening refreshes.
|
|
20
|
+
*
|
|
21
|
+
* Layout: ≥100 terminal columns renders an HStack (list ≈40% with a 30-column
|
|
22
|
+
* floor, detail takes the remainder); narrower terminals stack the panes
|
|
23
|
+
* vertically (list on top). The container is chosen per render at the current
|
|
24
|
+
* width. The window itself is FIXED geometry: the panel renders exactly
|
|
25
|
+
* `overlayContentBudget()` lines (short content pads blank, long content
|
|
26
|
+
* lives in the detail scroll window), so picking another turn never changes
|
|
27
|
+
* the window size — only a terminal resize re-derives it (overlay mounted at
|
|
28
|
+
* '90%' width / '85%' height; the budget floors the same percentage pi-tui
|
|
29
|
+
* does).
|
|
30
|
+
*
|
|
31
|
+
* Focus model: the keyboard lives on the left list by default; `→` hands it
|
|
32
|
+
* to the detail pane (`↑`/`↓` line-scroll, PgUp/PgDn or `[`/`]` page, `←` or
|
|
33
|
+
* Esc steps back; every other key is inert there). Esc grades detail → list
|
|
34
|
+
* → filter-clear → close — it never skips a level. Focus is visible: the
|
|
35
|
+
* focused pane's cues are the list's ▸ cursor (demoted to `›` while the
|
|
36
|
+
* detail is keyed) versus the detail pane's accent-BOLD title and its
|
|
37
|
+
* `← list · ↑↓ scroll` footer hint.
|
|
38
|
+
*
|
|
39
|
+
* Scroll reality (documented deviation from the original sketch): pi-tui's
|
|
40
|
+
* overlay path composites `component.render(width)` lines directly — the
|
|
41
|
+
* layout engine never descends into overlays, so a ScrollView can never
|
|
42
|
+
* obtain a viewport there (the same limitation AGENTS.md documents for plain
|
|
43
|
+
* Containers). The detail pane therefore manages its own scroll window (the
|
|
44
|
+
* SubagentViewerPanel precedent): `[` / `]` page, selection change resets to
|
|
45
|
+
* the top. The content itself is built from the same pi-tui primitives the
|
|
46
|
+
* main transcript uses (Text bubbles / Markdown), so the look matches.
|
|
47
|
+
*
|
|
48
|
+
* Static rebuild mode (the setTheme/relayout snapshot pattern): switching the
|
|
49
|
+
* selected turn REBUILDS the detail container from the turn's events — the
|
|
50
|
+
* render() path only ever slices an already-built line list and never
|
|
51
|
+
* touches the event data (iron rule 1: render never re-scans). The one
|
|
52
|
+
* deliberate exception is the render-time resize check below: an O(1)
|
|
53
|
+
* budget comparison that may rebuild ALREADY-DERIVED display rows (never
|
|
54
|
+
* events) so the fixed geometry tracks terminal resizes without a resize
|
|
55
|
+
* listener. Unlike the transcript's event-driven relayout(), this runs
|
|
56
|
+
* inside render() by design — do not copy it as a default pattern.
|
|
57
|
+
*/
|
|
58
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
59
|
+
import { type SessionEvent } from '@deepseek-ai/dsh-session';
|
|
60
|
+
import { Container, type Component, type TUI } from '@earendil-works/pi-tui';
|
|
61
|
+
import { type HistoryTurn } from './history-turns.ts';
|
|
62
|
+
import { PanelHost, TablePanel } from './panels.ts';
|
|
63
|
+
import { type TuiTheme } from './theme/index.ts';
|
|
64
|
+
/** Terminal width at which the browser switches from stacked to side-by-side. */
|
|
65
|
+
export declare const DUAL_PANE_MIN_COLUMNS = 100;
|
|
66
|
+
/** Floor of the left list pane in dual-pane mode (spec: min 30 columns). */
|
|
67
|
+
export declare const LEFT_PANE_MIN_COLUMNS = 30;
|
|
68
|
+
/** Rendered-line cap of one user bubble in the detail pane (spec: truncated). */
|
|
69
|
+
export declare const MAX_USER_BUBBLE_LINES = 40;
|
|
70
|
+
/**
|
|
71
|
+
* Content-row budget inside the framed overlay: showOverlay slices the
|
|
72
|
+
* component's lines at maxHeight ('85%' of the terminal — pi-tui floors the
|
|
73
|
+
* percentage, and so do we, so the budget never exceeds the real slice),
|
|
74
|
+
* and the FramedOverlay adds 4 chrome rows (top/bottom border + a blank
|
|
75
|
+
* spacer each). The browser renders EXACTLY this many lines (pad or cap —
|
|
76
|
+
* fixed window geometry), and every inner budget derives from it so no
|
|
77
|
+
* footer is ever sliced off. Testable; `rows` injected.
|
|
78
|
+
*/
|
|
79
|
+
export declare function overlayContentBudget(rows?: number | undefined): number;
|
|
80
|
+
/**
|
|
81
|
+
* Visible rows of the left list: the TablePanel chrome is 7 rows (title,
|
|
82
|
+
* ┬/header/┼/┴ rules, blank spacer, footer) and 2 more rows are reserved
|
|
83
|
+
* for the filter line and the status line — the two optional lines that can
|
|
84
|
+
* co-display with results (an applied filter plus a copy/load status), and
|
|
85
|
+
* under-reserving them slices the panel footers off the fixed budget; the
|
|
86
|
+
* stacked layout additionally owes the detail pane its own chrome + a living
|
|
87
|
+
* body (15 reserved rows), the side-by-side layout only the slice guard (9).
|
|
88
|
+
* Fixed at panel construction — a terminal resize mid-open keeps the stale
|
|
89
|
+
* budget until reopened (the accepted overlay behavior). Testable.
|
|
90
|
+
*/
|
|
91
|
+
export declare function listMaxVisible(rows?: number | undefined, columns?: number | undefined): number;
|
|
92
|
+
/** One row of the left list: the turn plus its pre-clipped display cells. */
|
|
93
|
+
export interface HistoryRow {
|
|
94
|
+
turn: HistoryTurn;
|
|
95
|
+
/** The log turn number, as displayed. */
|
|
96
|
+
turnLabel: string;
|
|
97
|
+
/** One-line preview (control chars folded, hard character cap). */
|
|
98
|
+
preview: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The left-list rows for a turn list under `query`: case-insensitive
|
|
102
|
+
* substring match on the preview text and the turn number, in seq order.
|
|
103
|
+
* Pure; the TablePanel clips `preview` to the cell width at render time.
|
|
104
|
+
*/
|
|
105
|
+
export declare function historyRows(turns: readonly HistoryTurn[], query: string): HistoryRow[];
|
|
106
|
+
/**
|
|
107
|
+
* The detail pane's content for one turn, as a fresh Container of the same
|
|
108
|
+
* primitives the main transcript renders: user prompts as canvasSubtle
|
|
109
|
+
* 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.
|
|
111
|
+
*/
|
|
112
|
+
export declare function buildTurnDetailContainer(turn: HistoryTurn, theme: TuiTheme): Container;
|
|
113
|
+
/** Structural TUI/session seam of the browser — keeps the flow testable. */
|
|
114
|
+
export interface HistoryBrowserDeps {
|
|
115
|
+
readonly ctx: Context;
|
|
116
|
+
readonly tui: TUI;
|
|
117
|
+
readonly theme: TuiTheme;
|
|
118
|
+
/** Current live session id, when one exists. */
|
|
119
|
+
getSessionId(): string | undefined;
|
|
120
|
+
/** Event snapshot of the live session (its agent's session.snapshotEvents()). */
|
|
121
|
+
getLiveEvents(): readonly SessionEvent[] | undefined;
|
|
122
|
+
/** Copy target: a plain editor setText (never submitted). */
|
|
123
|
+
copyToEditor(text: string): void;
|
|
124
|
+
restoreFocus(): void;
|
|
125
|
+
requestRender(): void;
|
|
126
|
+
}
|
|
127
|
+
/** Events + provenance of one browsed session. */
|
|
128
|
+
interface LoadedSession {
|
|
129
|
+
sessionId: string;
|
|
130
|
+
live: boolean;
|
|
131
|
+
events: readonly SessionEvent[];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The user-facing failure line for a failed session load. Corrupt logs get
|
|
135
|
+
* the ⚠ + repair pointer (the /resume vocabulary — repair itself is an
|
|
136
|
+
* agent-side flow and stays out of this read-only browser).
|
|
137
|
+
*/
|
|
138
|
+
export declare function historyLoadErrorMessage(id: string, error: unknown): string;
|
|
139
|
+
/** One row of the `s` session picker (the /resume picker's vocabulary). */
|
|
140
|
+
export interface SessionPickRow {
|
|
141
|
+
id: string;
|
|
142
|
+
updated: string;
|
|
143
|
+
dir: string;
|
|
144
|
+
session: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Case-insensitive substring filter over the picker's display vocabulary:
|
|
148
|
+
* the session title (preview/label, the ⚠ and ● markers included), the
|
|
149
|
+
* directory, and the raw session id (paste-an-id narrowing). Empty query
|
|
150
|
+
* matches everything, order preserved. Pure; exported for tests.
|
|
151
|
+
*/
|
|
152
|
+
export declare function filterSessionPickRows(rows: readonly SessionPickRow[], query: string): SessionPickRow[];
|
|
153
|
+
/**
|
|
154
|
+
* The browser overlay root: left TablePanel + right detail pane, arranged
|
|
155
|
+
* side-by-side (≥100 columns) or stacked (narrower), the container chosen
|
|
156
|
+
* per render at the current width. The keyboard stays with the left list
|
|
157
|
+
* (navigation, `/` filter, Enter/`c` copy, `s` session switch, Esc close);
|
|
158
|
+
* `[`/`]` page the detail pane; there is no focus management between panes.
|
|
159
|
+
*/
|
|
160
|
+
export declare class HistoryBrowserPanel implements Component {
|
|
161
|
+
private readonly deps;
|
|
162
|
+
private readonly host;
|
|
163
|
+
private listOptions;
|
|
164
|
+
private list;
|
|
165
|
+
private readonly detail;
|
|
166
|
+
private listMax;
|
|
167
|
+
private sessionId;
|
|
168
|
+
private live;
|
|
169
|
+
private turns;
|
|
170
|
+
private query;
|
|
171
|
+
private rows;
|
|
172
|
+
private status;
|
|
173
|
+
private closed;
|
|
174
|
+
private pickerLoading;
|
|
175
|
+
/**
|
|
176
|
+
* Where the keyboard lives: the left list (default) or the right detail
|
|
177
|
+
* pane. `→` hands focus to the detail pane, `←`/Esc step back; only the
|
|
178
|
+
* focused pane's keys act (detail focus makes ↑↓ scroll, list keys inert).
|
|
179
|
+
*/
|
|
180
|
+
private focus;
|
|
181
|
+
/**
|
|
182
|
+
* The overlay budget the current list panel was built for (and its derived
|
|
183
|
+
* list height): a terminal resize re-derives both — the one explicit
|
|
184
|
+
* external change the fixed-geometry render is allowed to react to.
|
|
185
|
+
*/
|
|
186
|
+
private builtBudget;
|
|
187
|
+
/** Set by openHistoryBrowser; delivers the closing echo text. */
|
|
188
|
+
onFinish: ((text: string) => void) | undefined;
|
|
189
|
+
constructor(deps: HistoryBrowserDeps, host: PanelHost, loaded: LoadedSession);
|
|
190
|
+
/**
|
|
191
|
+
* (Re)build the left TablePanel over the current rows — the subagent
|
|
192
|
+
* viewer's swap-the-panel pattern. Columns refit against the live rows
|
|
193
|
+
* (autoColumns scans every row), so a session whose turn numbers gain a
|
|
194
|
+
* digit gets a wider TURN column instead of a clipped one; the cursor
|
|
195
|
+
* lands on `preselect` (row 0 of a freshly loaded session). In-place row
|
|
196
|
+
* swaps (`setQuery`) keep using the retained options object.
|
|
197
|
+
*/
|
|
198
|
+
private rebuildListPanel;
|
|
199
|
+
invalidate(): void;
|
|
200
|
+
render(width: number): string[];
|
|
201
|
+
handleInput(data: string): void;
|
|
202
|
+
/** Move the keyboard between the two panes and refresh the focus visuals. */
|
|
203
|
+
private setFocus;
|
|
204
|
+
/** Close the overlay and deliver the closing echo text (once). */
|
|
205
|
+
private finish;
|
|
206
|
+
/** Refill the editor with the turn's user prompt and close (never submit). */
|
|
207
|
+
private copyTurn;
|
|
208
|
+
private copySelected;
|
|
209
|
+
/** Swap the browsed session; failures surface on the browser's status line. */
|
|
210
|
+
private loadAndShow;
|
|
211
|
+
/**
|
|
212
|
+
* Build the `s` session picker over prepared rows. Public so tests can
|
|
213
|
+
* drive the real panel (it mounts as its own overlay through the
|
|
214
|
+
* PanelHost). The filter is the same caller-held-query contract as the
|
|
215
|
+
* main list: `/` engages the input, every keystroke rebuilds the rows
|
|
216
|
+
* (case-insensitive substring over session title, directory and session
|
|
217
|
+
* id — `filterSessionPickRows`) with the cursor following its session
|
|
218
|
+
* across the rebuild, Esc clears the query before popping. The query is
|
|
219
|
+
* picker-local — reset on every `s` (CONTEXT.md "Filter").
|
|
220
|
+
*/
|
|
221
|
+
buildSessionPickerPanel(rows: readonly SessionPickRow[]): TablePanel<SessionPickRow>;
|
|
222
|
+
/** Open the session picker overlay (the browser stays mounted underneath). */
|
|
223
|
+
private openSessionPicker;
|
|
224
|
+
/** Live query swap: rebuild rows, keep the cursor on its turn when visible. */
|
|
225
|
+
private setQuery;
|
|
226
|
+
}
|
|
227
|
+
/** Outcome text of the /history command (the command echo line). */
|
|
228
|
+
export interface HistoryOpenResult {
|
|
229
|
+
text: string;
|
|
230
|
+
error: boolean;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Open the history browser. `sessionIdArg` (from `/history <sessionId>`)
|
|
234
|
+
* cold-reads that session; without one the CURRENT live session is browsed
|
|
235
|
+
* and, when none exists, a hint line is returned instead. Resolves with the
|
|
236
|
+
* closing echo text once the overlay closes (Esc, or copy-to-editor).
|
|
237
|
+
*/
|
|
238
|
+
export declare function openHistoryBrowser(deps: HistoryBrowserDeps, sessionIdArg: string | undefined): Promise<HistoryOpenResult>;
|
|
239
|
+
export {};
|