@itookit/dsht 0.1.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.
@@ -0,0 +1,36 @@
1
+ /** Shared display names and unambiguous slash-command target resolution. */
2
+ import { safeText, string } from "./wire.js";
3
+ /** Parse the short navigation commands and their equivalent long aliases. */
4
+ export function navigationCommand(value) {
5
+ const match = /^\/(ws|workspace|workspaces|s|session|sessions)(?:\s+(.+))?$/.exec(value);
6
+ if (!match)
7
+ return undefined;
8
+ return { kind: match[1] === 'ws' || match[1].startsWith('workspace') ? 'workspace' : 'session', query: match[2] };
9
+ }
10
+ /** Resolve the host's title projection, falling back to the session ID. */
11
+ export function sessionLabel(session) {
12
+ const projections = session.projections;
13
+ if (projections && typeof projections === 'object' && !Array.isArray(projections)) {
14
+ const values = projections.values;
15
+ const title = values && typeof values === 'object' && !Array.isArray(values) ? values.title : undefined;
16
+ if (typeof title === 'string' && safeText(title).trim())
17
+ return safeText(title).trim();
18
+ if (title && typeof title === 'object' && !Array.isArray(title) && typeof title.title === 'string' && safeText(title.title).trim())
19
+ return safeText(title.title).trim();
20
+ }
21
+ return string(session.sessionId);
22
+ }
23
+ /** Match an exact ID or name before a unique ID prefix; never choose an ambiguous target. */
24
+ export function resolveTarget(items, query, id, names) {
25
+ const target = query.replace(/^(["'])(.*)\1$/, '$2');
26
+ const exactId = items.find(item => item[id] === target);
27
+ if (exactId)
28
+ return exactId;
29
+ const exactNames = items.filter(item => names(item).includes(target));
30
+ const matches = exactNames.length ? exactNames : items.filter(item => string(item[id]).startsWith(target));
31
+ if (matches.length === 1)
32
+ return matches[0];
33
+ if (matches.length > 1)
34
+ throw new Error(`Ambiguous target: ${target}. Use a full ID.`);
35
+ throw new Error(`Target not found: ${target}`);
36
+ }
@@ -0,0 +1,25 @@
1
+ /** A path relative to the remote session's working directory. */
2
+ export interface FileReference {
3
+ path: string;
4
+ kind: 'file' | 'directory';
5
+ }
6
+ /** Find the unfinished reference at the end of the draft; email addresses do not trigger it.
7
+ * @param text - Complete composer draft.
8
+ * @returns The replaceable token and host query, or undefined outside a reference.
9
+ */
10
+ export declare function activeReference(text: string): {
11
+ prefix: string;
12
+ query: string;
13
+ quoted: boolean;
14
+ } | undefined;
15
+ /** Encode a candidate in Harness prompt syntax; directories keep completion open.
16
+ * @param candidate - Remote path and entry kind.
17
+ * @param quoted - Preserve an explicitly opened quote.
18
+ * @returns Mention text, or undefined for paths the mention grammar cannot encode.
19
+ */
20
+ export declare function fileMention(candidate: FileReference, quoted?: boolean): string | undefined;
21
+ /** Validate remote candidates and omit paths that cannot be inserted safely.
22
+ * @param value - Decoded fileReferences/list result.
23
+ * @returns Ordered file and directory candidates.
24
+ */
25
+ export declare function fileReferences(value: unknown): FileReference[];
@@ -0,0 +1,40 @@
1
+ /** Independent adapter for Harness path-only @ references; no file bytes are read. */
2
+ import { array, object, string } from "./wire.js";
3
+ /** Find the unfinished reference at the end of the draft; email addresses do not trigger it.
4
+ * @param text - Complete composer draft.
5
+ * @returns The replaceable token and host query, or undefined outside a reference.
6
+ */
7
+ export function activeReference(text) {
8
+ const quoted = /(?:^|\s)(@"([^"]*))$/u.exec(text);
9
+ if (quoted)
10
+ return { prefix: quoted[1], query: quoted[2], quoted: true };
11
+ const plain = /(?:^|\s)(@([^\s"]*))$/u.exec(text);
12
+ if (plain)
13
+ return { prefix: plain[1], query: plain[2], quoted: false };
14
+ return undefined;
15
+ }
16
+ /** Encode a candidate in Harness prompt syntax; directories keep completion open.
17
+ * @param candidate - Remote path and entry kind.
18
+ * @param quoted - Preserve an explicitly opened quote.
19
+ * @returns Mention text, or undefined for paths the mention grammar cannot encode.
20
+ */
21
+ export function fileMention(candidate, quoted = false) {
22
+ const path = candidate.path + (candidate.kind === 'directory' ? '/' : '');
23
+ if (/[\u0000-\u001f\u007f-\u009f"]/u.test(path))
24
+ return undefined;
25
+ if (!quoted && !/\s/u.test(path))
26
+ return `@${path}`;
27
+ return `@"${path}${candidate.kind === 'file' ? '"' : ''}`;
28
+ }
29
+ /** Validate remote candidates and omit paths that cannot be inserted safely.
30
+ * @param value - Decoded fileReferences/list result.
31
+ * @returns Ordered file and directory candidates.
32
+ */
33
+ export function fileReferences(value) {
34
+ return array(value).map(item => {
35
+ const row = object(item);
36
+ if (row.kind !== 'file' && row.kind !== 'directory')
37
+ throw new Error('Unknown file reference kind');
38
+ return { path: string(row.path), kind: row.kind };
39
+ }).filter(row => fileMention(row) !== undefined);
40
+ }
@@ -0,0 +1,26 @@
1
+ import type { Controller } from './controller.ts';
2
+ import { type ObjectValue } from './wire.ts';
3
+ /** Format elapsed wall time, clamping clock skew instead of displaying negative durations.
4
+ * @param milliseconds - Elapsed duration.
5
+ * @returns Minute/second display, with hours when needed.
6
+ */
7
+ export declare function elapsedTime(milliseconds: number): string;
8
+ /** Produce compact metadata lines without inferring missing provider measurements.
9
+ * @param values - Current host projection values.
10
+ * @param defaultModel - Host catalog default used before a session selects a route.
11
+ * @param running - Whether the current route or next route is primary.
12
+ * @returns Model, approximate context occupancy, and cumulative token buckets.
13
+ */
14
+ export declare function metricLines(values: ObjectValue, defaultModel: ObjectValue | undefined, running: boolean): string[];
15
+ /** Fit the status summary to one terminal row; details remain available through /status.
16
+ * @param fields - Priority-ordered status fields, followed by optional details.
17
+ * @param width - Available terminal columns.
18
+ * @returns A terminal-safe single line, shortened by display width.
19
+ */
20
+ export declare function compactStatus(fields: string[], width: number): string;
21
+ /** Render a live clock and selected-session metadata; the timer belongs to this mounted bar. */
22
+ export declare const StatusBar: import("react").NamedExoticComponent<{
23
+ controller: Controller;
24
+ expanded?: boolean;
25
+ revision?: number;
26
+ }>;
package/dist/status.js ADDED
@@ -0,0 +1,135 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /** Terminal status from host projections; cumulative usage and estimated context stay distinct. */
3
+ import { memo, useEffect, useState } from 'react';
4
+ import { Box, Text, useStdout } from 'ink';
5
+ import wrapAnsi from 'wrap-ansi';
6
+ import { costText } from "./cost.js";
7
+ import { safeText } from "./wire.js";
8
+ /** Format elapsed wall time, clamping clock skew instead of displaying negative durations.
9
+ * @param milliseconds - Elapsed duration.
10
+ * @returns Minute/second display, with hours when needed.
11
+ */
12
+ export function elapsedTime(milliseconds) {
13
+ const seconds = Math.max(0, Math.floor(milliseconds / 1000));
14
+ const minutes = Math.floor(seconds / 60);
15
+ return `${minutes >= 60 ? `${Math.floor(minutes / 60)}h ` : ''}${minutes % 60}m ${seconds % 60}s`;
16
+ }
17
+ /** Produce compact metadata lines without inferring missing provider measurements.
18
+ * @param values - Current host projection values.
19
+ * @param defaultModel - Host catalog default used before a session selects a route.
20
+ * @param running - Whether the current route or next route is primary.
21
+ * @returns Model, approximate context occupancy, and cumulative token buckets.
22
+ */
23
+ export function metricLines(values, defaultModel, running) {
24
+ const selection = record(values.modelSelection);
25
+ const current = running ? selection.lastUsed ?? selection.next ?? defaultModel : selection.next ?? selection.lastUsed ?? defaultModel;
26
+ const model = modelName(current);
27
+ const next = modelName(selection.next);
28
+ const pressure = record(values.contextPressure);
29
+ const used = numeric(pressure.projectedTokens) ?? numeric(pressure.pressureTokens);
30
+ const capacity = numeric(pressure.contextWindow);
31
+ const context = used !== undefined && capacity !== undefined && capacity > 0
32
+ ? `~${Math.min(100, Math.round(used / capacity * 100))}% (${count(used)} / ${count(capacity)})`
33
+ : 'unknown';
34
+ const usage = record(values.tokenUsage);
35
+ const buckets = [usage.uncachedInputTokens, usage.outputTokens, usage.cacheReadTokens, usage.cacheWriteTokens].map(numeric);
36
+ const total = buckets.every(value => value !== undefined) ? buckets.reduce((a, b) => a + b, 0) : undefined;
37
+ return [
38
+ `Model: ${model}${running && next !== 'unknown' && next !== model ? ` · Next: ${next}` : ''}`,
39
+ `Context: ${context} · Tokens: ${count(total)} total`,
40
+ `In (uncached): ${count(buckets[0])} · Out: ${count(buckets[1])} · Cache read/write: ${count(buckets[2])}/${count(buckets[3])}`,
41
+ ];
42
+ }
43
+ /** Fit the status summary to one terminal row; details remain available through /status.
44
+ * @param fields - Priority-ordered status fields, followed by optional details.
45
+ * @param width - Available terminal columns.
46
+ * @returns A terminal-safe single line, shortened by display width.
47
+ */
48
+ export function compactStatus(fields, width) {
49
+ 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)));
66
+ }
67
+ const extras = clean.slice(5);
68
+ while (extras.length && !fits(joins([...core, ...extras])))
69
+ extras.pop();
70
+ return fit(joins([...core, ...extras]), width);
71
+ }
72
+ /** 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 }) {
74
+ const { stdout } = useStdout();
75
+ const [now, setNow] = useState(Date.now);
76
+ const running = controller.running;
77
+ const since = controller.workingSince;
78
+ useEffect(() => {
79
+ setNow(Date.now());
80
+ if (!running)
81
+ return;
82
+ const timer = setInterval(() => setNow(Date.now()), 1000);
83
+ return () => clearInterval(timer);
84
+ }, [running, since]);
85
+ const state = controller.state;
86
+ const workspace = state.workspaces.find(item => item.workspaceId === state.workspaceId);
87
+ const view = controller.telemetry.view(state.sessionId);
88
+ const costs = controller.costs;
89
+ const sessionCost = costs?.hasSession(state.sessionId) ? costText(costs.total(state.sessionId)) : '?';
90
+ const todayEstimate = costs ? costText(costs.total(undefined, 1, Date.now())) : '?';
91
+ const todayCost = costs && (!costs.scannedAt || costs.error) && !todayEstimate.endsWith('*') ? todayEstimate + '*' : todayEstimate;
92
+ const label = workspace ? `${workspace.title} · ${workspace.path}` : 'none selected';
93
+ if (!expanded) {
94
+ const selection = record(view.values.modelSelection);
95
+ const route = record(running ? selection.lastUsed ?? selection.next ?? state.defaultModel : selection.next ?? selection.lastUsed ?? state.defaultModel);
96
+ const model = typeof route.model === 'string' ? `${route.model}${typeof route.reasoningEffort === 'string' ? ` (${route.reasoningEffort})` : ''}` : 'model ?';
97
+ const pressure = record(view.values.contextPressure);
98
+ const used = numeric(pressure.projectedTokens) ?? numeric(pressure.pressureTokens);
99
+ const capacity = numeric(pressure.contextWindow);
100
+ const usage = record(view.values.tokenUsage);
101
+ const buckets = [usage.uncachedInputTokens, usage.outputTokens, usage.cacheReadTokens, usage.cacheWriteTokens].map(numeric);
102
+ const total = buckets.every(value => value !== undefined) ? buckets.reduce((a, b) => a + b, 0) : undefined;
103
+ const compactCount = (value) => value === undefined ? '?' : new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(value);
104
+ const activity = running ? `Working ${since === undefined ? '?' : elapsedTime(now - since)}${state.transcript.activeTurnStartedAt === undefined ? '~' : ''}` : 'Idle';
105
+ const fields = [
106
+ `${!state.online ? 'Offline · ' : ''}${state.controlError || state.modelError ? '! ' : ''}${activity}`,
107
+ model, `ws: ${workspace ? workspace.title : '—'}`,
108
+ `ctx: ${used !== undefined && capacity !== undefined && capacity > 0 ? `~${Math.min(100, Math.round(used / capacity * 100))}%` : '?'}`,
109
+ `tok: ${compactCount(total)}`,
110
+ `in/out: ${compactCount(buckets[0])}/${compactCount(buckets[1])}`,
111
+ `cache: ${compactCount(buckets[2])}/${compactCount(buckets[3])}`,
112
+ `q:${count(view.queued)} jobs:${count(view.jobs)}`,
113
+ running ? 'Esc/^C stop' : '^C exit', '/status',
114
+ ];
115
+ const width = Math.max(1, (stdout.columns ?? 80) - 2);
116
+ const fee = costs ? ` · S:${sessionCost} D:${todayCost}` : '';
117
+ return _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [compactStatus(fields, Math.max(1, width - fee.length)), fee] });
118
+ }
119
+ return _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsxs(Text, { color: running ? 'yellow' : 'gray', children: [running
120
+ ? `Working ${since === undefined ? 'unknown duration' : elapsedTime(now - since)}${state.transcript.activeTurnStartedAt === undefined ? ' (observed)' : ''} · Esc / Ctrl+C stop`
121
+ : '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] }), _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)] })] });
122
+ });
123
+ function record(value) {
124
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
125
+ }
126
+ function numeric(value) {
127
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
128
+ }
129
+ function count(value) { return value === undefined ? '?' : value.toLocaleString('en-US'); }
130
+ function modelName(value) {
131
+ const model = record(value);
132
+ if (typeof model.model !== 'string' || typeof model.provider !== 'string')
133
+ return 'unknown';
134
+ return safeText(`${model.provider}/${model.model}${typeof model.reasoningEffort === 'string' ? ` (${model.reasoningEffort})` : ''}`);
135
+ }
@@ -0,0 +1,28 @@
1
+ /** Host projection values with per-key watermarks; snapshots never roll back newer updates. */
2
+ import { type ObjectValue } from './wire.ts';
3
+ /** Generation-local projection, inbox and background-job data for every session. */
4
+ export declare class Telemetry {
5
+ private entries;
6
+ private queues;
7
+ private jobs;
8
+ ready: boolean;
9
+ /** Replace all control state on a new stream baseline, then accept replacement frames.
10
+ * @param value - One decoded session/control frame.
11
+ */
12
+ accept(value: unknown): void;
13
+ /** Merge a complete follow snapshot without restoring absent or older projection values.
14
+ * @param id - Session identity.
15
+ * @param value - Projection baseline, when supplied by the host.
16
+ */
17
+ snapshot(id: string, value: unknown): void;
18
+ /** Read current values for one session; missing capabilities remain absent.
19
+ * @param id - Selected session identity, if any.
20
+ * @returns Projection values and known queue/job counts.
21
+ */
22
+ view(id?: string): {
23
+ values: ObjectValue;
24
+ queued?: number;
25
+ jobs?: number;
26
+ };
27
+ private entry;
28
+ }
@@ -0,0 +1,97 @@
1
+ /** Host projection values with per-key watermarks; snapshots never roll back newer updates. */
2
+ import { array, object, string } from "./wire.js";
3
+ /** Generation-local projection, inbox and background-job data for every session. */
4
+ export class Telemetry {
5
+ entries = new Map();
6
+ queues = new Map();
7
+ jobs = new Map();
8
+ ready = false;
9
+ /** Replace all control state on a new stream baseline, then accept replacement frames.
10
+ * @param value - One decoded session/control frame.
11
+ */
12
+ accept(value) {
13
+ const frame = object(value);
14
+ if (frame.type === 'baseline') {
15
+ const baseline = object(frame.value);
16
+ this.entries.clear();
17
+ this.queues.clear();
18
+ this.jobs.clear();
19
+ for (const [id, projection] of Object.entries(object(baseline.projections)))
20
+ this.snapshot(id, projection);
21
+ for (const [id, items] of Object.entries(object(baseline.queues)))
22
+ this.queues.set(id, array(items).length);
23
+ for (const [id, items] of Object.entries(object(baseline.jobs)))
24
+ this.jobs.set(id, activeJobs(items));
25
+ this.ready = true;
26
+ return;
27
+ }
28
+ if (!this.ready)
29
+ throw new Error('Session control update before baseline');
30
+ const id = string(frame.sessionId);
31
+ if (frame.type === 'projection') {
32
+ const entry = this.entry(id);
33
+ const key = string(frame.key);
34
+ const seq = sequence(frame.seq);
35
+ if (seq < (entry.revisions.get(key) ?? entry.baseline))
36
+ return;
37
+ if (frame.value === undefined)
38
+ throw new Error('Missing projection value');
39
+ entry.values[key] = frame.value;
40
+ entry.revisions.set(key, seq);
41
+ }
42
+ else if (frame.type === 'queue')
43
+ this.queues.set(id, array(frame.items).length);
44
+ else if (frame.type === 'jobs')
45
+ this.jobs.set(id, activeJobs(frame.items));
46
+ else
47
+ throw new Error('Unknown session control frame');
48
+ }
49
+ /** Merge a complete follow snapshot without restoring absent or older projection values.
50
+ * @param id - Session identity.
51
+ * @param value - Projection baseline, when supplied by the host.
52
+ */
53
+ snapshot(id, value) {
54
+ if (value === undefined)
55
+ return;
56
+ const baseline = object(value);
57
+ const seq = sequence(baseline.asOfSeq);
58
+ const values = object(baseline.values);
59
+ const entry = this.entry(id);
60
+ if (seq < entry.baseline)
61
+ return;
62
+ for (const key of new Set([...Object.keys(entry.values), ...Object.keys(values)])) {
63
+ if ((entry.revisions.get(key) ?? -1) > seq)
64
+ continue;
65
+ if (Object.hasOwn(values, key))
66
+ entry.values[key] = values[key];
67
+ else
68
+ delete entry.values[key];
69
+ entry.revisions.delete(key);
70
+ }
71
+ entry.baseline = seq;
72
+ }
73
+ /** Read current values for one session; missing capabilities remain absent.
74
+ * @param id - Selected session identity, if any.
75
+ * @returns Projection values and known queue/job counts.
76
+ */
77
+ view(id) {
78
+ return { values: id ? this.entries.get(id)?.values ?? {} : {},
79
+ queued: id ? this.queues.get(id) : undefined, jobs: id ? this.jobs.get(id) : undefined };
80
+ }
81
+ entry(id) {
82
+ let entry = this.entries.get(id);
83
+ if (!entry) {
84
+ entry = { baseline: -1, values: Object.create(null), revisions: new Map() };
85
+ this.entries.set(id, entry);
86
+ }
87
+ return entry;
88
+ }
89
+ }
90
+ function sequence(value) {
91
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < -1)
92
+ throw new Error('Invalid projection watermark');
93
+ return value;
94
+ }
95
+ function activeJobs(value) {
96
+ return array(value).filter(item => ['running', 'stopping'].includes(string(object(item).status))).length;
97
+ }
@@ -0,0 +1,64 @@
1
+ import { type Json } from './wire.ts';
2
+ interface ToolSummary {
3
+ name: string;
4
+ operation?: string;
5
+ }
6
+ /** Fit a tool operation to one terminal row without exposing the result body.
7
+ * @param text - Tool name, status icon and optional operation.
8
+ * @param width - Available terminal columns.
9
+ * @returns A single line with an ellipsis when shortened.
10
+ */
11
+ 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
+ /** A displayed message retains the durable sequence for stable reconciliation. */
15
+ export interface Message {
16
+ seq: number;
17
+ role: string;
18
+ text: string;
19
+ compact?: boolean;
20
+ }
21
+ /** Opening snapshots replace all state; durable events are deduplicated by sequence. */
22
+ export declare class Transcript {
23
+ private events;
24
+ private blocks;
25
+ private attempt;
26
+ private nextIndex;
27
+ private revision;
28
+ private legacyStream;
29
+ /** Changes whenever a follow frame or older page can affect the rendered transcript. */
30
+ version: number;
31
+ cursor: number;
32
+ hasMore: boolean;
33
+ ready: boolean;
34
+ /** Apply one follow frame; reject stream gaps so callers can reopen a snapshot. */
35
+ accept(value: unknown): void;
36
+ /** Add an older page without replacing the live tail. */
37
+ addPage(value: unknown): void;
38
+ /** Start of the open durable turn, when its timestamp is present in the retained window. */
39
+ get activeTurnStartedAt(): number | undefined;
40
+ /** Earliest retained record, used with the opening cursor for backward paging. */
41
+ get beforeSeq(): number | undefined;
42
+ /** Return append-origin conversation only; model-only replacement copies stay hidden. */
43
+ get messages(): Message[];
44
+ /** Project history with single-row tool operations at the caller's terminal width.
45
+ * @param width - Available terminal columns.
46
+ * @returns Conversation messages with compact tool-only rows.
47
+ */
48
+ messagesForWidth(width: number): Message[];
49
+ /** Text from the active attempt is transient and never duplicated into durable history. */
50
+ get liveText(): string;
51
+ /** Whether the active stream contains only tool operations, with no assistant prose. */
52
+ get liveToolOnly(): boolean;
53
+ /** Render live tool operations within a terminal row.
54
+ * @param width - Available terminal columns.
55
+ * @returns Transient assistant text with clipped tool rows.
56
+ */
57
+ liveTextForWidth(width: number): string;
58
+ private addRecords;
59
+ /** Older hosts log chunks directly; replay only the unfinished attempt for live display. */
60
+ private rebuildLegacyStream;
61
+ private validatePackedTiming;
62
+ private chunk;
63
+ }
64
+ export {};