dsh-ssh-tui 0.8.0 → 0.8.2-rc.1

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/README.en.md +74 -8
  3. package/README.md +451 -577
  4. package/docs/display-mode.md +122 -0
  5. package/docs/release-notes-0.7.4.md +91 -0
  6. package/docs/release-notes-0.8.0.md +56 -0
  7. package/docs/release-notes-0.8.1.md +79 -0
  8. package/docs/release-notes-0.8.2-rc.1.md +159 -0
  9. package/docs/remote-ops.md +51 -0
  10. package/docs/terminals.md +53 -0
  11. package/docs/windows.md +13 -0
  12. package/lib/approval-state.js +148 -0
  13. package/lib/approval-state.js.map +1 -0
  14. package/lib/attach.js +4 -4
  15. package/lib/attach.js.map +1 -1
  16. package/lib/auth-failure.js +74 -0
  17. package/lib/auth-failure.js.map +1 -1
  18. package/lib/color-depth.js +26 -0
  19. package/lib/color-depth.js.map +1 -1
  20. package/lib/commands.js +2 -0
  21. package/lib/commands.js.map +1 -1
  22. package/lib/copy-text.js +30 -8
  23. package/lib/copy-text.js.map +1 -1
  24. package/lib/dialogs.js +124 -0
  25. package/lib/dialogs.js.map +1 -1
  26. package/lib/display-mode.js +147 -0
  27. package/lib/display-mode.js.map +1 -0
  28. package/lib/display-sock.js +490 -35
  29. package/lib/display-sock.js.map +1 -1
  30. package/lib/footer-accents.js +28 -0
  31. package/lib/footer-accents.js.map +1 -0
  32. package/lib/footer-budget.js +328 -0
  33. package/lib/footer-budget.js.map +1 -0
  34. package/lib/footer-format.js +433 -0
  35. package/lib/footer-format.js.map +1 -0
  36. package/lib/footer-layout.js +407 -0
  37. package/lib/footer-layout.js.map +1 -0
  38. package/lib/footer-meters.js +185 -0
  39. package/lib/footer-meters.js.map +1 -0
  40. package/lib/footer.js +19 -561
  41. package/lib/footer.js.map +1 -1
  42. package/lib/glyph-measure.js +92 -0
  43. package/lib/glyph-measure.js.map +1 -0
  44. package/lib/i18n/en.js +83 -7
  45. package/lib/i18n/en.js.map +1 -1
  46. package/lib/i18n/zh.js +85 -7
  47. package/lib/i18n/zh.js.map +1 -1
  48. package/lib/index.js +73 -5
  49. package/lib/index.js.map +1 -1
  50. package/lib/paint.js +75 -17
  51. package/lib/paint.js.map +1 -1
  52. package/lib/picker.js +14 -13
  53. package/lib/picker.js.map +1 -1
  54. package/lib/plan-projection.js +291 -0
  55. package/lib/plan-projection.js.map +1 -0
  56. package/lib/plan.js +61 -11
  57. package/lib/plan.js.map +1 -1
  58. package/lib/platform.js +96 -0
  59. package/lib/platform.js.map +1 -1
  60. package/lib/question-state.js +83 -0
  61. package/lib/question-state.js.map +1 -0
  62. package/lib/representation.js +283 -0
  63. package/lib/representation.js.map +1 -0
  64. package/lib/screen.js +105 -0
  65. package/lib/screen.js.map +1 -0
  66. package/lib/selection.js +5 -3
  67. package/lib/selection.js.map +1 -1
  68. package/lib/session-blank.js +81 -0
  69. package/lib/session-blank.js.map +1 -0
  70. package/lib/session-list.js +102 -81
  71. package/lib/session-list.js.map +1 -1
  72. package/lib/startup.js +56 -0
  73. package/lib/startup.js.map +1 -1
  74. package/lib/stats.js +101 -3
  75. package/lib/stats.js.map +1 -1
  76. package/lib/subagent-model.js +8 -7
  77. package/lib/subagent-model.js.map +1 -1
  78. package/lib/term-text.js +436 -69
  79. package/lib/term-text.js.map +1 -1
  80. package/lib/terminal-input.js +132 -10
  81. package/lib/terminal-input.js.map +1 -1
  82. package/lib/theme.js +335 -0
  83. package/lib/theme.js.map +1 -0
  84. package/lib/throughput.js +305 -0
  85. package/lib/throughput.js.map +1 -0
  86. package/lib/tool-present.js +67 -15
  87. package/lib/tool-present.js.map +1 -1
  88. package/lib/tui.js +4186 -1133
  89. package/lib/tui.js.map +1 -1
  90. package/lib/types/approval-state.d.ts +75 -0
  91. package/lib/types/attach.d.ts +6 -2
  92. package/lib/types/auth-failure.d.ts +55 -0
  93. package/lib/types/color-depth.d.ts +21 -0
  94. package/lib/types/commands.d.ts +6 -0
  95. package/lib/types/copy-text.d.ts +18 -1
  96. package/lib/types/dialogs.d.ts +147 -1
  97. package/lib/types/display-mode.d.ts +99 -0
  98. package/lib/types/display-sock.d.ts +90 -0
  99. package/lib/types/footer-accents.d.ts +22 -0
  100. package/lib/types/footer-budget.d.ts +204 -0
  101. package/lib/types/footer-format.d.ts +221 -0
  102. package/lib/types/footer-layout.d.ts +292 -0
  103. package/lib/types/footer-meters.d.ts +151 -0
  104. package/lib/types/footer.d.ts +19 -269
  105. package/lib/types/glyph-measure.d.ts +41 -0
  106. package/lib/types/index.d.ts +13 -0
  107. package/lib/types/paint.d.ts +48 -0
  108. package/lib/types/plan-projection.d.ts +162 -0
  109. package/lib/types/plan.d.ts +18 -2
  110. package/lib/types/platform.d.ts +83 -0
  111. package/lib/types/question-state.d.ts +96 -0
  112. package/lib/types/representation.d.ts +498 -0
  113. package/lib/types/screen.d.ts +126 -0
  114. package/lib/types/session-blank.d.ts +51 -0
  115. package/lib/types/session-list.d.ts +34 -0
  116. package/lib/types/startup.d.ts +30 -0
  117. package/lib/types/stats.d.ts +64 -1
  118. package/lib/types/subagent-model.d.ts +7 -6
  119. package/lib/types/term-text.d.ts +100 -24
  120. package/lib/types/terminal-input.d.ts +37 -0
  121. package/lib/types/theme.d.ts +109 -0
  122. package/lib/types/throughput.d.ts +163 -0
  123. package/lib/types/tool-present.d.ts +43 -2
  124. package/lib/types/transcript-types.d.ts +63 -2
  125. package/lib/types/tui.d.ts +960 -12
  126. package/package.json +73 -67
