@itookit/dsht 0.2.1 → 0.2.4

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/dist/status.js CHANGED
@@ -1,9 +1,11 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /** Terminal status from host projections; cumulative usage and estimated context stay distinct. */
3
+ import { useTheme } from "./theme.js";
3
4
  import { memo, useEffect, useState } from 'react';
4
5
  import { Box, Text, useStdout } from 'ink';
5
6
  import wrapAnsi from 'wrap-ansi';
6
7
  import { costText } from "./cost.js";
8
+ import { toolLine } from "./transcript.js";
7
9
  import { safeText } from "./wire.js";
8
10
  /** Format elapsed wall time, clamping clock skew instead of displaying negative durations.
9
11
  * @param milliseconds - Elapsed duration.
@@ -12,6 +14,8 @@ import { safeText } from "./wire.js";
12
14
  export function elapsedTime(milliseconds) {
13
15
  const seconds = Math.max(0, Math.floor(milliseconds / 1000));
14
16
  const minutes = Math.floor(seconds / 60);
17
+ if (minutes === 0)
18
+ return `${seconds}s`;
15
19
  return `${minutes >= 60 ? `${Math.floor(minutes / 60)}h ` : ''}${minutes % 60}m ${seconds % 60}s`;
16
20
  }
17
21
  /** Produce compact metadata lines without inferring missing provider measurements.
@@ -41,47 +45,55 @@ export function metricLines(values, defaultModel, running) {
41
45
  ];
42
46
  }
43
47
  /** Fit the status summary to one terminal row; details remain available through /status.
44
- * @param fields - Priority-ordered status fields, followed by optional details.
48
+ * @param fields - Activity, model, cost, context, and cumulative usage groups in display order.
45
49
  * @param width - Available terminal columns.
46
50
  * @returns A terminal-safe single line, shortened by display width.
47
51
  */
48
52
  export function compactStatus(fields, width) {
53
+ return compactStatusFields(fields, width).filter(Boolean).join(' ');
54
+ }
55
+ /** Keep group identities while fitting plain text; ANSI styling is applied only after layout. */
56
+ function compactStatusFields(fields, width) {
57
+ if (width <= 0)
58
+ return [];
49
59
  const clean = fields.map(value => safeText(value).replace(/[\r\n\t]/g, ' '));
50
- const fit = (value, size) => {
51
- if (size < 2)
52
- return size === 1 ? '…' : '';
53
- const wrapped = wrapAnsi(value, size, { hard: true, wordWrap: false, trim: false });
54
- return wrapped.includes('\n') ? wrapAnsi(value, size - 1, { hard: true, wordWrap: false, trim: false }).split('\n')[0] + '…' : value;
55
- };
56
- const joins = (values) => values.filter(Boolean).join(' · ');
57
- const fits = (value) => !wrapAnsi(value, Math.max(1, width), { hard: true, wordWrap: false, trim: false }).includes('\n');
58
- if (fits(joins(clean)))
59
- return joins(clean);
60
- const core = clean.slice(0, 5);
61
- core[1] = fit(core[1] ?? '', Math.max(8, Math.min(28, Math.floor(width / 3))));
62
- core[2] = fit(core[2] ?? '', Math.max(6, Math.min(20, Math.floor(width / 5))));
63
- for (let size = Math.floor(width / 3); size >= 6 && !fits(joins(core)); size--) {
64
- core[1] = fit(clean[1] ?? '', size);
65
- core[2] = fit(clean[2] ?? '', Math.max(5, Math.floor(size * 0.6)));
60
+ const join = () => clean.filter(Boolean).join(' ');
61
+ const fits = () => !wrapAnsi(join(), width, { hard: true, wordWrap: false, trim: false }).includes('\n');
62
+ if (fits())
63
+ return clean;
64
+ // Keep the stable activity column while it fits; reclaim it on narrow terminals.
65
+ clean[0] = clean[0]?.trimEnd() ?? '';
66
+ if (fits())
67
+ return clean;
68
+ clean[3] = (clean[3] ?? '').replace(/[█░]+ /u, '');
69
+ if (fits())
70
+ return clean;
71
+ clean[1] = toolLine(clean[1] ?? '', Math.max(8, Math.min(24, Math.floor(width / 4))));
72
+ if (fits())
73
+ return clean;
74
+ for (const index of [4, 2, 1, 3]) {
75
+ clean[index] = '';
76
+ if (fits())
77
+ return clean;
66
78
  }
67
- const extras = clean.slice(5);
68
- while (extras.length && !fits(joins([...core, ...extras])))
69
- extras.pop();
70
- return fit(joins([...core, ...extras]), width);
79
+ return [toolLine(join(), width)];
71
80
  }
81
+ /** Compact token counts; one formatter is reused because construction dominates the format cost. */
82
+ const compactNumber = new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 });
72
83
  /** Render a live clock and selected-session metadata; the timer belongs to this mounted bar. */
