@toddzheng024/dscode-bundle 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.
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/bin/apply_patch +4 -0
- package/bootstrap.mjs +21 -0
- package/cordis.patch.yml +756 -0
- package/package.json +327 -0
- package/plugins/auto-review/audit.mjs +20 -0
- package/plugins/auto-review/index.mjs +181 -0
- package/plugins/auto-review/policy.mjs +59 -0
- package/plugins/dscode/index.mjs +35 -0
- package/plugins/session-metrics/index.mjs +44 -0
- package/plugins/session-metrics/pricing.mjs +18 -0
- package/plugins/session-metrics/store.mjs +24 -0
- package/plugins/session-metrics/view.mjs +58 -0
- package/plugins/tui-tools/hooks.mjs +23 -0
- package/plugins/tui-tools/index.mjs +164 -0
- package/plugins/ultra/policy.mjs +11 -0
- package/presets/dscode/agent.cordis.yml +288 -0
- package/presets/dscode/preset.yml +3 -0
- package/vendor/bash/LICENSE +21 -0
- package/vendor/bash/index.js +449 -0
- package/vendor/bash/types/background.d.ts +19 -0
- package/vendor/bash/types/index.d.ts +22 -0
- package/vendor/bash/types/render.d.ts +38 -0
- package/vendor/deepseek/LICENSE +21 -0
- package/vendor/deepseek/index.js +2102 -0
- package/vendor/deepseek/types/adapter.d.ts +163 -0
- package/vendor/deepseek/types/file-id.d.ts +19 -0
- package/vendor/deepseek/types/file-store.d.ts +83 -0
- package/vendor/deepseek/types/files-api.d.ts +103 -0
- package/vendor/deepseek/types/image-tokens.d.ts +19 -0
- package/vendor/deepseek/types/index.d.ts +106 -0
- package/vendor/deepseek/types/request-pricing.d.ts +48 -0
- package/vendor/deepseek/types/serialize.d.ts +85 -0
- package/vendor/deepseek/types/sse.d.ts +24 -0
- package/vendor/deepseek/types/translate.d.ts +37 -0
- package/vendor/deepseek/types/types.d.ts +178 -0
- package/vendor/deepseek/types/upload-index.d.ts +68 -0
- package/vendor/persistent/LICENSE +21 -0
- package/vendor/persistent/index.js +386 -0
- package/vendor/persistent/types/index.d.ts +24 -0
- package/vendor/tui/LICENSE +21 -0
- package/vendor/tui/devtools-CdTl3MNy.mjs +3643 -0
- package/vendor/tui/index.mjs +39105 -0
- package/vendor/tui/invariant.mjs +21 -0
- package/vendor/tui/rolldown-runtime-CMFfr-1z.mjs +26 -0
- package/vendor/tui/startup.mjs +109 -0
- package/vendor/tui/theme-DCT8Y2xf.mjs +628 -0
- package/vendor/tui/types/app.d.ts +304 -0
- package/vendor/tui/types/approval.d.ts +59 -0
- package/vendor/tui/types/attachments.d.ts +52 -0
- package/vendor/tui/types/authorization-panel.d.ts +22 -0
- package/vendor/tui/types/authorization.d.ts +36 -0
- package/vendor/tui/types/commands.d.ts +52 -0
- package/vendor/tui/types/editor-keys.d.ts +105 -0
- package/vendor/tui/types/editor.d.ts +6 -0
- package/vendor/tui/types/fork.d.ts +8 -0
- package/vendor/tui/types/git-workflow.d.ts +32 -0
- package/vendor/tui/types/history.d.ts +97 -0
- package/vendor/tui/types/index.d.ts +124 -0
- package/vendor/tui/types/input-split.d.ts +54 -0
- package/vendor/tui/types/internals.d.ts +26 -0
- package/vendor/tui/types/invariant.d.ts +15 -0
- package/vendor/tui/types/kernel-panels.d.ts +167 -0
- package/vendor/tui/types/keyboard.d.ts +80 -0
- package/vendor/tui/types/mentions.d.ts +81 -0
- package/vendor/tui/types/model-capabilities.d.ts +82 -0
- package/vendor/tui/types/models.d.ts +119 -0
- package/vendor/tui/types/permissions.d.ts +27 -0
- package/vendor/tui/types/plugin-inventory.d.ts +11 -0
- package/vendor/tui/types/presets.d.ts +22 -0
- package/vendor/tui/types/provider-settings.d.ts +239 -0
- package/vendor/tui/types/questions.d.ts +54 -0
- package/vendor/tui/types/render/animations.d.ts +265 -0
- package/vendor/tui/types/render/editor.d.ts +162 -0
- package/vendor/tui/types/render/export.d.ts +9 -0
- package/vendor/tui/types/render/fuzzy.d.ts +21 -0
- package/vendor/tui/types/render/inspector.d.ts +36 -0
- package/vendor/tui/types/render/lines.d.ts +66 -0
- package/vendor/tui/types/render/markdown.d.ts +29 -0
- package/vendor/tui/types/render/projection.d.ts +461 -0
- package/vendor/tui/types/render/status.d.ts +196 -0
- package/vendor/tui/types/render/text.d.ts +58 -0
- package/vendor/tui/types/render/tool-detail.d.ts +94 -0
- package/vendor/tui/types/render/tool-preview.d.ts +28 -0
- package/vendor/tui/types/render/width.d.ts +29 -0
- package/vendor/tui/types/session-directory.d.ts +173 -0
- package/vendor/tui/types/session-switch.d.ts +17 -0
- package/vendor/tui/types/settings-file.d.ts +41 -0
- package/vendor/tui/types/skills.d.ts +47 -0
- package/vendor/tui/types/startup.d.ts +65 -0
- package/vendor/tui/types/store.d.ts +58 -0
- package/vendor/tui/types/subagents.d.ts +70 -0
- package/vendor/tui/types/theme-panel.d.ts +24 -0
- package/vendor/tui/types/theme.d.ts +215 -0
- package/vendor/tui/types/version.d.ts +18 -0
- package/vendor/tui/types/whale-glyph.d.ts +6 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure session-event-to-view projection for the TUI transcript: one reducer
|
|
3
|
+
* over {@link SessionEvent}s producing the ordered entries the renderer draws.
|
|
4
|
+
* Rendering never reads the session directly — this module owns the view
|
|
5
|
+
* model, so tests drive it with plain event arrays.
|
|
6
|
+
*
|
|
7
|
+
* @module @deepseek-ai/dsh-tui/render/projection
|
|
8
|
+
*/
|
|
9
|
+
import { type ImageBlock, type MessageId, type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
10
|
+
import type { FileAttachmentRef } from '@deepseek-ai/dsh-attachment';
|
|
11
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
12
|
+
import type { TodoItem } from '@deepseek-ai/dsh-tool-todo';
|
|
13
|
+
import { type ToolDetail } from './tool-detail.ts';
|
|
14
|
+
/** One user prompt line. */
|
|
15
|
+
export interface UserEntry {
|
|
16
|
+
kind: 'user';
|
|
17
|
+
/** Joined text blocks of the user message. */
|
|
18
|
+
text: string;
|
|
19
|
+
/** True for collapsed injected context (plugin/continuation notices), which
|
|
20
|
+
* the renderer marks with a dim ↳ instead of the user ❯ prompt. */
|
|
21
|
+
notice: boolean;
|
|
22
|
+
/** Durable image references carried by this prompt. */
|
|
23
|
+
images?: readonly ImageBlock['attachment'][];
|
|
24
|
+
/** Durable file references carried by this prompt (0.1.5 file blocks). */
|
|
25
|
+
files?: readonly FileAttachmentRef[];
|
|
26
|
+
}
|
|
27
|
+
/** One user message waiting in the agent inbox (the web's queued-message row). */
|
|
28
|
+
export interface PendingEntry {
|
|
29
|
+
kind: 'pending';
|
|
30
|
+
/** Stable message identity shared with the durable `user/message` that retires it. */
|
|
31
|
+
messageId: MessageId;
|
|
32
|
+
/** Which inbox list holds the message: steering is consumed at the next step boundary. */
|
|
33
|
+
target: 'next-turn' | 'next-step';
|
|
34
|
+
/** Full message text — Codex PendingSteer renders queued prompts exactly like user rows. */
|
|
35
|
+
text: string;
|
|
36
|
+
/** Durable image references queued with this prompt. */
|
|
37
|
+
images?: readonly ImageBlock['attachment'][];
|
|
38
|
+
/** Durable file references queued with this prompt (0.1.5 file blocks). */
|
|
39
|
+
files?: readonly FileAttachmentRef[];
|
|
40
|
+
}
|
|
41
|
+
/** One authoritative assembled assistant reply. */
|
|
42
|
+
export interface AssistantEntry {
|
|
43
|
+
kind: 'assistant';
|
|
44
|
+
/** Joined text blocks of the assistant message. */
|
|
45
|
+
text: string;
|
|
46
|
+
/** Joined reasoning blocks from the same assembled message. */
|
|
47
|
+
reasoning: string;
|
|
48
|
+
/** True when a cancelled stream's delivered prefix was finalized as this
|
|
49
|
+
* entry (rc.8 `assistant/message.interrupted`) — rendered with a marker. */
|
|
50
|
+
interrupted?: true;
|
|
51
|
+
}
|
|
52
|
+
/** One model-requested tool invocation and its settled state. */
|
|
53
|
+
export interface ToolEntry {
|
|
54
|
+
kind: 'tool';
|
|
55
|
+
/** Correlation id shared with the matching `tool/result`. */
|
|
56
|
+
callId: string;
|
|
57
|
+
/**
|
|
58
|
+
* Global tool-call ordinal across the whole transcript (1, 2, 3…, never
|
|
59
|
+
* reset between turns). The tool-card badge and every error line that
|
|
60
|
+
* references the failed call share this number, so "call N" in an error
|
|
61
|
+
* always names the exact card the badge shows.
|
|
62
|
+
*/
|
|
63
|
+
ordinal: number;
|
|
64
|
+
/** Tool name as the model addressed it. */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Raw arguments JSON string exactly as the model produced it. */
|
|
67
|
+
arguments: string;
|
|
68
|
+
/** Bounded human-meaningful arguments preview for the tool card. */
|
|
69
|
+
preview: string;
|
|
70
|
+
/** Bounded delegation prompt (subagent cards' second row), '' when none. */
|
|
71
|
+
prompt: string;
|
|
72
|
+
/** Execution state; `running` until the paired result lands. */
|
|
73
|
+
state: 'running' | 'done' | 'error';
|
|
74
|
+
/** Bounded first text block of the result, empty until it lands. */
|
|
75
|
+
summary: string;
|
|
76
|
+
/**
|
|
77
|
+
* Bounded expansion payload for the verbose transcript (Ctrl+O), derived
|
|
78
|
+
* from the tool's persisted presentation metadata; undefined until the
|
|
79
|
+
* result lands and only when something renderable exists.
|
|
80
|
+
*/
|
|
81
|
+
detail: ToolDetail | undefined;
|
|
82
|
+
}
|
|
83
|
+
/** One slash-command execution dispatched through `ctx.commands`. */
|
|
84
|
+
export interface CommandEntry {
|
|
85
|
+
kind: 'command';
|
|
86
|
+
/** Pairing id shared with the matching `command/done`. */
|
|
87
|
+
commandId: string;
|
|
88
|
+
/** Lowercase command name without the leading slash. */
|
|
89
|
+
name: string;
|
|
90
|
+
/** Verbatim text following the command name. */
|
|
91
|
+
args: string;
|
|
92
|
+
/** Execution state; `running` until the paired lifecycle event lands. */
|
|
93
|
+
state: 'running' | 'done' | 'error';
|
|
94
|
+
/** Handler outcome text, empty until it lands. */
|
|
95
|
+
summary: string;
|
|
96
|
+
}
|
|
97
|
+
/** One turn-level failure surfaced from `turn/end`. */
|
|
98
|
+
export interface ErrorEntry {
|
|
99
|
+
kind: 'error';
|
|
100
|
+
/** `code: message` of the failure. */
|
|
101
|
+
text: string;
|
|
102
|
+
}
|
|
103
|
+
/** One non-error turn outcome surfaced from `turn/end`. */
|
|
104
|
+
export interface TurnMarkerEntry {
|
|
105
|
+
kind: 'turn-marker';
|
|
106
|
+
/** Human-readable outcome line, dim-rendered. */
|
|
107
|
+
text: string;
|
|
108
|
+
}
|
|
109
|
+
/** One completed compaction lifecycle surfaced from `compaction/end`. */
|
|
110
|
+
export interface CompactionEntry {
|
|
111
|
+
kind: 'compaction';
|
|
112
|
+
/** True when the compaction completed, false when it failed. */
|
|
113
|
+
ok: boolean;
|
|
114
|
+
/** Heuristic tokens shadowed by the compaction (summary or prune price). */
|
|
115
|
+
tokens: number;
|
|
116
|
+
/** Failure text when `ok` is false, empty otherwise. */
|
|
117
|
+
error: string;
|
|
118
|
+
}
|
|
119
|
+
/** One provider-routed model-request retry (the `llm/retry` pair). */
|
|
120
|
+
export interface RetryEntry {
|
|
121
|
+
kind: 'retry';
|
|
122
|
+
/** Correlation id shared with the matching `llm/retry-started`. */
|
|
123
|
+
retryId: string;
|
|
124
|
+
/** Retry policy mode from the event: `always` has no attempt cap. */
|
|
125
|
+
mode: 'normal' | 'always';
|
|
126
|
+
/** Attempt ordinal and its cap. */
|
|
127
|
+
attempt: number;
|
|
128
|
+
max: number;
|
|
129
|
+
/** Failure code that triggered the retry. */
|
|
130
|
+
code: string;
|
|
131
|
+
/** Backoff wait before the next attempt, in ms. */
|
|
132
|
+
delayMs: number;
|
|
133
|
+
/**
|
|
134
|
+
* `running` while the backoff waits, `done` once the attempt started — or
|
|
135
|
+
* when the turn ended first (the turn-end sweep finalizes orphans so they
|
|
136
|
+
* never pin the settled boundary).
|
|
137
|
+
*/
|
|
138
|
+
state: 'running' | 'done';
|
|
139
|
+
}
|
|
140
|
+
/** Turn-tail deliverables: files mutated by the turn's diff-bearing tools. */
|
|
141
|
+
export interface FilesEntry {
|
|
142
|
+
kind: 'files';
|
|
143
|
+
/** Unique mutated paths in call order, bounded. */
|
|
144
|
+
paths: readonly string[];
|
|
145
|
+
}
|
|
146
|
+
/** Ordered transcript items the renderer draws. */
|
|
147
|
+
export type TranscriptEntry = UserEntry | PendingEntry | AssistantEntry | ToolEntry | CommandEntry | ErrorEntry | TurnMarkerEntry | CompactionEntry | RetryEntry | FilesEntry;
|
|
148
|
+
/** The live goal the status line badges, folded from `goal/change`. */
|
|
149
|
+
export interface GoalFold {
|
|
150
|
+
/** Human-requested completion objective. */
|
|
151
|
+
objective: string;
|
|
152
|
+
/** Durable lifecycle phase. */
|
|
153
|
+
phase: 'active' | 'paused' | 'blocked' | 'complete';
|
|
154
|
+
/** Highest admitted continuation round and its cap. */
|
|
155
|
+
rounds: number;
|
|
156
|
+
max: number;
|
|
157
|
+
/** Blocked explanation, empty outside the blocked phase. */
|
|
158
|
+
blocked: string;
|
|
159
|
+
}
|
|
160
|
+
/** Cumulative token accounting folded from `assistant/message` usage reports. */
|
|
161
|
+
export interface UsageTotals {
|
|
162
|
+
/** Prompt-side billed tokens: `inputTokens` plus both cache buckets. */
|
|
163
|
+
inputTokens: number;
|
|
164
|
+
/** Completion-side tokens over the whole log. */
|
|
165
|
+
outputTokens: number;
|
|
166
|
+
/** Cache-read tokens over the whole log (0 when the adapter reports none). */
|
|
167
|
+
cacheReadTokens: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Estimated used tokens per context content type, folded from transcript
|
|
171
|
+
* events via {@link estimateTokens}. The segmented context bar's composition
|
|
172
|
+
* source: proportions across types are meaningful, absolute values are not
|
|
173
|
+
* (they never touch billing or the reported `lastPromptTokens`).
|
|
174
|
+
*/
|
|
175
|
+
export interface ContextSegments {
|
|
176
|
+
/** Rendered system-prompt text (latest `request/header`) plus injected-context notices. */
|
|
177
|
+
system: number;
|
|
178
|
+
/** Direct human prompts (durable `user/message` rows). */
|
|
179
|
+
prompt: number;
|
|
180
|
+
/** Assistant text blocks (visible replies). */
|
|
181
|
+
assistant: number;
|
|
182
|
+
/** Assistant reasoning blocks (hidden thinking). */
|
|
183
|
+
thinking: number;
|
|
184
|
+
/** Tool call arguments plus result text. */
|
|
185
|
+
tools: number;
|
|
186
|
+
}
|
|
187
|
+
/** Window-scoped figures the status line shows; timing uses event timestamps. */
|
|
188
|
+
export interface TranscriptStats {
|
|
189
|
+
/** Durable turns opened (`turn/start` events). */
|
|
190
|
+
turns: number;
|
|
191
|
+
/** Model requests made (`step/start` events). */
|
|
192
|
+
steps: number;
|
|
193
|
+
/** Summed model wall time: `step/start` → `assistant/message`, in ms. */
|
|
194
|
+
llmMs: number;
|
|
195
|
+
/** Summed tool wall time: `tool/call` → `tool/result`, in ms. */
|
|
196
|
+
toolMs: number;
|
|
197
|
+
/** Cumulative token accounting; input stays 0 until a report lands. */
|
|
198
|
+
usage: UsageTotals;
|
|
199
|
+
/** Prompt-side size of the most recent reported request (context pressure). */
|
|
200
|
+
lastPromptTokens: number;
|
|
201
|
+
/** Newest advertised route capacity, 0 when no adapter ever advertised one. */
|
|
202
|
+
contextWindow: number;
|
|
203
|
+
/** Estimated used tokens per content type (the segmented bar's composition). */
|
|
204
|
+
contextSegments: ContextSegments;
|
|
205
|
+
/** Summed first-token waits: `step/start` → first non-empty chunk, in ms. */
|
|
206
|
+
ttftMs: number;
|
|
207
|
+
/** Steps that produced a first chunk (the TTFT average's denominator). */
|
|
208
|
+
ttftSteps: number;
|
|
209
|
+
/** Summed decode spans: first chunk → `assistant/message`, in ms. */
|
|
210
|
+
decodeMs: number;
|
|
211
|
+
/** Completion tokens over timed decode spans (the tok/s numerator). */
|
|
212
|
+
decodeTokens: number;
|
|
213
|
+
/**
|
|
214
|
+
* Adapter-owned reasoning effort of the latest `request/header` config —
|
|
215
|
+
* the EFFECTIVE effort the session actually uses (a materialized model
|
|
216
|
+
* default is included, exactly as the adapter resolved it). Empty when the
|
|
217
|
+
* header carried none (provider-default behavior). The status line appends
|
|
218
|
+
* it to the model segment as `provider/model@effort`.
|
|
219
|
+
*/
|
|
220
|
+
reasoningEffort: string;
|
|
221
|
+
}
|
|
222
|
+
/** The complete TUI transcript view for one session. */
|
|
223
|
+
export interface TranscriptView {
|
|
224
|
+
/** Settled entries in log order. */
|
|
225
|
+
entries: readonly TranscriptEntry[];
|
|
226
|
+
/** Bounded text tail accumulated from live stream frames since the last settlement. */
|
|
227
|
+
streaming: string;
|
|
228
|
+
/** Bounded thinking tail accumulated from reasoning deltas since the last flush. */
|
|
229
|
+
streamingReasoning: string;
|
|
230
|
+
/** Latest whole-list todo snapshot from `todo/write`, empty when none. */
|
|
231
|
+
todos: readonly TodoItem[];
|
|
232
|
+
/**
|
|
233
|
+
* Global tool-call ordinal counter: the number the NEXT `tool/call` lands
|
|
234
|
+
* with (1-based). Never reset, so the counter and the badges/error lines
|
|
235
|
+
* stay consistent across turns and resumed sessions.
|
|
236
|
+
*/
|
|
237
|
+
toolCallOrdinal: number;
|
|
238
|
+
/** True while a durable turn is open (`turn/start` … `turn/end`). */
|
|
239
|
+
busy: boolean;
|
|
240
|
+
/** `turn/start` time of the open turn (0 while idle) — the web TurnStatus clock anchor. */
|
|
241
|
+
busySince: number;
|
|
242
|
+
/** Figures the status line renders. */
|
|
243
|
+
stats: TranscriptStats;
|
|
244
|
+
/**
|
|
245
|
+
* The `provider/model` pair of the last `request/header` snapshot — the
|
|
246
|
+
* session's own model record, which a resumed TUI prefers over the
|
|
247
|
+
* deployment default (mirrors the web host's resume selection order).
|
|
248
|
+
* Empty before the session's first request.
|
|
249
|
+
*/
|
|
250
|
+
model: string;
|
|
251
|
+
/** Plan mode state folded from the last `plan/mode` event. */
|
|
252
|
+
plan: boolean;
|
|
253
|
+
/** Active permission preset folded from the last `permission/preset` event, empty before one. */
|
|
254
|
+
permission: string;
|
|
255
|
+
/** Latest session title folded from the last `session/title` event, empty before one. */
|
|
256
|
+
title: string;
|
|
257
|
+
/**
|
|
258
|
+
* Effective system prompt assembled from `system/message` surface nodes
|
|
259
|
+
* (v3): the head node's text joined with every later non-empty node, blank
|
|
260
|
+
* lines between. Empty before the first system node or when every node is
|
|
261
|
+
* empty ("no system prompt").
|
|
262
|
+
*/
|
|
263
|
+
systemPrompt: string;
|
|
264
|
+
/** Sandbox-mode override folded from the last `sandbox/mode` event, empty when never switched. */
|
|
265
|
+
sandbox: string;
|
|
266
|
+
/** Current long-running goal folded from the last `goal/change`, undefined when cleared. */
|
|
267
|
+
goal: GoalFold | undefined;
|
|
268
|
+
/**
|
|
269
|
+
* Ordered live message ids per inbox target, mirrored from
|
|
270
|
+
* `agent/inbox/spliced` exactly like the upstream Inbox projection — the
|
|
271
|
+
* coordinates later removals resolve against.
|
|
272
|
+
*/
|
|
273
|
+
pending: {
|
|
274
|
+
'next-turn': readonly string[];
|
|
275
|
+
'next-step': readonly string[];
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Fold-internal timing anchors, never rendered: open step and tool-call
|
|
279
|
+
* start timestamps the next `assistant/message` / `tool/result` resolves
|
|
280
|
+
* against. Keyed `turn:step` and by call id. `turnSteps`/`turnTools`
|
|
281
|
+
* track which step/tool anchors still belong to the open turn so
|
|
282
|
+
* `turn/end` (and a superseding `step/start`) can sweep anchors an
|
|
283
|
+
* interruption left behind; `turnFiles` keys mutated paths by turn.
|
|
284
|
+
*/
|
|
285
|
+
readonly anchors: {
|
|
286
|
+
stepStart: Map<string, number>;
|
|
287
|
+
toolStart: Map<string, number>;
|
|
288
|
+
firstChunkAt: Map<string, number>;
|
|
289
|
+
compactionTokens: Map<string, number>;
|
|
290
|
+
lastPruneTokens: number;
|
|
291
|
+
turnFiles: Map<number, Set<string>>;
|
|
292
|
+
turnSteps: Map<number, string>;
|
|
293
|
+
turnTools: Map<number, Set<string>>;
|
|
294
|
+
/** Live `system/message` surface nodes by event seq (empty string = an empty node). */
|
|
295
|
+
systemNodes: Map<number, string>;
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/** Human-readable bounded image labels for transcript, inspector, and export surfaces. */
|
|
299
|
+
export declare function imageLabels(images: readonly ImageBlock['attachment'][] | undefined): string;
|
|
300
|
+
/** Human-readable bounded file labels for the same surfaces (0.1.5 file blocks). */
|
|
301
|
+
export declare function fileLabels(files: readonly FileAttachmentRef[] | undefined): string;
|
|
302
|
+
/** Prompt text with its durable image and file labels, without exposing local paths or bytes. */
|
|
303
|
+
export declare function promptDisplayText(entry: Pick<UserEntry | PendingEntry, 'text' | 'images' | 'files'>): string;
|
|
304
|
+
/** A fresh, empty transcript view. */
|
|
305
|
+
export declare function createTranscriptView(): TranscriptView;
|
|
306
|
+
/**
|
|
307
|
+
* Fold one session event into an updated view (copy-on-write).
|
|
308
|
+
* @param view - the view before the event.
|
|
309
|
+
* @param event - one durable session event from `session/event` or the log.
|
|
310
|
+
* @returns the view after the event; the input view is never mutated.
|
|
311
|
+
*/
|
|
312
|
+
export declare function projectEvent(view: TranscriptView, event: SessionEvent): TranscriptView;
|
|
313
|
+
/**
|
|
314
|
+
* Mutable replay accumulator: folds a persisted log into the identical view
|
|
315
|
+
* `projectEvent` would produce, but in near-linear time. Where `projectEvent`
|
|
316
|
+
* is copy-on-write — every append/scan rebuilds the whole `entries` array, so
|
|
317
|
+
* folding a full log costs O(N²) — the accumulator appends by push, resolves
|
|
318
|
+
* id-keyed updates (tool/result, command/done, retry-started) through index
|
|
319
|
+
* maps, and tombstones retired pending rows, so the whole log folds in O(N)
|
|
320
|
+
* plus one compaction pass when tombstones exist.
|
|
321
|
+
*
|
|
322
|
+
* Index maps never delete: every appended row registers its index, so an id
|
|
323
|
+
* lookup miss provably means no matching row exists and the update is an O(1)
|
|
324
|
+
* no-op (a malicious/orphan-heavy log cannot force per-orphan full-array
|
|
325
|
+
* scans). Each id maps to ALL of its indices, so a duplicate id updates every
|
|
326
|
+
* matching row exactly like the copy-on-write reducer.
|
|
327
|
+
*
|
|
328
|
+
* @internal Exported only so tests can (a) prove replay ≡ sequential
|
|
329
|
+
* `projectEvent` folds and (b) assert the linear complexity deterministically
|
|
330
|
+
* via {@link ReplayAccumulator.ops}, which counts entry-level container work
|
|
331
|
+
* instead of relying on wall-clock thresholds. No public consumer.
|
|
332
|
+
*/
|
|
333
|
+
export interface ReplayAccumulator {
|
|
334
|
+
/** Working entry list; `undefined` marks a retired pending row (tombstone). */
|
|
335
|
+
entries: (TranscriptEntry | undefined)[];
|
|
336
|
+
/** callId → every index into `entries` holding a `tool` row with that id. */
|
|
337
|
+
toolIndex: Map<string, number[]>;
|
|
338
|
+
/** commandId → every index into `entries` holding a `command` row with that id. */
|
|
339
|
+
commandIndex: Map<string, number[]>;
|
|
340
|
+
/** retryId → every index into `entries` holding a `retry` row with that id. */
|
|
341
|
+
retryIndex: Map<string, number[]>;
|
|
342
|
+
/** messageId → every index into `entries` holding a `pending` row with that id. */
|
|
343
|
+
pendingIndex: Map<string, number[]>;
|
|
344
|
+
/** Tombstone count; zero means `entries` is already the final array. */
|
|
345
|
+
removedCount: number;
|
|
346
|
+
/** Mutable inbox id lists, mirroring `view.pending` order per target. */
|
|
347
|
+
pendingTurn: string[];
|
|
348
|
+
pendingStep: string[];
|
|
349
|
+
streaming: string;
|
|
350
|
+
streamingReasoning: string;
|
|
351
|
+
todos: readonly TodoItem[];
|
|
352
|
+
/** Global tool-call ordinal counter (see `TranscriptView.toolCallOrdinal`). */
|
|
353
|
+
toolCallOrdinal: number;
|
|
354
|
+
busy: boolean;
|
|
355
|
+
busySince: number;
|
|
356
|
+
model: string;
|
|
357
|
+
plan: boolean;
|
|
358
|
+
permission: string;
|
|
359
|
+
title: string;
|
|
360
|
+
systemPrompt: string;
|
|
361
|
+
sandbox: string;
|
|
362
|
+
goal: GoalFold | undefined;
|
|
363
|
+
stats: TranscriptStats;
|
|
364
|
+
stepStart: Map<string, number>;
|
|
365
|
+
toolStart: Map<string, number>;
|
|
366
|
+
firstChunkAt: Map<string, number>;
|
|
367
|
+
compactionTokens: Map<string, number>;
|
|
368
|
+
lastPruneTokens: number;
|
|
369
|
+
turnFiles: Map<number, Set<string>>;
|
|
370
|
+
turnSteps: Map<number, string>;
|
|
371
|
+
turnTools: Map<number, Set<string>>;
|
|
372
|
+
/** Live `system/message` surface nodes by event seq (empty string = an empty node). */
|
|
373
|
+
systemNodes: Map<number, string>;
|
|
374
|
+
/** Entry-level container operations performed so far (test instrumentation). */
|
|
375
|
+
ops: number;
|
|
376
|
+
}
|
|
377
|
+
/** @internal A fresh replay accumulator whose state mirrors `createTranscriptView()`. */
|
|
378
|
+
export declare function createReplayAccumulator(): ReplayAccumulator;
|
|
379
|
+
/**
|
|
380
|
+
* Fold one session event into a replay accumulator. This mirrors
|
|
381
|
+
* {@link projectEvent} case for case — same stats arithmetic, same anchor
|
|
382
|
+
* set/delete behavior, same entry shapes — so the finished view is identical
|
|
383
|
+
* to a sequential fold; only the `entries` container operations are mutable.
|
|
384
|
+
*
|
|
385
|
+
* @internal Test-instrumentation path; `projectEvents` is the public entry.
|
|
386
|
+
* @returns whether the event changed the accumulated state — the live store
|
|
387
|
+
* stays silent and keeps its snapshot identity for ignored events, exactly
|
|
388
|
+
* like the copy-on-write reducer returning its input view unchanged.
|
|
389
|
+
*/
|
|
390
|
+
export declare function replayProjectEvent(acc: ReplayAccumulator, event: SessionEvent): boolean;
|
|
391
|
+
/**
|
|
392
|
+
* Materialize the accumulated fold as a `TranscriptView`, compacting any
|
|
393
|
+
* retired tombstones. The anchors maps are handed through as-is (their
|
|
394
|
+
* content is identical to a sequential fold's).
|
|
395
|
+
*
|
|
396
|
+
* @internal Test-instrumentation path; `projectEvents` is the public entry.
|
|
397
|
+
*/
|
|
398
|
+
export declare function finishReplay(acc: ReplayAccumulator): TranscriptView;
|
|
399
|
+
/**
|
|
400
|
+
* Materialize the accumulated fold as a fresh immutable snapshot for the
|
|
401
|
+
* live store. Unlike {@link finishReplay} — the one-shot replay entry, which
|
|
402
|
+
* hands the accumulator's own arrays through because the accumulator is
|
|
403
|
+
* discarded — every array a renderer can hold is copied here, so later
|
|
404
|
+
* folds never mutate a snapshot already handed out. Same fields, same
|
|
405
|
+
* tombstone compaction.
|
|
406
|
+
*
|
|
407
|
+
* @internal Live-store path; `projectEvents` is the public entry.
|
|
408
|
+
*/
|
|
409
|
+
export declare function snapshotReplayView(acc: ReplayAccumulator): TranscriptView;
|
|
410
|
+
/**
|
|
411
|
+
* Fold a replayed event history into one view.
|
|
412
|
+
*
|
|
413
|
+
* Folding is near-linear in the log size: the mutable replay accumulator
|
|
414
|
+
* appends in place and resolves id-keyed updates through index maps, so a
|
|
415
|
+
* long persisted session replays without the O(N²) copy-on-write rebuilds a
|
|
416
|
+
* naive sequential fold would incur. The result is identical to folding
|
|
417
|
+
* {@link projectEvent} per event in order.
|
|
418
|
+
* @param events - events in `seq` order.
|
|
419
|
+
* @returns the folded view.
|
|
420
|
+
*/
|
|
421
|
+
export declare function projectEvents(events: readonly SessionEvent[]): TranscriptView;
|
|
422
|
+
/**
|
|
423
|
+
* Fold one process-local assistant-stream chunk frame (session-log v2+ keeps
|
|
424
|
+
* durable logs settlement-only; live typing rides the `agent/assistant-stream`
|
|
425
|
+
* agent event). Same first-token anchoring the durable `assistant/chunk` event
|
|
426
|
+
* used to carry: the first non-empty delta anchors the TTFT and empty
|
|
427
|
+
* keep-alive deltas do not count. The caller maps the frame's attempt to the
|
|
428
|
+
* `turn:step` key (the start frame owns turn/step; chunk frames do not).
|
|
429
|
+
* @param acc - the live replay accumulator.
|
|
430
|
+
* @param key - the `turn:step` key the attempt's start frame declared.
|
|
431
|
+
* @param time - the frame's safe-integer timestamp.
|
|
432
|
+
* @param chunk - the model chunk the frame carries.
|
|
433
|
+
* @returns whether the accumulator changed (the store stays silent otherwise).
|
|
434
|
+
*/
|
|
435
|
+
export declare function applyAssistantStreamChunk(acc: ReplayAccumulator, key: string, time: number, chunk: StreamChunk): boolean;
|
|
436
|
+
/**
|
|
437
|
+
* Drop the live streaming tails without a settlement (an `agent/assistant-stream`
|
|
438
|
+
* end frame with an `abandoned` outcome, or a session switch). The next start
|
|
439
|
+
* frame rebuilds from scratch.
|
|
440
|
+
* @param acc - the live replay accumulator.
|
|
441
|
+
* @returns whether any tail text was discarded.
|
|
442
|
+
*/
|
|
443
|
+
export declare function clearAssistantStream(acc: ReplayAccumulator): boolean;
|
|
444
|
+
/**
|
|
445
|
+
* The append-only flush boundary for a transcript view: the count of entries
|
|
446
|
+
* no later event can remove. Entries at or beyond this index are mutable and
|
|
447
|
+
* must stay in the live tree.
|
|
448
|
+
*
|
|
449
|
+
* `pending` rows are excluded even though they are not a running tool/retry:
|
|
450
|
+
* the inbox claims or cancels them durably (`agent/inbox/spliced` removals,
|
|
451
|
+
* `user/message` retirement), and an append-only `<Static>` flush cannot
|
|
452
|
+
* erase a row that vanishes from the view — the retired row would ghost on
|
|
453
|
+
* screen until the next source-backed replay. Running commands join the
|
|
454
|
+
* mutable boundary for the same reason in reverse: `command/done` mutates the
|
|
455
|
+
* row's state/summary, so a flushed row would keep its stale running mark
|
|
456
|
+
* until a resize-triggered replay. Everything else (including a completed
|
|
457
|
+
* tail) is final: later events only APPEND new rows.
|
|
458
|
+
* @param entries - the view's transcript entries in order.
|
|
459
|
+
* @returns the count of entries safe to flush (0 for an empty transcript).
|
|
460
|
+
*/
|
|
461
|
+
export declare function settledEntryCount(entries: readonly TranscriptEntry[]): number;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status-bar composition for the TUI footer. Codex/Claude-Code-style split
|
|
3
|
+
* line: identity facts and session figures flow from the left, while the
|
|
4
|
+
* permission badge (the Codex "autonomous selection" anchor, with its
|
|
5
|
+
* shift+tab cycle hint) pins to the right edge. Every segment carries a tone
|
|
6
|
+
* the footer maps to a theme color, and layoutStatusBar degrades the line
|
|
7
|
+
* item by item so it always fits one physical row — truncation with an
|
|
8
|
+
* ellipsis happens only after every lesser group has already dropped out.
|
|
9
|
+
*
|
|
10
|
+
* @module @deepseek-ai/dsh-tui/render/status
|
|
11
|
+
*/
|
|
12
|
+
import type { TranscriptStats } from './projection.ts';
|
|
13
|
+
/**
|
|
14
|
+
* Compact token count: 517 / 12.2K / 517K / 1.2M (one decimal under three
|
|
15
|
+
* digits), mirroring the web composer's StatsLine format.
|
|
16
|
+
* @param n - token count.
|
|
17
|
+
* @returns display string.
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatTokens(n: number): string;
|
|
20
|
+
/**
|
|
21
|
+
* Compact duration: 45.2s under a minute, 2m42s from there on.
|
|
22
|
+
* @param ms - duration in milliseconds.
|
|
23
|
+
* @returns display string.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatDuration(ms: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* Cache-hit share of billed prompt-side input.
|
|
28
|
+
* @param usage - cumulative token totals.
|
|
29
|
+
* @returns percent rounded to one decimal place, or null when no input was billed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function cacheHitPercent(usage: TranscriptStats['usage']): number | null;
|
|
32
|
+
/**
|
|
33
|
+
* Presentation tones for status spans; the footer maps each to a theme color
|
|
34
|
+
* (Codex status-line accents: model/path/branch/state/usage categories).
|
|
35
|
+
*/
|
|
36
|
+
export type StatusTone = 'model' | 'live' | 'path' | 'branch' | 'value' | 'label' | 'meta' | 'accent' | 'success' | 'warn' | 'error' | 'ctxFill';
|
|
37
|
+
/** One colored run inside the status bar. */
|
|
38
|
+
export interface StatusSpan {
|
|
39
|
+
text: string;
|
|
40
|
+
tone: StatusTone;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* One pipe-separated cluster on the leading side of the bar. Spans are the
|
|
44
|
+
* full visual sequence: junction separators ride along as their own dim
|
|
45
|
+
* 'label'-tone spans, so joining is a flat concat with no implicit glue.
|
|
46
|
+
*/
|
|
47
|
+
export interface StatusGroup {
|
|
48
|
+
spans: readonly StatusSpan[];
|
|
49
|
+
}
|
|
50
|
+
/** One physical row of the footer: leading clusters and trailing badges. */
|
|
51
|
+
export interface StatusRow {
|
|
52
|
+
/** Leading clusters, pipe-separated in display order; index 0 is identity. */
|
|
53
|
+
left: readonly StatusGroup[];
|
|
54
|
+
/** Trailing spans pinned to the right edge, dot-separated in display order. */
|
|
55
|
+
right: readonly StatusSpan[];
|
|
56
|
+
/** Whether the shift+tab cycle hint rides after the permission badge. */
|
|
57
|
+
hint: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The footer layout: two stacked physical rows. Row 1 keeps the primary
|
|
61
|
+
* controls in model, cwd, mode, branch, context, permission order. Row 2
|
|
62
|
+
* carries every secondary session/run figure and degrades independently.
|
|
63
|
+
*/
|
|
64
|
+
export interface StatusLayout {
|
|
65
|
+
row1: StatusRow;
|
|
66
|
+
row2: StatusRow;
|
|
67
|
+
}
|
|
68
|
+
/** Separator between leading clusters. */
|
|
69
|
+
export declare const STATUS_GROUP_SEPARATOR = " | ";
|
|
70
|
+
/** Separator between trailing state spans. */
|
|
71
|
+
export declare const STATUS_ITEM_SEPARATOR = " \u00B7 ";
|
|
72
|
+
/** The Codex-style mode cycle hint appended to the permission badge. */
|
|
73
|
+
export declare const STATUS_CYCLE_HINT = " (shift+tab to cycle)";
|
|
74
|
+
/**
|
|
75
|
+
* Interior columns of the context bar. The layout starts every bar at this
|
|
76
|
+
* width so the drop ladder can pre-measure the group, then degrades the
|
|
77
|
+
* readout and shrinks the bar inside a tighter budget before dropping the
|
|
78
|
+
* group (see CONTEXT_MIN_WIDTH) rather than asking the layout for more room.
|
|
79
|
+
*/
|
|
80
|
+
export declare const CONTEXT_BAR_WIDTH = 24;
|
|
81
|
+
/**
|
|
82
|
+
* Render context occupancy as ONE stepless proportional bar: a solid
|
|
83
|
+
* DeepSeek-blue fill run tracking the occupancy and a dim dotted free
|
|
84
|
+
* track for the rest. Nothing else lives inside the bar — the usage
|
|
85
|
+
* readout rides outside it (see contextGroupSpans) — so the geometry
|
|
86
|
+
* always reads as the true remaining share. A given occupancy always
|
|
87
|
+
* renders the identical bar.
|
|
88
|
+
* @param usedTokens - reported used tokens.
|
|
89
|
+
* @param contextWindow - route capacity.
|
|
90
|
+
* @param width - total bar columns.
|
|
91
|
+
* @returns tone-split spans for the footer to paint.
|
|
92
|
+
*/
|
|
93
|
+
export declare function contextBar(usedTokens: number, contextWindow: number, width: number): readonly StatusSpan[];
|
|
94
|
+
/** How much usage detail the context group's readout carries. */
|
|
95
|
+
export type ContextReadoutMode = 'full' | 'percent' | 'none';
|
|
96
|
+
/**
|
|
97
|
+
* Compose the context group: the proportional bar plus the usage readout
|
|
98
|
+
* OUTSIDE the bar, so the dotted track keeps its proportional meaning no
|
|
99
|
+
* matter how wide the readout is. `full` reads `12.3K/1.0M 25%`; `percent`
|
|
100
|
+
* drops the absolute pair; `none` is the bare bar. The readout turns amber
|
|
101
|
+
* once occupancy reaches the warning threshold.
|
|
102
|
+
*/
|
|
103
|
+
export declare function contextGroupSpans(usedTokens: number, contextWindow: number, barWidth: number, readout: ContextReadoutMode): readonly StatusSpan[];
|
|
104
|
+
/**
|
|
105
|
+
* One customizable status item (the Codex /statusline picker contract).
|
|
106
|
+
* 'left' items render as pipe-separated clusters after the identity dot;
|
|
107
|
+
* 'right' items pin to the right edge as dot-separated state badges.
|
|
108
|
+
*/
|
|
109
|
+
export type StatusItemId = 'model' | 'cwd' | 'branch' | 'plan' | 'mode' | 'turns' | 'durations' | 'cache' | 'context' | 'tokens' | 'title' | 'goal' | 'sandbox' | 'permission';
|
|
110
|
+
/** Picker-facing metadata for one customizable item. */
|
|
111
|
+
export interface StatusItemInfo {
|
|
112
|
+
id: StatusItemId;
|
|
113
|
+
/** Short picker label. */
|
|
114
|
+
label: string;
|
|
115
|
+
/** One-line picker description of what the item shows. */
|
|
116
|
+
description: string;
|
|
117
|
+
/** Which side of the split row the item renders on. */
|
|
118
|
+
side: 'left' | 'right';
|
|
119
|
+
}
|
|
120
|
+
/** The full item catalog in canonical order (the /statusline default). */
|
|
121
|
+
export declare const STATUS_ITEMS: readonly StatusItemInfo[];
|
|
122
|
+
/**
|
|
123
|
+
* Default order: the whole catalog (matches the pre-customization bar).
|
|
124
|
+
* The busy dot is not an item — it always leads the identity cluster.
|
|
125
|
+
*/
|
|
126
|
+
export declare const DEFAULT_STATUSLINE_ITEMS: readonly StatusItemId[];
|
|
127
|
+
/**
|
|
128
|
+
* Parse a persisted statusline item list. The stored value is the ordered
|
|
129
|
+
* set of ENABLED items (the Codex /statusline contract): unknown ids and
|
|
130
|
+
* duplicates drop out, and a non-array value (missing or corrupt file)
|
|
131
|
+
* falls back to the full default set. An explicitly empty array is valid —
|
|
132
|
+
* the bar degrades to its busy dot alone.
|
|
133
|
+
* @param value - the raw parsed JSON value (expected string[]).
|
|
134
|
+
* @returns the normalized ordered item list.
|
|
135
|
+
*/
|
|
136
|
+
export declare function parseStatuslineItems(value: unknown): readonly StatusItemId[];
|
|
137
|
+
/**
|
|
138
|
+
* Extra left padding on the secondary row so its content aligns with the
|
|
139
|
+
* model name's left edge on the primary row (padding 2 + busy dot 2). The
|
|
140
|
+
* layout subtracts it from row 2's budget so the indent can never wrap it.
|
|
141
|
+
*/
|
|
142
|
+
export declare const STATUS_ROW2_INDENT = 2;
|
|
143
|
+
/** Identity facts the runner resolves once at mount; empty strings drop out. */
|
|
144
|
+
export interface StatusFacts {
|
|
145
|
+
/** 'provider/model' selection serving this session. */
|
|
146
|
+
model: string;
|
|
147
|
+
/** Agent preset composing this session. */
|
|
148
|
+
mode?: string;
|
|
149
|
+
/** Working-directory basename the session serves. */
|
|
150
|
+
cwd: string;
|
|
151
|
+
/** Git branch name, empty outside a repository or on a detached HEAD file. */
|
|
152
|
+
branch: string;
|
|
153
|
+
/** Short session identifier (last dash-separated segment or tail). */
|
|
154
|
+
sessionId: string;
|
|
155
|
+
/** Latest session title (folded from 'session/title'); shown in place of the id. */
|
|
156
|
+
title: string;
|
|
157
|
+
/** Sandbox-mode override (folded from 'sandbox/mode'), empty when never switched. */
|
|
158
|
+
sandbox: string;
|
|
159
|
+
/** Live goal summary (folded from 'goal/change'), undefined when none. */
|
|
160
|
+
goal: {
|
|
161
|
+
phase: string;
|
|
162
|
+
rounds: number;
|
|
163
|
+
max: number;
|
|
164
|
+
} | undefined;
|
|
165
|
+
/** Whether plan mode is active (folded from 'plan/mode'). */
|
|
166
|
+
plan: boolean;
|
|
167
|
+
/** Active or pending permission preset; empty only when the service is unavailable. */
|
|
168
|
+
permission: string;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Traffic-light tone for a permission preset: read-only stays success green,
|
|
172
|
+
* full access reads error red, and every workspace-scoped middle ground
|
|
173
|
+
* (including unknown presets) reads warning amber.
|
|
174
|
+
* @param permission - active permission preset label.
|
|
175
|
+
* @returns tone for the badge span.
|
|
176
|
+
*/
|
|
177
|
+
export declare function permissionTone(permission: string): StatusTone;
|
|
178
|
+
/**
|
|
179
|
+
* Compose the two-row footer layout under a column budget. Row 1 keeps model,
|
|
180
|
+
* cwd, mode, branch, context, then the right-pinned permission badge and cycle
|
|
181
|
+
* hint. It drops hint, context, and permission before ellipsizing identity.
|
|
182
|
+
* Row 2 fits all secondary figures and state within its own budget.
|
|
183
|
+
* @param facts - identity facts resolved by the runner.
|
|
184
|
+
* @param stats - session figures folded from the durable log.
|
|
185
|
+
* @param columns - usable columns for each row (before their left padding).
|
|
186
|
+
* @param options - 'busy' hides the cycle hint while a turn runs (Codex
|
|
187
|
+
* keeps mode hints idle-only); 'items' is the ordered enabled-item config
|
|
188
|
+
* from /statusline (defaults to the full catalog). Display order follows the
|
|
189
|
+
* config per side while the drop ladder keeps its fixed ranks.
|
|
190
|
+
* @returns the two rows to render; row1.left is never empty.
|
|
191
|
+
*/
|
|
192
|
+
export declare function layoutStatusBar(facts: StatusFacts, stats: TranscriptStats, columns: number, options?: {
|
|
193
|
+
busy?: boolean;
|
|
194
|
+
items?: readonly string[];
|
|
195
|
+
contextWidth?: number;
|
|
196
|
+
}): StatusLayout;
|