@@ -235,3 +235,86 @@ export declare function restrictPathToUserSync(path: string, options?: {
235
235
  * @returns true when chrome must be drawn in ASCII.
236
236
  */
237
237
  export declare function asciiFallbackEnabled(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
238
+ /**
239
+ * What this process can serve.
240
+ *
241
+ * The plugin is a *terminal* UI, and the host it is mounted into decides whether
242
+ * there is a terminal at all:
243
+ *
244
+ * - `host-relay`: this is the detached TUI Host itself (see
245
+ * `isTuiHostProcess`); its terminal lives in whichever window attaches to the
246
+ * session, so a missing TTY here is expected and it must serve.
247
+ * - `terminal`: a real TTY on stdin and stdout.
248
+ * - `desktop`: the desktop Harness launcher (Electron-as-Node, GUI subsystem).
249
+ * It never has a console, so no terminal profile can ever run under it.
250
+ * - `no-tty`: anything else without a TTY — a pipe (`… | tee`), cron, a service.
251
+ */
252
+ export type TerminalAvailability = 'host-relay' | 'terminal' | 'desktop' | 'no-tty';
253
+ /**
254
+ * Decide what to serve, from facts the caller can state.
255
+ *
256
+ * Pure on purpose: this is the one branch that decides whether the plugin mounts
257
+ * anything at all, and every caller-visible behaviour difference (a window, a
258
+ * log line, a hard failure) hangs off it.
259
+ * @param facts - the host process marker, the two TTY answers, and whether the
260
+ * launcher is the desktop's.
261
+ * @returns the availability bucket.
262
+ */
263
+ export declare function terminalAvailability(facts: {
264
+ hostProcess: boolean;
265
+ stdinIsTty: boolean;
266
+ stdoutIsTty: boolean;
267
+ desktop: boolean;
268
+ }): TerminalAvailability;
269
+ /**
270
+ * Whether this process is the desktop app's launcher rather than a terminal Node.
271
+ *
272
+ * The desktop Harness ships a PATH shim that runs its own GUI-subsystem binary
273
+ * with `ELECTRON_RUN_AS_NODE=1` (`DeepSeek Harness.exe --expose-internals
274
+ * app.asar/dsh/.../cli.js`). A GUI-subsystem process started from a console does
275
+ * not attach to it on Windows, so `process.stdin.isTTY` / `stdout.isTTY` stay
276
+ * undefined and no terminal profile can start under it. That is a property of
277
+ * the launcher, not of the user's terminal, and the two cases deserve different
278
+ * advice — which is all this decides.
279
+ * @param facts - the runtime's own answers (`process.versions`, `execPath`, `argv`).
280
+ * @returns true when the launcher is the desktop app's Electron-as-Node shim.
281
+ */
282
+ export declare function desktopLauncher(facts: {
283
+ electron?: string | undefined;
284
+ execPath?: string | undefined;
285
+ argv?: readonly string[] | undefined;
286
+ }): boolean;
287
+ /**
288
+ * The message a terminal profile fails with when the launcher has no TTY.
289
+ *
290
+ * The desktop case is worth its own wording: the generic "must be TTYs" line
291
+ * sends a desktop user hunting for a broken terminal, while the real answer is
292
+ * "this launcher never has one — use the npm CLI in a terminal/SSH session, and
293
+ * note the desktop app does not need this plugin at all".
294
+ * @param desktop - whether {@link desktopLauncher} recognised the launcher.
295
+ * @returns the error text for the plugin to throw.
296
+ */
297
+ export declare function nonTtyErrorMessage(desktop: boolean): string;
298
+ /**
299
+ * What to log when the plugin has nowhere to draw.
300
+ *
301
+ * Deliberately not an error: on the desktop the situation is normal (a GUI app
302
+ * has no console), and a terminal profile is not a thing the user broke. The
303
+ * line has to (a) say nothing is running *from this plugin*, (b) explain why,
304
+ * (c) give the one command that fixes it, and (d) not read as a failure — the
305
+ * plugin used to throw here, which surfaced as "1 entry did not activate" and,
306
+ * on the desktop, took the app down with it.
307
+ * @param availability - `desktop` or `no-tty`.
308
+ * @returns the log line.
309
+ */
310
+ export declare function inactiveNotice(availability: 'desktop' | 'no-tty'): string;
311
+ /**
312
+ * The message for the opt-in hard failure (`requireTerminal: true`).
313
+ *
314
+ * Same two situations as {@link inactiveNotice}, but for callers who would
315
+ * rather a profile fail loudly than quietly do nothing — a script asserting that
316
+ * a terminal profile really starts, for one.
317
+ * @param desktop - whether the launcher is the desktop's.
318
+ * @returns the error text.
319
+ */
320
+ export declare function requiredTerminalError(desktop: boolean): string;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The durable half of an ask-user question, as the Session reports it.
3
+ *
4
+ * A question used to exist only as a live request: the dialog the answerer opened
5
+ * and the row it pushed. Nothing rebuilt it from the log, so `--resume` showed the
6
+ * `ask_user_question` *tool* card and nothing that said what was asked or answered —
7
+ * the same session read differently depending on whether it was lived or replayed.
8
+ *
9
+ * The Session already records the truth: `tool/call` carries the questions and
10
+ * `tool/result` (or a late `user-question-reply` message) carries the answers, and
11
+ * `@deepseek-ai/dsh-user-questions` folds both into a projection every Client can
12
+ * read (`ctx.sessionProjections.stateOf(session, 'userQuestions')`). This module
13
+ * turns that view into the records the transcript paints, and it is deliberately
14
+ * pure: the caller supplies the questions, because the projection's *settled* half
15
+ * names only the call and the answers.
16
+ *
17
+ * Two limits are the Harness's, not this module's, and both are why the caller
18
+ * keeps its live path as well:
19
+ *
20
+ * - the projection tracks only **timed** `ask_user_question` calls — a call made
21
+ * while the blocking legacy schema was in effect appears in neither half, so a
22
+ * session that only ever used it folds to an empty view;
23
+ * - it needs the projection service to be registered at all, which a bare Context
24
+ * in a test (or an embedder without `dsh-session-projection`) does not provide.
25
+ *
26
+ * @module dsh-ssh-tui/question-state
27
+ */
28
+ import type { AskUserQuestionAnswerItem, AskUserQuestionItem } from '@deepseek-ai/dsh-user-questions';
29
+ /**
30
+ * The projection's two halves, declared here as the *shape this plugin reads*
31
+ * rather than imported by name.
32
+ *
33
+ * `PendingUserQuestion`, `SettledUserQuestion` and `UserQuestionProjectionView` are
34
+ * named exports of `dsh-user-questions` only from 0.2.0-rc.2 on: the same module on
35
+ * 0.1.7-rc.x and 0.2.0-rc.1 exports a different surface, and importing the names made
36
+ * the plugin fail to *compile* there — which is how three CI legs went red at 0.8.2's
37
+ * first push. Nothing here needs the host's names: every field below is already read
38
+ * defensively (`questionViewOf` takes `unknown` and parses it), and the two item types
39
+ * are exported by every line the plugin declares.
40
+ */
41
+ export interface QuestionViewPending {
42
+ readonly callId: string;
43
+ readonly questions: readonly AskUserQuestionItem[];
44
+ /** `open` while the window is live, `continued` once it closed but still accepts. */
45
+ readonly state?: string;
46
+ }
47
+ export interface QuestionViewSettled {
48
+ readonly callId: string;
49
+ readonly answers?: readonly AskUserQuestionAnswerItem[];
50
+ }
51
+ /** Both halves of one Session's timed `ask_user_question` state. */
52
+ export interface QuestionView {
53
+ readonly active: readonly QuestionViewPending[];
54
+ readonly settled: readonly QuestionViewSettled[];
55
+ }
56
+ /** One question as the transcript should show it, live or replayed. */
57
+ export interface DurableQuestionRecord {
58
+ /** The `ask_user_question` call this question belongs to. */
59
+ callId: string;
60
+ question: AskUserQuestionItem;
61
+ /**
62
+ * `open` is a foreground wait nobody has answered yet; `continued` is one whose
63
+ * window closed but which the Session still accepts an answer for; the other two
64
+ * are terminal.
65
+ */
66
+ state: 'open' | 'continued' | 'answered' | 'cancelled';
67
+ /** What the human chose; empty while the question is unanswered. */
68
+ answers: readonly AskUserQuestionAnswerItem[];
69
+ }
70
+ /** The projection view, or the empty one when nothing is registered. */
71
+ export declare function questionViewOf(value: unknown): QuestionView | undefined;
72
+ /** Whether the projection has anything to say about this session's questions. */
73
+ export declare function questionViewIsEmpty(view: QuestionView | undefined): boolean;
74
+ /**
75
+ * Every question the Session log can still account for, in ask order then
76
+ * settlement order.
77
+ *
78
+ * A settled call names only its answers, so a call whose questions this process
79
+ * never saw (a resumed session whose `tool/call` predates the log window it read)
80
+ * contributes nothing rather than a half-record: an answer with no question is not
81
+ * something the transcript can draw.
82
+ * @param view - the projection view, or undefined when nothing is registered.
83
+ * @param questionsOf - the questions recorded for one call, when known.
84
+ * @returns one record per question, newest last.
85
+ */
86
+ export declare function durableQuestionRecords(view: QuestionView | undefined, questionsOf: (callId: string) => readonly AskUserQuestionItem[] | undefined): DurableQuestionRecord[];
87
+ /**
88
+ * How one answered question reads on its card.
89
+ *
90
+ * Mirrors what the live dialog writes when it settles a card, so a session that
91
+ * was replayed shows the same sentence as one that was lived: the typed answer
92
+ * when there was one, the chosen labels otherwise.
93
+ * @param answers - the recorded answers for this question.
94
+ * @returns the summary text.
95
+ */
96
+ export declare function answerSummaryText(answers: readonly AskUserQuestionAnswerItem[]): string;
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Representation policy: the one place that decides what a transcript line *is*.
3
+ *
4
+ * AD-10 in `docs/decisions/b2-architecture-decisions.md` froze the shape —
5
+ * `Event / Command → Representation Policy → Representation → Renderer` — and this
6
+ * module is the policy. A renderer never decides whether something exists, and a
7
+ * creation site never decides whether its line is history or an echo: it names its
8
+ * *source*, and the table below says what that source means.
9
+ *
10
+ * The boundary is enforced by the type system rather than by review:
11
+ *
12
+ * - `pushRow` takes a `Representation`, not a `Row`, so a line cannot reach the
13
+ * transcript without passing through `represent()` — a bare row is a type error;
14
+ * - `RepresentationSource` is `keyof` the policy table, so naming a source that has
15
+ * no policy is also a type error: adding a creation site *requires* adding its
16
+ * policy, and there is no default to fall back on;
17
+ * - anything that arrives anyway (a JavaScript caller, a test fixture) is pushed
18
+ * with no metadata and counted as **unclassified** by `auditRepresentations`,
19
+ * which the suite asserts is zero. Nothing is silently assumed durable.
20
+ *
21
+ * Three durabilities (B2.3a):
22
+ *
23
+ * - `durable` — rebuilt from the authoritative session log by replaying it, so it
24
+ * is there again after a resume;
25
+ * - `display` — lives only for this Host/display's lifetime; its absence after a
26
+ * resume is correct, not a loss;
27
+ * - `live` — a runtime projection, recomputed every tick. **It must never be a
28
+ * source row at all** (B2.2 moved the streaming text and the wait card out); a
29
+ * `live` row in the transcript is a bug, and the audit reports it as one.
30
+ *
31
+ * The A/B/C/D classes are orthogonal and describe the *form* of the
32
+ * representation, not its importance:
33
+ *
34
+ * - **A** persistent transcript entry — an ordinary row;
35
+ * - **B** ephemeral transient — never a source row (the live tail, a Surface);
36
+ * - **C** summary entry — a row that summarizes an exchange (`question` cards);
37
+ * - **D** artifact reference — a row that points at an object (plans, later).
38
+ *
39
+ * "Important" is not the same as "durable": a quota alert matters and is still
40
+ * `display`, because no replay can bring it back.
41
+ */
42
+ import type { Row } from './transcript-types.js';
43
+ /** Where a line can come from after a resume. */
44
+ export type Durability = 'durable' | 'display' | 'live';
45
+ /**
46
+ * Where a representation is *shown* (B2.3b's routing boundary).
47
+ *
48
+ * Classification says what a line is; routing says where it goes. The policy owns
49
+ * both, so a control-plane confirmation cannot be a transcript row at one call site
50
+ * and a footer echo at the next: the source decides, once.
51
+ *
52
+ * - `transcript` — a real line in the log's view (narrative, a durable summary, a
53
+ * Screen's line-mode echo, or a local record with causal value);
54
+ * - `echo` — a Host-local footer chip: the confirmation of something the reader just
55
+ * did. Replaced by the next echo, cleared by the next submit, never in the log;
56
+ * - `notice` — a one-row Host-local notice above the composer, for feedback that
57
+ * needs to be read now but is not history (a failed command, an operational
58
+ * warning). Same lifetime rules as the echo, more room for the message.
59
+ *
60
+ * The rule behind the split is AD-13: narrative and causal facts go to the
61
+ * transcript, runtime state belongs to the footer's own facts, and immediate action
62
+ * feedback is an echo or a notice.
63
+ */
64
+ export type Destination = 'transcript' | 'echo' | 'notice';
65
+ /** The form of the representation (see the module comment). */
66
+ export type RepresentationClass = 'A' | 'B' | 'C' | 'D';
67
+ /**
68
+ * Every place that may put a line into the transcript.
69
+ *
70
+ * Adding an entry here is what makes a new creation site possible, and the entry
71
+ * states what the line *is*. The ids name the producer, not the wording: one id
72
+ * covers a family of messages whose answer to "is this history?" is the same.
73
+ */
74
+ export declare const REPRESENTATION_POLICY: {
75
+ readonly 'user-message': {
76
+ readonly durability: "durable";
77
+ readonly class: "A";
78
+ readonly destination: "transcript";
79
+ };
80
+ readonly 'assistant-message': {
81
+ readonly durability: "durable";
82
+ readonly class: "A";
83
+ readonly destination: "transcript";
84
+ };
85
+ readonly 'reasoning-message': {
86
+ readonly durability: "durable";
87
+ readonly class: "A";
88
+ readonly destination: "transcript";
89
+ };
90
+ readonly 'tool-call': {
91
+ readonly durability: "durable";
92
+ readonly class: "A";
93
+ readonly destination: "transcript";
94
+ };
95
+ readonly 'turn-error': {
96
+ readonly durability: "durable";
97
+ readonly class: "A";
98
+ readonly destination: "transcript";
99
+ };
100
+ /** Notices the log itself carries: reminders, context snapshots, interrupted streams. */
101
+ readonly 'session-notice': {
102
+ readonly durability: "durable";
103
+ readonly class: "A";
104
+ readonly destination: "transcript";
105
+ };
106
+ readonly compaction: {
107
+ readonly durability: "durable";
108
+ readonly class: "A";
109
+ readonly destination: "transcript";
110
+ };
111
+ readonly goal: {
112
+ readonly durability: "durable";
113
+ readonly class: "A";
114
+ readonly destination: "transcript";
115
+ };
116
+ readonly 'prompt-injection': {
117
+ readonly durability: "durable";
118
+ readonly class: "A";
119
+ readonly destination: "transcript";
120
+ };
121
+ readonly 'plan-row': {
122
+ readonly durability: "durable";
123
+ readonly class: "D";
124
+ readonly destination: "transcript";
125
+ };
126
+ readonly 'subagent-card': {
127
+ readonly durability: "durable";
128
+ readonly class: "A";
129
+ readonly destination: "transcript";
130
+ };
131
+ readonly 'command-event': {
132
+ readonly durability: "durable";
133
+ readonly class: "A";
134
+ readonly destination: "transcript";
135
+ };
136
+ readonly 'retry-notice': {
137
+ readonly durability: "durable";
138
+ readonly class: "A";
139
+ readonly destination: "transcript";
140
+ };
141
+ readonly 'session-route': {
142
+ readonly durability: "durable";
143
+ readonly class: "A";
144
+ readonly destination: "transcript";
145
+ };
146
+ /** The question card: one row summarizing an exchange, rebuilt from the projection. */
147
+ readonly 'question-card': {
148
+ readonly durability: "durable";
149
+ readonly class: "C";
150
+ readonly destination: "transcript";
151
+ };
152
+ readonly boot: {
153
+ readonly durability: "display";
154
+ readonly class: "A";
155
+ readonly destination: "transcript";
156
+ };
157
+ /** A confirmation of something the reader just did: a footer echo. */
158
+ readonly 'command-feedback': {
159
+ readonly durability: "display";
160
+ readonly class: "A";
161
+ readonly destination: "echo";
162
+ };
163
+ /**
164
+ * A command that *reports* state rather than changing it (`/retryauth` with no
165
+ * argument, `/notify` showing its target).
166
+ *
167
+ * A report is information, not an acknowledgement: the reader may need to read it
168
+ * twice, or compare it with something they scrolled past, so it stays in the
169
+ * transcript. The policy draws that line, not the call site.
170
+ */
171
+ readonly 'command-status': {
172
+ readonly durability: "display";
173
+ readonly class: "A";
174
+ readonly destination: "transcript";
175
+ };
176
+ /** The outcome of a repair that edited the install (`/mode fix`). */
177
+ readonly 'repair-result': {
178
+ readonly durability: "display";
179
+ readonly class: "A";
180
+ readonly destination: "transcript";
181
+ };
182
+ /**
183
+ * A local command that failed.
184
+ *
185
+ * Kept in the transcript: the message is usually an error chain the reader has to
186
+ * be able to scroll back to, and re-running the command is not the same as reading
187
+ * what it said. (A command that was simply *misused* is the notice below.)
188
+ */
189
+ readonly 'command-error': {
190
+ readonly durability: "display";
191
+ readonly class: "A";
192
+ readonly destination: "transcript";
193
+ };
194
+ /**
195
+ * A command that could not be understood at all: a mistyped name, a missing
196
+ * argument, a value outside the table. Immediate feedback, and nothing to keep —
197
+ * the reader fixes the line and moves on.
198
+ */
199
+ readonly 'command-misuse': {
200
+ readonly durability: "display";
201
+ readonly class: "A";
202
+ readonly destination: "notice";
203
+ };
204
+ /** How to use a command: the reader asked for it, and it is not history. */
205
+ readonly 'command-usage': {
206
+ readonly durability: "display";
207
+ readonly class: "A";
208
+ readonly destination: "notice";
209
+ };
210
+ readonly 'auth-notice': {
211
+ readonly durability: "display";
212
+ readonly class: "A";
213
+ readonly destination: "transcript";
214
+ };
215
+ readonly onboarding: {
216
+ readonly durability: "display";
217
+ readonly class: "A";
218
+ readonly destination: "transcript";
219
+ };
220
+ /**
221
+ * An auto approval the TUI *refused*. The transient notice, not a row (B2.4).
222
+ *
223
+ * The durable trace of a refusal is the tool card it guarded — its `approval`
224
+ * field, rebuilt from the Harness's own `approval/asked` + `approval/decided`
225
+ * pair — so a second, permanent sentence saying "rejected" would be a second
226
+ * representation of the same event (and one nothing can rebuild). What the notice
227
+ * adds is *now*: the reader sees why the model changed course while it happens.
228
+ */
229
+ readonly 'approval-notice': {
230
+ readonly durability: "display";
231
+ readonly class: "A";
232
+ readonly destination: "notice";
233
+ };
234
+ /**
235
+ * An auto approval the TUI *granted*.
236
+ *
237
+ * Routed to the footer chip rather than the notice row: an allow is an
238
+ * acknowledgement, and it happens far more often than a refusal — the identity
239
+ * row is for the one the reader must not miss.
240
+ */
241
+ readonly 'approval-allowance': {
242
+ readonly durability: "display";
243
+ readonly class: "A";
244
+ readonly destination: "echo";
245
+ };
246
+ readonly 'doctor-result': {
247
+ readonly durability: "display";
248
+ readonly class: "A";
249
+ readonly destination: "transcript";
250
+ };
251
+ /** Preset wizard/list feedback and its failures. */
252
+ readonly 'preset-feedback': {
253
+ readonly durability: "display";
254
+ readonly class: "A";
255
+ readonly destination: "echo";
256
+ };
257
+ readonly 'preset-error': {
258
+ readonly durability: "display";
259
+ readonly class: "A";
260
+ readonly destination: "transcript";
261
+ };
262
+ /** The self-update path: progress is an echo, a failure is a notice. */
263
+ readonly 'update-feedback': {
264
+ readonly durability: "display";
265
+ readonly class: "A";
266
+ readonly destination: "echo";
267
+ };
268
+ readonly 'update-error': {
269
+ readonly durability: "display";
270
+ readonly class: "A";
271
+ readonly destination: "notice";
272
+ };
273
+ /** A quota or balance reading the reader asked for. */
274
+ readonly 'quota-report': {
275
+ readonly durability: "display";
276
+ readonly class: "A";
277
+ readonly destination: "echo";
278
+ };
279
+ /** The context window filling up: operational, and worth reading now. */
280
+ readonly 'runtime-warning': {
281
+ readonly durability: "display";
282
+ readonly class: "A";
283
+ readonly destination: "transcript";
284
+ };
285
+ /** An approval the TUI could not resolve on its own. */
286
+ readonly 'approval-warning': {
287
+ readonly durability: "display";
288
+ readonly class: "A";
289
+ readonly destination: "transcript";
290
+ };
291
+ /** A question the Session still holds but cannot answer any more. */
292
+ readonly 'question-notice': {
293
+ readonly durability: "display";
294
+ readonly class: "A";
295
+ readonly destination: "transcript";
296
+ };
297
+ /**
298
+ * A child agent starting or ending.
299
+ *
300
+ * Left in the transcript on purpose: it is narrative about work that happened, and
301
+ * the durable child *card* is what a reader scrolls back to.
302
+ */
303
+ readonly 'subagent-notice': {
304
+ readonly durability: "display";
305
+ readonly class: "A";
306
+ readonly destination: "transcript";
307
+ };
308
+ /**
309
+ * What happened while the display was away.
310
+ *
311
+ * AD-13's causal exception: a gap in the work is exactly what a later reader needs
312
+ * explained, so this one stays in the transcript rather than becoming an echo.
313
+ */
314
+ readonly 'away-summary': {
315
+ readonly durability: "display";
316
+ readonly class: "A";
317
+ readonly destination: "transcript";
318
+ };
319
+ readonly 'attach-notice': {
320
+ readonly durability: "display";
321
+ readonly class: "A";
322
+ readonly destination: "transcript";
323
+ };
324
+ readonly 'find-feedback': {
325
+ readonly durability: "display";
326
+ readonly class: "A";
327
+ readonly destination: "echo";
328
+ };
329
+ readonly 'copy-feedback': {
330
+ readonly durability: "display";
331
+ readonly class: "A";
332
+ readonly destination: "echo";
333
+ };
334
+ /**
335
+ * The caveat that a terminal cannot take an OSC 52 write, so the copy may not have
336
+ * landed.
337
+ *
338
+ * An operational warning (AD-13), not an acknowledgement: it goes to the notice row
339
+ * where it can be read, and stays out of the footer chip that says "copied" — the
340
+ * two would otherwise overwrite each other.
341
+ */
342
+ readonly 'copy-caveat': {
343
+ readonly durability: "display";
344
+ readonly class: "A";
345
+ readonly destination: "notice";
346
+ };
347
+ readonly 'plan-notice': {
348
+ readonly durability: "display";
349
+ readonly class: "A";
350
+ readonly destination: "echo";
351
+ };
352
+ /**
353
+ * A plan artifact's lifecycle in the transcript: created, a review's answer, the
354
+ * mode left. The *reference* the reader scrolls back to (`plan-row` points at the
355
+ * same artifact), and the only representation of a transition — the plan body lives
356
+ * with the artifact, not in a second copy.
357
+ */
358
+ readonly 'plan-lifecycle': {
359
+ readonly durability: "durable";
360
+ readonly class: "D";
361
+ readonly destination: "transcript";
362
+ };
363
+ /**
364
+ * A message the reader submitted while a turn was running.
365
+ *
366
+ * The record of it is the `user/message` the Harness appends when the next step
367
+ * claims it — that row is the durable representation. This one is the
368
+ * acknowledgement ("accepted, applies at the next step"), which has to be seen
369
+ * the moment it happens and is worth no geometry.
370
+ */
371
+ readonly 'steer-notice': {
372
+ readonly durability: "display";
373
+ readonly class: "A";
374
+ readonly destination: "notice";
375
+ };
376
+ readonly 'error-surface': {
377
+ readonly durability: "display";
378
+ readonly class: "A";
379
+ readonly destination: "notice";
380
+ };
381
+ /** Line mode's echo of a Screen / Surface — there is no frame there to show it. */
382
+ readonly 'report-echo': {
383
+ readonly durability: "display";
384
+ readonly class: "A";
385
+ readonly destination: "transcript";
386
+ };
387
+ readonly 'surface-echo': {
388
+ readonly durability: "display";
389
+ readonly class: "A";
390
+ readonly destination: "transcript";
391
+ };
392
+ /**
393
+ * The workspace-changes card.
394
+ *
395
+ * The canonical `display` case (B2.3a): it *looks* like history — a card about a
396
+ * turn that happened — but the summary is not in the session log and a restarted
397
+ * Host cannot reopen it, so it must not pretend to survive a resume.
398
+ */
399
+ readonly 'changes-card': {
400
+ readonly durability: "display";
401
+ readonly class: "A";
402
+ readonly destination: "transcript";
403
+ };
404
+ /** Rows a test fixture puts in, so the suite does not have to fabricate a policy. */
405
+ readonly fixture: {
406
+ readonly durability: "display";
407
+ readonly class: "A";
408
+ readonly destination: "transcript";
409
+ };
410
+ };
411
+ /** A source id that has a policy. Its type *is* the table's keys. */
412
+ export type RepresentationSource = keyof typeof REPRESENTATION_POLICY;
413
+ /** What a row carries once it has been through the policy. */
414
+ export interface RepresentationMeta {
415
+ source: RepresentationSource;
416
+ durability: Durability;
417
+ representationClass: RepresentationClass;
418
+ /** Where this representation is shown — a transcript row, an echo or a notice. */
419
+ destination: Destination;
420
+ }
421
+ /** A row plus the policy that produced it. The only thing `pushRow` accepts. */
422
+ export interface Representation {
423
+ readonly meta: RepresentationMeta;
424
+ readonly row: Row;
425
+ }
426
+ /**
427
+ * Classify one row.
428
+ *
429
+ * The only constructor of a `Representation`: it looks the source up in the table,
430
+ * so the durability and class can never disagree with the policy, and a source with
431
+ * no entry cannot be named (the parameter's type is the table's keys).
432
+ */
433
+ export declare function represent(source: RepresentationSource, row: Row): Representation;
434
+ /** Whether a value really is a classified representation (a runtime guard for JS callers). */
435
+ export declare function isRepresentation(value: unknown): value is Representation;
436
+ export interface RepresentationAudit {
437
+ /** Rows carrying a policy, by durability. */
438
+ counts: Record<Durability, number>;
439
+ /** Rows with no `representation` at all: must be 0. */
440
+ unclassified: number;
441
+ /**
442
+ * Rows claiming `live`: must be 0 — a live projection is not a source row.
443
+ *
444
+ * Counted from the row's own metadata, so a row whose source is not even in the
445
+ * table (and is therefore also `unclassified`) still reports the live violation.
446
+ */
447
+ liveRows: number;
448
+ /** Per source: durability, class, destination and how many rows carry it. */
449
+ sources: {
450
+ source: string;
451
+ durability: Durability;
452
+ representationClass: RepresentationClass;
453
+ destination: Destination;
454
+ rows: number;
455
+ }[];
456
+ /** Rows by destination. `unknown` must be 0: every source has a route. */
457
+ destinations: Record<Destination, number>;
458
+ /** Sources with no destination — impossible by construction, and asserted anyway. */
459
+ unknownDestination: number;
460
+ /**
461
+ * Sources the policy knows, with no rows in this transcript. Named so a caller can
462
+ * see the *whole* policy rather than only the part that happened to be exercised.
463
+ */
464
+ unusedSources: string[];
465
+ }
466
+ /**
467
+ * Read a transcript's representations back.
468
+ *
469
+ * This is the audit artifact: it answers, for a real transcript, how many lines are
470
+ * history, how many are this Host's own voice, and how many nobody classified. The
471
+ * `live` and `unclassified` numbers are the two that must be zero, and the suite
472
+ * asserts exactly that rather than trusting the policy table alone.
473
+ */
474
+ export declare function auditRepresentations(rows: readonly unknown[]): RepresentationAudit;
475
+ /** The audit as text: what `scripts/representation-audit.mjs` prints. */
476
+ /**
477
+ * One semantic question, one primary representation — counted from real rows
478
+ * (B2.4).
479
+ *
480
+ * The success criterion of the ask/approval round is a *count*, so it is audited
481
+ * like one: an ordinary `ask_user_question` call is represented by its question
482
+ * card, and a generic tool row for the same call would be a second primary for one
483
+ * semantic event. The tool's name is not read off the row's title but off `name`,
484
+ * because that is the field the policy suppresses.
485
+ * @param rows - the transcript rows to audit.
486
+ * @returns the counts, with the call ids that broke the invariant.
487
+ */
488
+ export declare function auditQuestionPrimaries(rows: readonly {
489
+ kind?: string;
490
+ callId?: string;
491
+ name?: string;
492
+ }[]): {
493
+ questionRows: number;
494
+ genericToolRows: number;
495
+ calls: number;
496
+ duplicateCalls: string[];
497
+ };
498
+ export declare function formatRepresentationAudit(audit: RepresentationAudit): string;