73
- export const StatusBar = memo(function StatusBar({ controller, expanded = false }) {
84
+ export const StatusBar = memo(function StatusBar({ controller, expanded = false, width, paused = false }) {
85
+ const theme = useTheme();
74
86
  const { stdout } = useStdout();
75
87
  const [now, setNow] = useState(Date.now);
76
88
  const running = controller.running;
77
89
  const since = controller.workingSince;
78
90
  useEffect(() => {
79
91
  setNow(Date.now());
80
- if (!running)
92
+ if (!running || paused)
81
93
  return;
82
94
  const timer = setInterval(() => setNow(Date.now()), 1000);
83
95
  return () => clearInterval(timer);
84
- }, [running, since]);
96
+ }, [running, since, paused]);
85
97
  const state = controller.state;
86
98
  const workspace = state.workspaces.find(item => item.workspaceId === state.workspaceId);
87
99
  const view = controller.telemetry.view(state.sessionId);
@@ -94,32 +106,38 @@ export const StatusBar = memo(function StatusBar({ controller, expanded = false
94
106
  if (!expanded) {
95
107
  const selection = record(view.values.modelSelection);
96
108
  const route = record(running ? selection.lastUsed ?? selection.next ?? state.defaultModel : selection.next ?? selection.lastUsed ?? state.defaultModel);
97
- const model = typeof route.model === 'string' ? `${route.model}${typeof route.reasoningEffort === 'string' ? ` (${route.reasoningEffort})` : ''}` : 'model ?';
109
+ const model = typeof route.model === 'string' ? `${route.model.replace(/^deepseek-/, '')}${typeof route.reasoningEffort === 'string' ? ` · ${route.reasoningEffort}` : ''}` : 'model ?';
98
110
  const pressure = record(view.values.contextPressure);
99
111
  const used = numeric(pressure.projectedTokens) ?? numeric(pressure.pressureTokens);
100
112
  const capacity = numeric(pressure.contextWindow);
113
+ const percent = used !== undefined && capacity !== undefined && capacity > 0 ? Math.min(100, Math.round(used / capacity * 100)) : undefined;
114
+ const filled = percent === undefined ? 0 : Math.round(percent / 10);
115
+ const context = percent === undefined ? 'ctx ?' : `${'█'.repeat(filled)}${'░'.repeat(10 - filled)} ~${percent}%`;
101
116
  const usage = record(view.values.tokenUsage);
102
117
  const buckets = [usage.uncachedInputTokens, usage.outputTokens, usage.cacheReadTokens, usage.cacheWriteTokens].map(numeric);
103
118
  const total = buckets.every(value => value !== undefined) ? buckets.reduce((a, b) => a + b, 0) : undefined;
104
- const compactCount = (value) => value === undefined ? '?' : new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(value);
105
- const activity = running ? `Working ${since === undefined ? '?' : elapsedTime(now - since)}${state.transcript.activeTurnStartedAt === undefined ? '~' : ''}` : 'Idle';
119
+ const compactCount = (value) => value === undefined ? '?' : compactNumber.format(value);
120
+ const activity = running ? `◐ Working · ${since === undefined ? '?' : elapsedTime(now - since)}${state.transcript.activeTurnStartedAt === undefined ? '~' : ''} · Ctrl+C Stop` : '● Ready';
121
+ const warning = state.controlError || state.modelError || coverage === 'partial' ? '! ' : '';
106
122
  const fields = [
107
- `${!state.online ? 'Offline · ' : ''}${state.controlError || state.modelError || coverage === 'partial' ? '! ' : ''}${activity}`,
108
- model, `ws: ${workspace ? workspace.title : '—'}`,
109
- `ctx: ${used !== undefined && capacity !== undefined && capacity > 0 ? `~${Math.min(100, Math.round(used / capacity * 100))}%` : '?'}`,
110
- `tok: ${compactCount(total)}`,
111
- `in/out: ${compactCount(buckets[0])}/${compactCount(buckets[1])}`,
112
- `cache: ${compactCount(buckets[2])}/${compactCount(buckets[3])}`,
113
- `q:${count(view.queued)} jobs:${count(view.jobs)}`,
114
- running ? 'Esc/^C stop' : '^C exit', '/status',
123
+ `${warning}${!state.online ? 'Offline · ' : ''}${activity}`.padEnd(31),
124
+ model,
125
+ costs ? `${costs.hasSession(state.sessionId) ? compactCost(costs.total(state.sessionId)) : '?'}/${compactCost(costs.total(undefined, 1, Date.now()))}` : '?/?',
126
+ context,
127
+ `${count(numeric(record(view.values.sessionStats).turns))} turns · ${compactCount(total)} tok`,
128
+ ];
129
+ const fitted = compactStatusFields(fields, width ?? Math.max(1, (stdout.columns ?? 80) - 2));
130
+ const colors = [
131
+ !state.online ? theme.status.offline : warning ? theme.status.warning : running ? theme.status.working : theme.status.ready,
132
+ theme.status.model, costs ? theme.status.cost : theme.status.usage,
133
+ percent === undefined ? theme.status.usage : percent >= 95 ? theme.status.critical : percent >= 80 ? theme.status.warning : theme.status.context,
134
+ theme.status.usage,
115
135
  ];
116
- const width = Math.max(1, (stdout.columns ?? 80) - 2);
117
- const fee = costs ? ` · S:${sessionCost} D:${todayCost}` : '';
118
- return _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [compactStatus(fields, Math.max(1, width - fee.length)), fee] });
136
+ return _jsx(Text, { wrap: "truncate-end", children: fitted.map((text, index) => text ? _jsxs(Text, { children: [fitted.slice(0, index).some(Boolean) && _jsx(Text, { color: theme.colors.muted, children: ' ' }), _jsx(Text, { color: colors[index], bold: index === 0, children: text })] }, index) : null) });
119
137
  }
120
- return _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsxs(Text, { color: running ? 'yellow' : 'gray', children: [running
121
- ? `Working ${since === undefined ? 'unknown duration' : elapsedTime(now - since)}${state.transcript.activeTurnStartedAt === undefined ? ' (observed)' : ''} · Esc / Ctrl+C stop`
122
- : 'Idle · Ctrl+C exit', !state.online ? ' · disconnected, last known status' : ''] }), state.sessionId && _jsxs(Text, { children: ["Session ID: ", safeText(state.sessionId)] }), _jsxs(Text, { wrap: "truncate-end", children: ["Workspace: ", safeText(label)] }), metricLines(view.values, state.defaultModel, running).map((line, index) => _jsx(Text, { dimColor: true, children: safeText(line) }, index)), costs && _jsxs(Text, { dimColor: true, children: ["Cost (CNY estimate): Session ", sessionCost, " \u00B7 Today ", todayCost] }), costs && coverage === 'partial' && _jsxs(Text, { color: "yellow", children: ["Cost coverage incomplete: ", costs.error ? safeText(costs.error) : 'no complete scan yet'] }), _jsxs(Text, { dimColor: true, children: ["Queued: ", count(view.queued), " \u00B7 Active jobs: ", count(view.jobs)] }), state.controlError && _jsx(Text, { color: "yellow", children: safeText(state.controlError) }), state.modelError && _jsxs(Text, { color: "yellow", children: ["Model catalog unavailable: ", safeText(state.modelError)] })] });
138
+ return _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: theme.border, paddingX: 1, children: [_jsxs(Text, { color: running ? theme.colors.context : theme.colors.muted, children: [running
139
+ ? `◐ Working · ${since === undefined ? 'unknown duration' : elapsedTime(now - since)}${state.transcript.activeTurnStartedAt === undefined ? ' (observed)' : ''} · Ctrl+C Stop`
140
+ : '● Ready · Ctrl+C exit', !state.online ? ' · disconnected, last known status' : ''] }), _jsxs(Text, { wrap: "truncate-end", children: ["Host: ", safeText(controller.base), " \u00B7 ", safeText(state.status)] }), state.sessionId && _jsxs(Text, { children: ["Session ID: ", safeText(state.sessionId)] }), controller.sessionMode && _jsxs(Text, { children: ["Mode: ", safeText(controller.sessionMode)] }), _jsxs(Text, { wrap: "truncate-end", children: ["Workspace: ", safeText(label)] }), metricLines(view.values, state.defaultModel, running).map((line, index) => _jsx(Text, { dimColor: true, children: safeText(line) }, index)), costs && _jsxs(Text, { dimColor: true, children: ["Cost (CNY estimate): Session ", sessionCost, " \u00B7 Today ", todayCost] }), costs && coverage === 'partial' && _jsxs(Text, { color: theme.colors.context, children: ["Cost coverage incomplete: ", costs.error ? safeText(costs.error) : 'no complete scan yet'] }), _jsxs(Text, { dimColor: true, children: ["Turns: ", count(numeric(record(view.values.sessionStats).turns))] }), _jsxs(Text, { dimColor: true, children: ["Queued: ", count(view.queued), " \u00B7 Active jobs: ", count(view.jobs)] }), state.controlError && _jsx(Text, { color: theme.colors.context, children: safeText(state.controlError) }), state.presetError && _jsxs(Text, { color: theme.colors.context, children: ["Preset names unavailable: ", safeText(state.presetError)] }), state.modelError && _jsxs(Text, { color: theme.colors.context, children: ["Model catalog unavailable: ", safeText(state.modelError)] })] });
123
141
  });
