@itookit/dsht 0.3.8 → 0.5.2
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.i18n.yaml +2 -2
- package/README.md +33 -12
- package/README.zh.md +35 -14
- package/dist/catalog/controller.d.ts +26 -6
- package/dist/catalog/controller.js +73 -45
- package/dist/catalog/index.d.ts +1 -0
- package/dist/cli/dsht.js +206 -18
- package/dist/cli/startup.d.ts +40 -0
- package/dist/cli/startup.js +314 -0
- package/dist/cli/trace-summary.d.ts +78 -0
- package/dist/cli/trace-summary.js +241 -0
- package/dist/cli/verifier.d.ts +64 -0
- package/dist/cli/verifier.js +265 -0
- package/dist/contracts.d.ts +359 -0
- package/dist/contracts.js +1 -0
- package/dist/controller/commands.d.ts +47 -0
- package/dist/controller/commands.js +322 -0
- package/dist/controller/connection-streams.d.ts +22 -0
- package/dist/controller/connection-streams.js +105 -0
- package/dist/controller/connection.d.ts +24 -31
- package/dist/controller/connection.js +48 -111
- package/dist/controller/controller.d.ts +412 -178
- package/dist/controller/controller.js +713 -167
- package/dist/controller/foreground.d.ts +44 -0
- package/dist/controller/foreground.js +79 -0
- package/dist/controller/index.d.ts +8 -1
- package/dist/controller/index.js +5 -0
- package/dist/controller/loop-contract.d.ts +136 -0
- package/dist/controller/loop-contract.js +308 -0
- package/dist/controller/loop-coordinator.d.ts +48 -0
- package/dist/controller/loop-coordinator.js +647 -0
- package/dist/controller/loop-prompts-schema.d.ts +56 -0
- package/dist/controller/loop-prompts-schema.js +144 -0
- package/dist/controller/loop-prompts.d.ts +55 -0
- package/dist/controller/loop-prompts.generated.d.ts +104 -0
- package/dist/controller/loop-prompts.generated.js +185 -0
- package/dist/controller/loop-prompts.js +104 -0
- package/dist/controller/loop-protocols.d.ts +39 -0
- package/dist/controller/loop-protocols.js +115 -0
- package/dist/controller/loop.d.ts +275 -0
- package/dist/controller/loop.js +378 -0
- package/dist/controller/prompts.d.ts +54 -0
- package/dist/controller/prompts.js +162 -0
- package/dist/controller/trace-log.d.ts +45 -0
- package/dist/controller/trace-log.js +144 -0
- package/dist/controller/verifier.d.ts +130 -0
- package/dist/controller/verifier.js +75 -0
- package/dist/cost/controller.d.ts +1 -1
- package/dist/cost/controller.js +12 -5
- package/dist/cost/index.d.ts +1 -1
- package/dist/cost/index.js +1 -1
- package/dist/cost/ledger.d.ts +0 -1
- package/dist/cost/ledger.js +0 -1
- package/dist/cost/scanner.js +1 -0
- package/dist/json.d.ts +18 -0
- package/dist/json.js +19 -0
- package/dist/references.d.ts +25 -0
- package/dist/references.js +26 -0
- package/dist/session/connection-view.d.ts +2 -11
- package/dist/session/controller.d.ts +94 -105
- package/dist/session/controller.js +262 -536
- package/dist/session/history-reader.d.ts +32 -0
- package/dist/session/history-reader.js +170 -0
- package/dist/session/history.d.ts +6 -18
- package/dist/session/history.js +1 -24
- package/dist/session/index.d.ts +9 -4
- package/dist/session/index.js +7 -3
- package/dist/session/info.d.ts +20 -82
- package/dist/session/info.js +52 -25
- package/dist/session/interactions.d.ts +26 -0
- package/dist/session/interactions.js +75 -0
- package/dist/session/markdown.js +1 -1
- package/dist/session/math.js +1 -1
- package/dist/session/mutation-gate.d.ts +51 -0
- package/dist/session/mutation-gate.js +73 -0
- package/dist/session/navigation.d.ts +2 -89
- package/dist/session/navigation.js +2 -129
- package/dist/session/navigator.d.ts +47 -0
- package/dist/session/navigator.js +158 -0
- package/dist/session/peek.d.ts +38 -0
- package/dist/session/peek.js +103 -0
- package/dist/session/prompt-backfill.d.ts +23 -0
- package/dist/session/prompt-backfill.js +88 -0
- package/dist/session/references.d.ts +2 -20
- package/dist/session/references.js +1 -26
- package/dist/session/runtime.d.ts +26 -0
- package/dist/session/runtime.js +28 -0
- package/dist/session/state.d.ts +20 -0
- package/dist/session/state.js +1 -0
- package/dist/session/telemetry.d.ts +25 -17
- package/dist/session/telemetry.js +66 -60
- package/dist/session/transcript.d.ts +5 -7
- package/dist/session/transcript.js +2 -15
- package/dist/session/types.d.ts +25 -0
- package/dist/session/types.js +0 -1
- package/dist/session-title.d.ts +9 -0
- package/dist/session-title.js +21 -0
- package/dist/shell/controller.d.ts +31 -1
- package/dist/shell/controller.js +34 -2
- package/dist/shell/index.d.ts +3 -3
- package/dist/shell/index.js +2 -2
- package/dist/shell/runner.d.ts +10 -0
- package/dist/shell/runner.js +48 -9
- package/dist/slash/index.d.ts +10 -0
- package/dist/slash/index.js +7 -0
- package/dist/slash/parse.d.ts +42 -0
- package/dist/slash/parse.js +259 -0
- package/dist/slash/pipeline.d.ts +140 -0
- package/dist/slash/pipeline.js +115 -0
- package/dist/slash/registry.d.ts +88 -0
- package/dist/slash/registry.js +177 -0
- package/dist/slash/types.d.ts +126 -0
- package/dist/slash/types.js +1 -0
- package/dist/state.d.ts +16 -18
- package/dist/state.js +4 -3
- package/dist/text.d.ts +28 -0
- package/dist/text.js +55 -0
- package/dist/transport/client.d.ts +4 -3
- package/dist/transport/client.js +71 -25
- package/dist/transport/events.d.ts +104 -0
- package/dist/transport/events.js +149 -0
- package/dist/transport/wire.d.ts +9 -17
- package/dist/transport/wire.js +2 -27
- package/dist/ui/app.js +750 -550
- package/dist/ui/chat/header.js +1 -1
- package/dist/ui/chat/history-view.d.ts +1 -1
- package/dist/ui/chat/loop-status.d.ts +11 -0
- package/dist/ui/chat/loop-status.js +28 -0
- package/dist/ui/chat/navigation-model.d.ts +86 -0
- package/dist/ui/chat/navigation-model.js +107 -0
- package/dist/ui/chat/shell-view.d.ts +17 -2
- package/dist/ui/chat/shell-view.js +45 -3
- package/dist/ui/chat/status.d.ts +47 -3
- package/dist/ui/chat/status.js +65 -50
- package/dist/ui/chat/use-history-view.d.ts +69 -0
- package/dist/ui/chat/use-history-view.js +123 -0
- package/dist/ui/chat/viewport.d.ts +1 -1
- package/dist/ui/dialogs/cost.d.ts +21 -4
- package/dist/ui/dialogs/cost.js +7 -12
- package/dist/ui/dialogs/index.d.ts +22 -5
- package/dist/ui/dialogs/index.js +19 -3
- package/dist/ui/dialogs/loop.d.ts +43 -0
- package/dist/ui/dialogs/loop.js +224 -0
- package/dist/ui/dialogs/peek.d.ts +25 -0
- package/dist/ui/dialogs/peek.js +35 -0
- package/dist/ui/dialogs/picker.d.ts +2 -0
- package/dist/ui/dialogs/picker.js +4 -2
- package/dist/ui/dialogs/use-panels.d.ts +53 -0
- package/dist/ui/dialogs/use-panels.js +51 -0
- package/dist/ui/input/mouse.d.ts +12 -2
- package/dist/ui/input/mouse.js +20 -7
- package/dist/ui/input/references.d.ts +1 -1
- package/dist/ui/input/use-composer.d.ts +35 -0
- package/dist/ui/input/use-composer.js +109 -0
- package/dist/ui/input/use-deferred-lines.d.ts +16 -0
- package/dist/ui/input/use-deferred-lines.js +54 -0
- package/dist/ui/input/use-history-recall.d.ts +20 -0
- package/dist/ui/input/use-history-recall.js +47 -0
- package/dist/ui/status/model.d.ts +7 -0
- package/dist/ui/status/model.js +5 -0
- package/dist/ui/theme/index.d.ts +1 -1
- package/package.json +6 -4
- package/dist/ui/commands/parse.d.ts +0 -104
- package/dist/ui/commands/parse.js +0 -135
- package/dist/ui/commands/registry.d.ts +0 -33
- package/dist/ui/commands/registry.js +0 -73
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HostAccess } from '../transport/host.ts';
|
|
2
|
+
import { Transcript } from './transcript.ts';
|
|
3
|
+
import type { HistorySearch } from './types.ts';
|
|
4
|
+
interface Selection {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
revision: number;
|
|
7
|
+
record: Transcript;
|
|
8
|
+
}
|
|
9
|
+
/** History reads cannot navigate, change status, or write another domain's state. */
|
|
10
|
+
export interface HistoryHost extends Pick<HostAccess, 'require' | 'online' | 'signal'> {
|
|
11
|
+
selected(): Selection;
|
|
12
|
+
current(selection: Selection): boolean;
|
|
13
|
+
owns(transcript: Transcript): boolean;
|
|
14
|
+
changed(): void;
|
|
15
|
+
}
|
|
16
|
+
export declare class HistoryReader {
|
|
17
|
+
private readonly host;
|
|
18
|
+
private readonly tasks;
|
|
19
|
+
constructor(host: HistoryHost);
|
|
20
|
+
/** A selection change cancels everything; closing one detached window cancels only its pages. */
|
|
21
|
+
cancel(source?: Transcript): void;
|
|
22
|
+
settle(): Promise<void>;
|
|
23
|
+
private read;
|
|
24
|
+
waitForHistory(signal: AbortSignal): Promise<void>;
|
|
25
|
+
older(signal?: AbortSignal, transcript?: Transcript): Promise<void>;
|
|
26
|
+
private page;
|
|
27
|
+
historyThrough(target: number | 'first', signal: AbortSignal): Promise<void>;
|
|
28
|
+
searchHistory(query: string, signal: AbortSignal): Promise<HistorySearch>;
|
|
29
|
+
/** The returned window belongs to the caller until it is installed as the reading view. */
|
|
30
|
+
historyAt(target: number, signal: AbortSignal): Promise<Transcript>;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/** Selected-session history I/O, with cancellation tied to the record or window being read. */
|
|
2
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
3
|
+
import { object } from "../json.js";
|
|
4
|
+
import { toolLine } from "../text.js";
|
|
5
|
+
import { Transcript } from "./transcript.js";
|
|
6
|
+
const SEARCH_MATCH_LIMIT = 200;
|
|
7
|
+
export class HistoryReader {
|
|
8
|
+
host;
|
|
9
|
+
tasks = new Set();
|
|
10
|
+
constructor(host) {
|
|
11
|
+
this.host = host;
|
|
12
|
+
}
|
|
13
|
+
/** A selection change cancels everything; closing one detached window cancels only its pages. */
|
|
14
|
+
cancel(source) {
|
|
15
|
+
for (const task of this.tasks)
|
|
16
|
+
if (source === undefined || task.source === source)
|
|
17
|
+
task.abort.abort();
|
|
18
|
+
}
|
|
19
|
+
async settle() { await Promise.allSettled([...this.tasks].map(task => task.promise)); }
|
|
20
|
+
read(caller, source, work, discard) {
|
|
21
|
+
const abort = new AbortController();
|
|
22
|
+
const signal = AbortSignal.any([abort.signal, this.host.signal(), ...(caller ? [caller] : [])]);
|
|
23
|
+
// Register before dispatch so even a synchronous observer can cancel this operation.
|
|
24
|
+
const task = { abort, source, promise: Promise.resolve() };
|
|
25
|
+
const promise = Promise.resolve().then(async () => {
|
|
26
|
+
signal.throwIfAborted();
|
|
27
|
+
const selected = this.host.selected();
|
|
28
|
+
const check = () => {
|
|
29
|
+
signal.throwIfAborted();
|
|
30
|
+
if (!this.host.online() || !this.host.current(selected))
|
|
31
|
+
throw new Error('Session changed while reading history');
|
|
32
|
+
if (source !== undefined && !this.host.owns(source))
|
|
33
|
+
throw new Error('History window closed');
|
|
34
|
+
};
|
|
35
|
+
check();
|
|
36
|
+
const value = await work({ ...selected, client: this.host.require(), signal, check });
|
|
37
|
+
try {
|
|
38
|
+
check();
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
discard?.(value);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}).finally(() => { this.tasks.delete(task); });
|
|
46
|
+
task.promise = promise;
|
|
47
|
+
this.tasks.add(task);
|
|
48
|
+
return promise;
|
|
49
|
+
}
|
|
50
|
+
waitForHistory(signal) {
|
|
51
|
+
return this.read(signal, undefined, async (context) => {
|
|
52
|
+
const deadline = Date.now() + context.client.timeoutMs;
|
|
53
|
+
while (!context.record.ready) {
|
|
54
|
+
context.check();
|
|
55
|
+
if (Date.now() >= deadline)
|
|
56
|
+
throw new Error('Session snapshot timed out');
|
|
57
|
+
await delay(20, undefined, { signal: context.signal });
|
|
58
|
+
}
|
|
59
|
+
context.check();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
older(signal, transcript) {
|
|
63
|
+
return this.read(signal, transcript, context => this.page(context, transcript ?? context.record));
|
|
64
|
+
}
|
|
65
|
+
async page(context, transcript) {
|
|
66
|
+
context.check();
|
|
67
|
+
if (!transcript.ready || !transcript.hasMore || transcript.beforeSeq === undefined)
|
|
68
|
+
return;
|
|
69
|
+
const before = transcript.beforeSeq;
|
|
70
|
+
const result = await context.client.call('session/page', { request: {
|
|
71
|
+
address: { kind: 'session', sessionId: context.sessionId }, throughSeq: transcript.cursor,
|
|
72
|
+
beforeSeq: before, maxMessages: 80,
|
|
73
|
+
} }, context.signal);
|
|
74
|
+
context.check();
|
|
75
|
+
if (!this.host.owns(transcript))
|
|
76
|
+
throw new Error('History window closed');
|
|
77
|
+
transcript.addPage(result);
|
|
78
|
+
this.host.changed();
|
|
79
|
+
if (transcript.hasMore && (transcript.beforeSeq === undefined || transcript.beforeSeq >= before)) {
|
|
80
|
+
throw new Error('Host history page did not advance');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
historyThrough(target, signal) {
|
|
84
|
+
return this.read(signal, undefined, async (context) => {
|
|
85
|
+
const transcript = context.record;
|
|
86
|
+
if (!transcript.ready)
|
|
87
|
+
throw new Error('Wait for the session snapshot');
|
|
88
|
+
while (transcript.hasMore && (target === 'first' || transcript.beforeSeq !== undefined && transcript.beforeSeq > target)) {
|
|
89
|
+
await this.page(context, transcript);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
searchHistory(query, signal) {
|
|
94
|
+
return this.read(signal, undefined, async (context) => {
|
|
95
|
+
const source = context.record;
|
|
96
|
+
if (!source.ready)
|
|
97
|
+
throw new Error('Wait for the session snapshot');
|
|
98
|
+
const throughSeq = source.readThrough;
|
|
99
|
+
const needle = query.toLowerCase();
|
|
100
|
+
const result = { items: [], truncated: false };
|
|
101
|
+
const scan = (transcript) => {
|
|
102
|
+
const messages = transcript.messages;
|
|
103
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
104
|
+
context.check();
|
|
105
|
+
const message = messages[index];
|
|
106
|
+
if (message.role === 'Tool')
|
|
107
|
+
continue;
|
|
108
|
+
const text = message.text;
|
|
109
|
+
const match = text.toLowerCase().indexOf(needle);
|
|
110
|
+
if (match < 0)
|
|
111
|
+
continue;
|
|
112
|
+
if (result.items.length === SEARCH_MATCH_LIMIT) {
|
|
113
|
+
result.truncated = true;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
result.items.push({ seq: message.seq, role: message.role,
|
|
117
|
+
preview: Buffer.from(toolLine(text.slice(Math.max(0, match - 40), match + needle.length + 100), 160)).toString('utf8') });
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
};
|
|
121
|
+
if (!scan(source))
|
|
122
|
+
return result;
|
|
123
|
+
let beforeSeq = source.beforeSeq;
|
|
124
|
+
let hasMore = source.hasMore;
|
|
125
|
+
while (hasMore && beforeSeq !== undefined) {
|
|
126
|
+
const page = object(await context.client.call('session/page', { request: {
|
|
127
|
+
address: { kind: 'session', sessionId: context.sessionId }, throughSeq, beforeSeq, maxMessages: 80,
|
|
128
|
+
} }, context.signal));
|
|
129
|
+
context.check();
|
|
130
|
+
const temporary = new Transcript();
|
|
131
|
+
try {
|
|
132
|
+
temporary.accept({ type: 'snapshot', cursor: throughSeq, assistantStream: { revision: 0 }, records: page.records, hasMore: page.hasMore });
|
|
133
|
+
const next = temporary.beforeSeq;
|
|
134
|
+
if (temporary.hasMore && (next === undefined || next >= beforeSeq))
|
|
135
|
+
throw new Error('Host history page did not advance');
|
|
136
|
+
if (!scan(temporary))
|
|
137
|
+
return result;
|
|
138
|
+
beforeSeq = next;
|
|
139
|
+
hasMore = temporary.hasMore;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
temporary.dispose();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** The returned window belongs to the caller until it is installed as the reading view. */
|
|
149
|
+
historyAt(target, signal) {
|
|
150
|
+
return this.read(signal, undefined, async (context) => {
|
|
151
|
+
const throughSeq = context.record.readThrough;
|
|
152
|
+
const page = object(await context.client.call('session/page', { request: {
|
|
153
|
+
address: { kind: 'session', sessionId: context.sessionId }, throughSeq,
|
|
154
|
+
beforeSeq: target + 1, maxMessages: 80,
|
|
155
|
+
} }, context.signal));
|
|
156
|
+
context.check();
|
|
157
|
+
const window = new Transcript();
|
|
158
|
+
try {
|
|
159
|
+
window.accept({ type: 'snapshot', cursor: throughSeq, assistantStream: { revision: 0 }, records: page.records, hasMore: page.hasMore });
|
|
160
|
+
if (!window.messages.some(message => message.seq === target))
|
|
161
|
+
throw new Error('The host did not return the requested message');
|
|
162
|
+
return window;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
window.dispose();
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}, window => window.dispose());
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -14,24 +14,6 @@ export interface HistoryRow {
|
|
|
14
14
|
/** Draw the row on the theme's local-command bar, used by `!` commands. */
|
|
15
15
|
highlight?: boolean;
|
|
16
16
|
}
|
|
17
|
-
/** Wrap text into rows with the per-kind trimming rule.
|
|
18
|
-
* @param text - Text to wrap.
|
|
19
|
-
* @param width - Available terminal columns.
|
|
20
|
-
* @param kind - Part kind, which decides whether wrapped lines are trimmed.
|
|
21
|
-
* @param seq - Durable sequence, for committed parts.
|
|
22
|
-
* @returns The wrapped rows.
|
|
23
|
-
*/
|
|
24
|
-
/** Wrap plain local text into terminal rows, preserving its own whitespace.
|
|
25
|
-
*
|
|
26
|
-
* Command output is aligned by spaces and indented by stack traces, so this never collapses runs of
|
|
27
|
-
* whitespace the way tool summaries do; only the terminal width decides where a row breaks.
|
|
28
|
-
* @param text - Raw text, possibly containing newlines.
|
|
29
|
-
* @param width - Available terminal columns.
|
|
30
|
-
* @param kind - Row kind used for coloring.
|
|
31
|
-
* @param highlight - Whether the rows are a local command line drawn on the command bar.
|
|
32
|
-
* @returns One row per wrapped terminal line; empty text yields one empty row.
|
|
33
|
-
*/
|
|
34
|
-
export declare function plainRows(text: string, width: number, kind: RowKind, highlight?: boolean): HistoryRow[];
|
|
35
17
|
/** Drop all terminal rows and layout metadata for an evicted or inactive transcript.
|
|
36
18
|
* @param transcript - Transcript whose previously returned layout is no longer used.
|
|
37
19
|
*/
|
|
@@ -59,6 +41,12 @@ export declare function layoutStats(transcript: Transcript): {
|
|
|
59
41
|
* @param liveReasoning - Fold mode for completed live blocks; below 60 content columns it also folds active reasoning.
|
|
60
42
|
* @returns Row count, sequence offsets, and a viewport reader; `lines` materializes all rows for exports only.
|
|
61
43
|
*/
|
|
44
|
+
/** Plain view of one laid-out record: the projection engine's only public product.
|
|
45
|
+
*
|
|
46
|
+
* It exposes rows through `viewport` and message positions through `offsets`, so a caller can render
|
|
47
|
+
* a window without holding the record itself.
|
|
48
|
+
*/
|
|
49
|
+
export type SessionRender = ReturnType<typeof historyLayout>;
|
|
62
50
|
export declare function historyLayout(transcript: Transcript, width: number, reasoning?: Reasoning, overrides?: ReadonlySet<number>, liveReasoning?: Reasoning): {
|
|
63
51
|
messages: Message[];
|
|
64
52
|
offsets: Map<number, number>;
|
package/dist/session/history.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Indexed semantic history with bounded terminal-row caching and viewport-only materialization. */
|
|
2
2
|
import wrapAnsi from 'wrap-ansi';
|
|
3
|
-
import { toolLine } from "./transcript.js";
|
|
4
3
|
import { hasMarkdown, markdownRows } from "./markdown.js";
|
|
4
|
+
import { toolLine } from "../text.js";
|
|
5
5
|
/** A session owns one layout cache; dropped sessions release their entire cache. */
|
|
6
6
|
class LayoutIndex {
|
|
7
7
|
width;
|
|
@@ -140,21 +140,6 @@ const liveRows = new WeakMap();
|
|
|
140
140
|
* @param seq - Durable sequence, for committed parts.
|
|
141
141
|
* @returns The wrapped rows.
|
|
142
142
|
*/
|
|
143
|
-
/** Wrap plain local text into terminal rows, preserving its own whitespace.
|
|
144
|
-
*
|
|
145
|
-
* Command output is aligned by spaces and indented by stack traces, so this never collapses runs of
|
|
146
|
-
* whitespace the way tool summaries do; only the terminal width decides where a row breaks.
|
|
147
|
-
* @param text - Raw text, possibly containing newlines.
|
|
148
|
-
* @param width - Available terminal columns.
|
|
149
|
-
* @param kind - Row kind used for coloring.
|
|
150
|
-
* @param highlight - Whether the rows are a local command line drawn on the command bar.
|
|
151
|
-
* @returns One row per wrapped terminal line; empty text yields one empty row.
|
|
152
|
-
*/
|
|
153
|
-
export function plainRows(text, width, kind, highlight = false) {
|
|
154
|
-
const columns = Math.max(1, width);
|
|
155
|
-
const wrapped = wrapAnsi(text === '' ? ' ' : text, columns, { hard: true, trim: false });
|
|
156
|
-
return wrapped.split('\n').map(line => ({ text: line, kind, ...(highlight ? { highlight: true } : {}) }));
|
|
157
|
-
}
|
|
158
143
|
function wrapRows(text, width, kind, seq) {
|
|
159
144
|
return wrapAnsi(text, width, { hard: true, trim: !['tool', 'success', 'error'].includes(kind) })
|
|
160
145
|
.split('\n').map(text => ({ text, kind, seq }));
|
|
@@ -295,14 +280,6 @@ export function layoutStats(transcript) {
|
|
|
295
280
|
return undefined;
|
|
296
281
|
return { ...index.stats(), liveWraps: index.liveWraps.size, liveMarkdown: index.liveMarkdown.size };
|
|
297
282
|
}
|
|
298
|
-
/** Lay out an indexed conversation without concatenating its historical rows on every stream frame.
|
|
299
|
-
* @param transcript - Selected session's semantic content and unfinished assistant output.
|
|
300
|
-
* @param width - Available terminal columns.
|
|
301
|
-
* @param reasoning - Global committed/completed reasoning fold mode.
|
|
302
|
-
* @param overrides - Sequences whose fold mode differs from the global mode; replace the set on changes.
|
|
303
|
-
* @param liveReasoning - Fold mode for completed live blocks; below 60 content columns it also folds active reasoning.
|
|
304
|
-
* @returns Row count, sequence offsets, and a viewport reader; `lines` materializes all rows for exports only.
|
|
305
|
-
*/
|
|
306
283
|
export function historyLayout(transcript, width, reasoning = 'row', overrides = noOverrides, liveReasoning = reasoning) {
|
|
307
284
|
let index = indexes.get(transcript);
|
|
308
285
|
if (!index || index.width !== width || index.reasoning !== reasoning || index.overrides !== overrides) {
|
package/dist/session/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/** Session domain: the selected session, its transcript, layout, telemetry and navigation. */
|
|
2
2
|
export { SessionController } from './controller.ts';
|
|
3
|
-
export {
|
|
3
|
+
export { SessionMutationGate } from './mutation-gate.ts';
|
|
4
|
+
export type { MutationAdmission, MutationLane } from './mutation-gate.ts';
|
|
5
|
+
export { contentText, Transcript } from './transcript.ts';
|
|
6
|
+
export { toolLine } from '../text.ts';
|
|
4
7
|
export type { LivePhase, Message, MessagePart, ThoughtEntry } from './transcript.ts';
|
|
5
8
|
export { historyLayout, layoutStats, releaseHistoryLayout } from './history.ts';
|
|
6
9
|
export { markdownCacheStats } from './markdown.ts';
|
|
@@ -10,9 +13,11 @@ export type { QueuedInput } from './telemetry.ts';
|
|
|
10
13
|
export { DEFAULT_HISTORY_LIMITS, historyLimits } from './memory.ts';
|
|
11
14
|
export type { HistoryLimits } from './memory.ts';
|
|
12
15
|
export { DEFAULT_PROMPT_LIMITS, promptText, PromptIndex, SessionInfo } from './info.ts';
|
|
13
|
-
export type {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
+
export type { InteractionState, OptionState, PromptEntry, PromptLimits, PromptRecord, } from './info.ts';
|
|
17
|
+
export { resolveTarget } from './navigation.ts';
|
|
18
|
+
export { sessionLabel } from '../session-title.ts';
|
|
19
|
+
export { fileReferences } from './references.ts';
|
|
20
|
+
export { activeReference, fileMention } from '../references.ts';
|
|
16
21
|
export type { FileReference } from './references.ts';
|
|
17
22
|
export { saveSessionLog } from './export.ts';
|
|
18
23
|
export type { HistorySearch, RemovalTarget } from './types.ts';
|
package/dist/session/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/** Session domain: the selected session, its transcript, layout, telemetry and navigation. */
|
|
2
2
|
export { SessionController } from "./controller.js";
|
|
3
|
-
export {
|
|
3
|
+
export { SessionMutationGate } from "./mutation-gate.js";
|
|
4
|
+
export { contentText, Transcript } from "./transcript.js";
|
|
5
|
+
export { toolLine } from "../text.js";
|
|
4
6
|
export { historyLayout, layoutStats, releaseHistoryLayout } from "./history.js";
|
|
5
7
|
export { markdownCacheStats } from "./markdown.js";
|
|
6
8
|
export { Telemetry } from "./telemetry.js";
|
|
7
9
|
export { DEFAULT_HISTORY_LIMITS, historyLimits } from "./memory.js";
|
|
8
10
|
export { DEFAULT_PROMPT_LIMITS, promptText, PromptIndex, SessionInfo } from "./info.js";
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
+
export { resolveTarget } from "./navigation.js";
|
|
12
|
+
export { sessionLabel } from "../session-title.js";
|
|
13
|
+
export { fileReferences } from "./references.js";
|
|
14
|
+
export { activeReference, fileMention } from "../references.js";
|
|
11
15
|
export { saveSessionLog } from "./export.js";
|
package/dist/session/info.d.ts
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* from, so an entry evicted by the budgets stays recoverable: the loaded window refills anything the
|
|
5
|
-
* transcript still holds, and `session/page` refetches anything older than the window. Budgets
|
|
6
|
-
* therefore bound memory without deciding reachability — which is what a base-200 buffer got wrong.
|
|
7
|
-
*
|
|
8
|
-
* Locally submitted slash commands never become durable records, so they are retained too, marked as
|
|
9
|
-
* non-durable. A durable echo of a locally recorded prompt upgrades that entry instead of adding a
|
|
10
|
-
* second copy, keeping the refill boundary (the oldest durable sequence) exact.
|
|
11
|
-
*/
|
|
12
|
-
import { type Reasoning } from './history.ts';
|
|
13
|
-
import { Transcript } from './transcript.ts';
|
|
14
|
-
import type { HistorySearch } from './types.ts';
|
|
15
|
-
import type { ObjectValue } from '../transport/wire.ts';
|
|
16
|
-
/** A durable user prompt as the transcript reports it, before retention. */
|
|
17
|
-
export interface PromptRecord {
|
|
18
|
-
seq: number;
|
|
19
|
-
text: string;
|
|
20
|
-
}
|
|
1
|
+
import { Transcript, type PromptRecord } from './transcript.ts';
|
|
2
|
+
export type { PromptRecord } from './transcript.ts';
|
|
3
|
+
import type { AnswerValue } from './types.ts';
|
|
21
4
|
/** One retained prompt: its text, plus the durable sequence when the host also recorded it. */
|
|
22
5
|
export interface PromptEntry extends PromptRecord {
|
|
23
6
|
durable: boolean;
|
|
@@ -31,61 +14,6 @@ export interface PromptLimits {
|
|
|
31
14
|
export declare const DEFAULT_PROMPT_LIMITS: PromptLimits;
|
|
32
15
|
/** Flatten one transcript prompt into the single line the composer recalls. */
|
|
33
16
|
export declare function promptText(value: string): string;
|
|
34
|
-
/** The selected session's composer: its draft, the caret in it, and a draft a dialog parked aside. */
|
|
35
|
-
export interface ComposerState {
|
|
36
|
-
draft: string;
|
|
37
|
-
cursor: number;
|
|
38
|
-
parked: string;
|
|
39
|
-
}
|
|
40
|
-
/** How the selected session's record is being read: which window, where, and what is expanded.
|
|
41
|
-
*
|
|
42
|
-
* Everything here changes how the record renders, which is why it is session state rather than a
|
|
43
|
-
* transient panel flag; the record's content stays in `Transcript`. `window` is a strong reference
|
|
44
|
-
* released by `closeWindow`, and the layout cache keyed by it is a weak one.
|
|
45
|
-
*/
|
|
46
|
-
export interface ViewState {
|
|
47
|
-
/** Detached record shown instead of the live transcript while reading jumped-to history. */
|
|
48
|
-
window?: Transcript;
|
|
49
|
-
scroll: number;
|
|
50
|
-
/** Reading protection: reclamation pauses until the reader returns to the live end. */
|
|
51
|
-
pinned: boolean;
|
|
52
|
-
/** Message sequences whose reasoning is expanded beyond the default fold. */
|
|
53
|
-
folds: ReadonlySet<number>;
|
|
54
|
-
/** Fold mode for the live attempt's completed reasoning. */
|
|
55
|
-
liveReasoning: Reasoning;
|
|
56
|
-
}
|
|
57
|
-
/** Composer-adjacent `@` reference menu: the highlighted row and the draft that dismissed it. */
|
|
58
|
-
export interface ReferenceState {
|
|
59
|
-
index: number;
|
|
60
|
-
dismissed?: string;
|
|
61
|
-
}
|
|
62
|
-
/** Model dialog step: the catalog plus the provider or model being inspected. */
|
|
63
|
-
export interface ModelState {
|
|
64
|
-
catalog: ObjectValue;
|
|
65
|
-
provider?: string;
|
|
66
|
-
model?: ObjectValue;
|
|
67
|
-
}
|
|
68
|
-
/** Panels the reader opened for the selected session.
|
|
69
|
-
*
|
|
70
|
-
* Only visibility and query text: every panel's rows come from the record, so closing one loses
|
|
71
|
-
* nothing and a session switch may clear all of it. The row cursor inside a panel is not here — it
|
|
72
|
-
* is focus, held by `Picker` and reset through its `key`.
|
|
73
|
-
*/
|
|
74
|
-
export interface PanelState {
|
|
75
|
-
thoughts: boolean;
|
|
76
|
-
queue: boolean;
|
|
77
|
-
model?: ModelState;
|
|
78
|
-
history?: {
|
|
79
|
-
query: string;
|
|
80
|
-
contentSearch: boolean;
|
|
81
|
-
matches?: HistorySearch;
|
|
82
|
-
};
|
|
83
|
-
search?: {
|
|
84
|
-
query: string;
|
|
85
|
-
items: ObjectValue[];
|
|
86
|
-
hasMore: boolean;
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
17
|
/** Keyboard state of one pending question's options. */
|
|
90
18
|
export interface OptionState {
|
|
91
19
|
key: string;
|
|
@@ -98,7 +26,7 @@ export interface OptionState {
|
|
|
98
26
|
* `SessionController.interactions`, which is derived per session on every publish.
|
|
99
27
|
*/
|
|
100
28
|
export interface InteractionState {
|
|
101
|
-
answers: Record<string,
|
|
29
|
+
answers: Record<string, AnswerValue['answers']>;
|
|
102
30
|
option?: OptionState;
|
|
103
31
|
approval?: {
|
|
104
32
|
eventId: string;
|
|
@@ -107,7 +35,7 @@ export interface InteractionState {
|
|
|
107
35
|
}
|
|
108
36
|
/** Client-owned state of the selected session, reset whenever another session is opened.
|
|
109
37
|
*
|
|
110
|
-
* It holds the record, the prompt index, the
|
|
38
|
+
* It holds the record, the prompt index, the reading view and the local interaction
|
|
111
39
|
* state because all five belong to one session and none of them is owned by the host beyond what the
|
|
112
40
|
* record mirrors; everything derivable from `Telemetry` or `CostLedger` stays out (see the design's
|
|
113
41
|
* §5.7.6). The record is referenced here and nowhere else, so "the selected session" has one entry.
|
|
@@ -117,11 +45,9 @@ export declare class SessionInfo {
|
|
|
117
45
|
/** The selected session's record. Replaced — never mutated in place — when another session opens. */
|
|
118
46
|
record: Transcript;
|
|
119
47
|
readonly prompts: PromptIndex;
|
|
120
|
-
|
|
121
|
-
|
|
48
|
+
/** Detached history window the reader jumped to; released by `closeWindow` and `reset`. */
|
|
49
|
+
window?: Transcript;
|
|
122
50
|
readonly interaction: InteractionState;
|
|
123
|
-
readonly reference: ReferenceState;
|
|
124
|
-
readonly panels: PanelState;
|
|
125
51
|
constructor(sessionId?: string);
|
|
126
52
|
/** Forget everything a previous session owned, keeping this instance identity-stable for `State`. */
|
|
127
53
|
reset(sessionId?: string): void;
|
|
@@ -143,6 +69,12 @@ export declare class PromptIndex {
|
|
|
143
69
|
private shed;
|
|
144
70
|
private position;
|
|
145
71
|
private draft;
|
|
72
|
+
/** Prompts the client sent on the operator's behalf; their durable echo never enters recall.
|
|
73
|
+
*
|
|
74
|
+
* Kept across `reset()` so re-opening the session in the same process stays clean, and bounded
|
|
75
|
+
* because an agent loop can send a prompt per attempt.
|
|
76
|
+
*/
|
|
77
|
+
private readonly internal;
|
|
146
78
|
constructor(limits?: PromptLimits);
|
|
147
79
|
/** Retained entry count, so a caller can tell an empty index from a parked cursor. */
|
|
148
80
|
get length(): number;
|
|
@@ -167,8 +99,14 @@ export declare class PromptIndex {
|
|
|
167
99
|
* live window holds, so the lazy backward step could not recover them.
|
|
168
100
|
*/
|
|
169
101
|
markComplete(): void;
|
|
170
|
-
/** Forget one session's prompts and cursor. */
|
|
102
|
+
/** Forget one session's prompts and cursor; client-generated suppression is process-wide. */
|
|
171
103
|
reset(): void;
|
|
104
|
+
/** Remember one prompt the client sent itself, so its durable echo never enters recall.
|
|
105
|
+
*
|
|
106
|
+
* An agent loop submits many turns; without this they would crowd out what the operator typed.
|
|
107
|
+
* @param value - Prompt text the client sent on the operator's behalf.
|
|
108
|
+
*/
|
|
109
|
+
suppress(value: string): void;
|
|
172
110
|
/** Fold one transcript scan: its prompts, plus the watermark it covered.
|
|
173
111
|
*
|
|
174
112
|
* The watermark advances even when the scan found no prompt, so a turn of assistant and tool
|
package/dist/session/info.js
CHANGED
|
@@ -15,11 +15,13 @@ import { Transcript } from "./transcript.js";
|
|
|
15
15
|
export const DEFAULT_PROMPT_LIMITS = { maxEntries: 2000, maxBytes: 512 * 1024 };
|
|
16
16
|
/** Longest single prompt worth recalling; a larger one is skipped rather than truncated in place. */
|
|
17
17
|
const MAX_ENTRY_CHARS = 128 * 1024;
|
|
18
|
+
/** Client-generated prompts remembered for recall suppression; a bounded run needs far fewer. */
|
|
19
|
+
const MAX_INTERNAL_PROMPTS = 512;
|
|
18
20
|
/** Flatten one transcript prompt into the single line the composer recalls. */
|
|
19
21
|
export function promptText(value) { return value.replace(/\r?\n/g, ' ').trim(); }
|
|
20
22
|
/** Client-owned state of the selected session, reset whenever another session is opened.
|
|
21
23
|
*
|
|
22
|
-
* It holds the record, the prompt index, the
|
|
24
|
+
* It holds the record, the prompt index, the reading view and the local interaction
|
|
23
25
|
* state because all five belong to one session and none of them is owned by the host beyond what the
|
|
24
26
|
* record mirrors; everything derivable from `Telemetry` or `CostLedger` stays out (see the design's
|
|
25
27
|
* §5.7.6). The record is referenced here and nowhere else, so "the selected session" has one entry.
|
|
@@ -29,11 +31,9 @@ export class SessionInfo {
|
|
|
29
31
|
/** The selected session's record. Replaced — never mutated in place — when another session opens. */
|
|
30
32
|
record = new Transcript();
|
|
31
33
|
prompts = new PromptIndex();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
/** Detached history window the reader jumped to; released by `closeWindow` and `reset`. */
|
|
35
|
+
window;
|
|
34
36
|
interaction = { answers: {} };
|
|
35
|
-
reference = { index: 0 };
|
|
36
|
-
panels = { thoughts: false, queue: false };
|
|
37
37
|
constructor(sessionId = '') {
|
|
38
38
|
this.sessionId = sessionId;
|
|
39
39
|
}
|
|
@@ -45,32 +45,18 @@ export class SessionInfo {
|
|
|
45
45
|
this.record.dispose();
|
|
46
46
|
this.record = new Transcript();
|
|
47
47
|
this.prompts.reset();
|
|
48
|
-
this.composer.draft = '';
|
|
49
|
-
this.composer.cursor = 0;
|
|
50
|
-
this.composer.parked = '';
|
|
51
|
-
this.view.scroll = 0;
|
|
52
|
-
this.view.pinned = false;
|
|
53
|
-
this.view.folds = new Set();
|
|
54
|
-
this.view.liveReasoning = 'row';
|
|
55
48
|
this.interaction.answers = {};
|
|
56
49
|
this.interaction.option = undefined;
|
|
57
50
|
this.interaction.approval = undefined;
|
|
58
|
-
this.reference.index = 0;
|
|
59
|
-
this.reference.dismissed = undefined;
|
|
60
|
-
this.panels.thoughts = false;
|
|
61
|
-
this.panels.queue = false;
|
|
62
|
-
this.panels.model = undefined;
|
|
63
|
-
this.panels.history = undefined;
|
|
64
|
-
this.panels.search = undefined;
|
|
65
51
|
}
|
|
66
52
|
/** Release the detached history window, if the reader has one open. */
|
|
67
53
|
closeWindow() {
|
|
68
|
-
const window = this.
|
|
54
|
+
const window = this.window;
|
|
69
55
|
if (!window)
|
|
70
56
|
return;
|
|
71
57
|
releaseHistoryLayout(window);
|
|
72
58
|
window.dispose();
|
|
73
|
-
this.
|
|
59
|
+
this.window = undefined;
|
|
74
60
|
}
|
|
75
61
|
}
|
|
76
62
|
/** Seq-ordered prompts with a recall cursor, owned by the selected session.
|
|
@@ -88,6 +74,12 @@ export class PromptIndex {
|
|
|
88
74
|
shed = false;
|
|
89
75
|
position;
|
|
90
76
|
draft = '';
|
|
77
|
+
/** Prompts the client sent on the operator's behalf; their durable echo never enters recall.
|
|
78
|
+
*
|
|
79
|
+
* Kept across `reset()` so re-opening the session in the same process stays clean, and bounded
|
|
80
|
+
* because an agent loop can send a prompt per attempt.
|
|
81
|
+
*/
|
|
82
|
+
internal = new Set();
|
|
91
83
|
constructor(limits = DEFAULT_PROMPT_LIMITS) {
|
|
92
84
|
this.limits = limits;
|
|
93
85
|
}
|
|
@@ -122,7 +114,7 @@ export class PromptIndex {
|
|
|
122
114
|
*/
|
|
123
115
|
markComplete() { if (!this.shed)
|
|
124
116
|
this.complete = true; }
|
|
125
|
-
/** Forget one session's prompts and cursor. */
|
|
117
|
+
/** Forget one session's prompts and cursor; client-generated suppression is process-wide. */
|
|
126
118
|
reset() {
|
|
127
119
|
this.entries = [];
|
|
128
120
|
this.bytes = 0;
|
|
@@ -132,6 +124,24 @@ export class PromptIndex {
|
|
|
132
124
|
this.position = undefined;
|
|
133
125
|
this.draft = '';
|
|
134
126
|
}
|
|
127
|
+
/** Remember one prompt the client sent itself, so its durable echo never enters recall.
|
|
128
|
+
*
|
|
129
|
+
* An agent loop submits many turns; without this they would crowd out what the operator typed.
|
|
130
|
+
* @param value - Prompt text the client sent on the operator's behalf.
|
|
131
|
+
*/
|
|
132
|
+
suppress(value) {
|
|
133
|
+
const text = promptText(value);
|
|
134
|
+
if (!text)
|
|
135
|
+
return;
|
|
136
|
+
this.internal.delete(text);
|
|
137
|
+
this.internal.add(text);
|
|
138
|
+
while (this.internal.size > MAX_INTERNAL_PROMPTS) {
|
|
139
|
+
const oldest = this.internal.values().next().value;
|
|
140
|
+
if (oldest === undefined)
|
|
141
|
+
break;
|
|
142
|
+
this.internal.delete(oldest);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
135
145
|
/** Fold one transcript scan: its prompts, plus the watermark it covered.
|
|
136
146
|
*
|
|
137
147
|
* The watermark advances even when the scan found no prompt, so a turn of assistant and tool
|
|
@@ -154,7 +164,10 @@ export class PromptIndex {
|
|
|
154
164
|
* @param value - Submitted command text; consecutive repeats coalesce.
|
|
155
165
|
*/
|
|
156
166
|
record(value) {
|
|
157
|
-
const
|
|
167
|
+
const text = promptText(value);
|
|
168
|
+
if (this.internal.has(text))
|
|
169
|
+
return;
|
|
170
|
+
const entry = { seq: this.newestSeq, text, durable: false };
|
|
158
171
|
if (!this.retainable(entry) || this.entries.at(-1)?.text === entry.text)
|
|
159
172
|
return;
|
|
160
173
|
this.entries.push(entry);
|
|
@@ -166,8 +179,16 @@ export class PromptIndex {
|
|
|
166
179
|
* @returns How many entries were actually retained.
|
|
167
180
|
*/
|
|
168
181
|
prepend(values) {
|
|
182
|
+
const oldest = this.oldest;
|
|
183
|
+
const seen = new Set();
|
|
169
184
|
const older = values.map(value => ({ seq: value.seq, text: promptText(value.text), durable: true }))
|
|
170
|
-
.filter(value =>
|
|
185
|
+
.filter(value => {
|
|
186
|
+
if (oldest !== undefined && value.seq >= oldest || seen.has(value.seq)
|
|
187
|
+
|| this.internal.has(value.text) || !this.retainable(value))
|
|
188
|
+
return false;
|
|
189
|
+
seen.add(value.seq);
|
|
190
|
+
return true;
|
|
191
|
+
});
|
|
171
192
|
if (!older.length)
|
|
172
193
|
return 0;
|
|
173
194
|
this.entries = [...older, ...this.entries];
|
|
@@ -213,7 +234,8 @@ export class PromptIndex {
|
|
|
213
234
|
/** Retain one durable prompt; a durable echo upgrades a local entry instead of duplicating it. */
|
|
214
235
|
push(value) {
|
|
215
236
|
const entry = { seq: value.seq, text: promptText(value.text), durable: true };
|
|
216
|
-
|
|
237
|
+
// A prompt the client itself sent (an agent loop) is durable history, but not composer recall.
|
|
238
|
+
if (this.internal.has(entry.text) || !this.retainable(entry))
|
|
217
239
|
return;
|
|
218
240
|
const last = this.entries.at(-1);
|
|
219
241
|
if (last && last.text === entry.text) {
|
|
@@ -227,10 +249,15 @@ export class PromptIndex {
|
|
|
227
249
|
}
|
|
228
250
|
/** Drop the oldest entries until both budgets hold; a dropped prefix stays reloadable. */
|
|
229
251
|
trim() {
|
|
252
|
+
let removed = 0;
|
|
230
253
|
while (this.entries.length > this.limits.maxEntries || this.bytes > this.limits.maxBytes) {
|
|
231
254
|
this.bytes -= this.entries.shift().text.length * 2;
|
|
255
|
+
removed++;
|
|
232
256
|
this.shed = true;
|
|
257
|
+
this.complete = false;
|
|
233
258
|
}
|
|
259
|
+
if (this.position !== undefined)
|
|
260
|
+
this.position = Math.max(0, this.position - removed);
|
|
234
261
|
}
|
|
235
262
|
}
|
|
236
263
|
/** Bytes of prompt text the process keeps across sessions before evicting the least recently used. */
|