124
142
  function record(value) {
125
143
  return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
@@ -134,3 +152,6 @@ function modelName(value) {
134
152
  return 'unknown';
135
153
  return safeText(`${model.provider}/${model.model}${typeof model.reasoningEffort === 'string' ? ` (${model.reasoningEffort})` : ''}`);
136
154
  }
155
+ function compactCost(total) {
156
+ return `~¥${total.amount.toFixed(2)}${total.unknown || total.estimated ? '*' : ''}`;
157
+ }
@@ -2,10 +2,13 @@
2
2
  import { type ObjectValue } from './wire.ts';
3
3
  /** Generation-local projection, inbox and background-job data for every session. */
4
4
  export declare class Telemetry {
5
+ private readonly retainedKeys?;
5
6
  private entries;
6
7
  private queues;
7
8
  private jobs;
8
9
  ready: boolean;
10
+ /** Optionally retain only projection capabilities consumed by this client. */
11
+ constructor(retainedKeys?: ReadonlySet<string> | undefined);
9
12
  /** Replace all control state on a new stream baseline, then accept replacement frames.
10
13
  * @param value - One decoded session/control frame.
11
14
  */
package/dist/telemetry.js CHANGED
@@ -2,10 +2,15 @@
2
2
  import { array, object, string } from "./wire.js";
3
3
  /** Generation-local projection, inbox and background-job data for every session. */
4
4
  export class Telemetry {
5
+ retainedKeys;
5
6
  entries = new Map();
6
7
  queues = new Map();
7
8
  jobs = new Map();
8
9
  ready = false;
10
+ /** Optionally retain only projection capabilities consumed by this client. */
11
+ constructor(retainedKeys) {
12
+ this.retainedKeys = retainedKeys;
13
+ }
9
14
  /** Replace all control state on a new stream baseline, then accept replacement frames.
10
15
  * @param value - One decoded session/control frame.
11
16
  */
@@ -36,6 +41,8 @@ export class Telemetry {
36
41
  return;
37
42
  if (frame.value === undefined)
38
43
  throw new Error('Missing projection value');
44
+ if (this.retainedKeys && !this.retainedKeys.has(key))
45
+ return;
39
46
  entry.values[key] = frame.value;
40
47
  entry.revisions.set(key, seq);
41
48
  }
@@ -60,6 +67,8 @@ export class Telemetry {
60
67
  if (seq < entry.baseline)
61
68
  return;
62
69
  for (const key of new Set([...Object.keys(entry.values), ...Object.keys(values)])) {
70
+ if (this.retainedKeys && !this.retainedKeys.has(key))
71
+ continue;
63
72
  if ((entry.revisions.get(key) ?? -1) > seq)
64
73
  continue;
65
74
  if (Object.hasOwn(values, key))
@@ -0,0 +1,15 @@
1
+ import type { RowKind } from './history.ts';
2
+ /** Themes provide semantic roles so components never depend on palette-specific color names. */
3
+ export interface Theme {
4
+ name: string;
5
+ colors: Record<RowKind, string>;
6
+ status: Record<'working' | 'ready' | 'offline' | 'model' | 'cost' | 'context' | 'warning' | 'critical' | 'usage', string>;
7
+ accent: string;
8
+ border: string;
9
+ }
10
+ /** Catppuccin Mocha, preserving the terminal background and its color-capability fallback. */
11
+ export declare const mocha: Theme;
12
+ /** Components read semantic colors from the current application theme. */
13
+ export declare const ThemeContext: import("react").Context<Theme>;
14
+ /** Return the theme selected by the surrounding application provider. */
15
+ export declare function useTheme(): Theme;
package/dist/theme.js ADDED
@@ -0,0 +1,15 @@
1
+ /** Semantic terminal palette. Source: https://catppuccin.com/palette/ (Mocha). */
2
+ import { createContext, useContext } from 'react';
3
+ /** Catppuccin Mocha, preserving the terminal background and its color-capability fallback. */
4
+ export const mocha = {
5
+ name: 'Catppuccin Mocha',
6
+ colors: { user: '#89b4fa', assistant: '#a6e3a1', context: '#f9e2af', text: '#cdd6f4',
7
+ reasoning: '#cba6f7', tool: '#89dceb', success: '#a6e3a1', error: '#f38ba8', muted: '#a6adc8' },
8
+ status: { working: '#f9e2af', ready: '#a6e3a1', offline: '#f38ba8', model: '#cba6f7',
9
+ cost: '#89dceb', context: '#a6e3a1', warning: '#f9e2af', critical: '#f38ba8', usage: '#a6adc8' },
10
+ accent: '#cba6f7', border: '#6c7086',
11
+ };
12
+ /** Components read semantic colors from the current application theme. */
13
+ export const ThemeContext = createContext(mocha);
14
+ /** Return the theme selected by the surrounding application provider. */
15
+ export function useTheme() { return useContext(ThemeContext); }
@@ -1,7 +1,9 @@
1
+ import type { HistoryLimits } from './memory.ts';
1
2
  import { type Json } from './wire.ts';
2
3
  interface ToolSummary {
3
4
  name: string;
4
5
  operation?: string;
6
+ command?: string;
5
7
  }
6
8
  /** Fit a tool operation to one terminal row without exposing the result body.
7
9
  * @param text - Tool name, status icon and optional operation.
@@ -9,23 +11,69 @@ interface ToolSummary {
9
11
  * @returns A single line with an ellipsis when shortened.
10
12
  */
11
13
  export declare function toolLine(text: string, width: number): string;
12
- /** Render known content blocks and preserve unknown plugin blocks as JSON. */
13
- export declare function contentText(content: Json | undefined, tools?: ReadonlyMap<string, ToolSummary>, width?: number): string;
14
+ /** Render known content blocks and preserve unknown plugin blocks as JSON.
15
+ * @param content - Message content blocks.
16
+ * @param tools - Tool summaries by call ID, when retained history provides them.
17
+ * @param width - Available terminal columns.
18
+ * @param reasoning - `full` keeps streamed reasoning at length; `row` folds a committed block into one row.
19
+ * @returns Blocks joined by newlines.
20
+ */
21
+ export declare function contentText(content: Json | undefined, tools?: ReadonlyMap<string, ToolSummary>, width?: number, reasoning?: 'row' | 'full'): string;
22
+ /** Semantic content stays separate from terminal rows, styles, and fold state. */
23
+ export interface MessagePart {
24
+ kind: 'text' | 'reasoning' | 'tool' | 'success' | 'error';
25
+ text: string;
26
+ /** Closed live reasoning can fold before the assistant message is committed. */
27
+ closed?: boolean;
28
+ }
14
29
  /** A displayed message retains the durable sequence for stable reconciliation. */
15
30
  export interface Message {
16
31
  seq: number;
17
32
  role: string;
18
33
  text: string;
34
+ parts: MessagePart[];
19
35
  compact?: boolean;
36
+ /** One-row rendering of committed reasoning, used only while the conversation folds reasoning. */
37
+ folded?: string;
38
+ }
39
+ /** Lightweight reasoning navigation row; summaries never contain a copy of the full thought. */
40
+ export interface ThoughtEntry {
41
+ seq: number;
42
+ promptSeq?: number;
43
+ prompt: string;
44
+ preview: string;
20
45
  }
21
46
  /** Opening snapshots replace all state; durable events are deduplicated by sequence. */
22
47
  export declare class Transcript {
48
+ private sizes;
49
+ private storedBytes;
50
+ private throughSeq;
51
+ private promptBeforeWindow;
52
+ private disposed;
53
+ /** Increments when cached bodies are evicted, so view state can drop obsolete references. */
54
+ memoryRevision: number;
55
+ private transientSeqs;
23
56
  private events;
24
57
  private blocks;
58
+ private thoughtIndex;
59
+ private liveProjection;
60
+ private closedBlocks;
61
+ private oldestSeq;
62
+ private turnMarker;
25
63
  private attempt;
26
64
  private nextIndex;
27
65
  private revision;
28
66
  private legacyStream;
67
+ private keysDirty;
68
+ private sortedSeqs;
69
+ private projectionRevision;
70
+ private projection;
71
+ private displaySeqsRevision;
72
+ private displayed;
73
+ private displayedMessages;
74
+ private legacyDirty;
75
+ private legacyPosition;
76
+ private legacySeq;
29
77
  /** Changes whenever a follow frame or older page can affect the rendered transcript. */
30
78
  version: number;
31
79
  cursor: number;
@@ -35,19 +83,49 @@ export declare class Transcript {
35
83
  accept(value: unknown): void;
36
84
  /** Add an older page without replacing the live tail. */
37
85
  addPage(value: unknown): void;
86
+ /** Durable read cutoff includes followed records that were not present in the opening snapshot. */
87
+ get readThrough(): number;
88
+ /** Estimated UTF-16 JSON payload bytes; excludes object overhead and unfinished stream blocks. */
89
+ get retainedBytes(): number;
90
+ /** Release reloadable prefix records with hysteresis, retaining a recent tail and active legacy chunks.
91
+ * @param limits - Soft record and payload budgets; callers suspend this operation while reading history.
92
+ * @returns Number of removed records. Paging remains possible at the advanced durable cutoff.
93
+ */
94
+ trimHistory(limits: HistoryLimits): number;
95
+ /** Empty an inactive session immediately; late frames cannot repopulate a disposed transcript. */
96
+ dispose(): void;
97
+ private dropProjections;
98
+ private storeEvent;
99
+ private deleteEvent;
38
100
  /** Start of the open durable turn, when its timestamp is present in the retained window. */
39
101
  get activeTurnStartedAt(): number | undefined;
40
- /** Earliest retained record, used with the opening cursor for backward paging. */
102
+ /** Cached prompt/reasoning summaries, oldest first; stream deltas never invalidate this index. */
103
+ get thoughts(): ThoughtEntry[];
104
+ /** Latest loaded user prompt summary, sharing the durable thought index. */
105
+ get latestPrompt(): string;
106
+ /** Number of semantic records and unfinished legacy chunks held by the client. */
107
+ get retainedRecordCount(): number;
108
+ /** Earliest loaded record, used with the opening cursor for backward paging. */
41
109
  get beforeSeq(): number | undefined;
110
+ /** Retained sequences in ascending order; resorted only when a retained event changes. */
111
+ private sortedKeys;
112
+ /** Sequences of the event types that contribute a displayed message. */
113
+ private displaySeqs;
42
114
  /** Return append-origin conversation only; model-only replacement copies stay hidden. */
43
115
  get messages(): Message[];
44
116
  /** Project history with single-row tool operations at the caller's terminal width.
45
117
  * @param width - Available terminal columns.
46
- * @returns Conversation messages with compact tool-only rows.
118
+ * @returns Conversation messages with compact tool-only rows; the array is shared and must not be mutated.
47
119
  */
48
120
  messagesForWidth(width: number): Message[];
121
+ /** Build the message list, reusing each unchanged event's earlier projection at the same width. */
122
+ private project;
49
123
  /** Text from the active attempt is transient and never duplicated into durable history. */
50
124
  get liveText(): string;
125
+ /** Attempt identity resets the view's live fold preference between responses. */
126
+ get liveAttemptKey(): string | undefined;
127
+ /** Whether an active attempt has content, without joining its potentially long reasoning. */
128
+ get hasLiveContent(): boolean;
51
129
  /** Whether the active stream contains only tool operations, with no assistant prose. */
52
130
  get liveToolOnly(): boolean;
53
131
  /** Render live tool operations within a terminal row.
@@ -55,9 +133,16 @@ export declare class Transcript {
55
133
  * @returns Transient assistant text with clipped tool rows.
56
134
  */
57
135
  liveTextForWidth(width: number): string;
136
+ /** Live semantic blocks; completed reasoning folds independently of the unfinished answer.
137
+ * @param width - Terminal width used for compact tool summaries.
138
+ * @returns Ordered blocks without terminal style escapes.
139
+ */
140
+ liveParts(width: number): MessagePart[];
58
141
  private addRecords;
59
- /** Older hosts log chunks directly; replay only the unfinished attempt for live display. */
60
- private rebuildLegacyStream;
142
+ /** Older hosts log chunks directly; fold the unfinished attempt into the live tail for display.
143
+ * A page behind the folded position, or a replaced event, forces a full re-fold; streamed chunks extend it.
144
+ */
145
+ private foldLegacy;
61
146
  private validatePackedTiming;
62
147
  private chunk;
63
148
  }