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.
- package/CHANGELOG.md +150 -0
- package/README.en.md +74 -8
- package/README.md +451 -577
- package/docs/display-mode.md +122 -0
- package/docs/release-notes-0.7.4.md +91 -0
- package/docs/release-notes-0.8.0.md +56 -0
- package/docs/release-notes-0.8.1.md +79 -0
- package/docs/release-notes-0.8.2-rc.1.md +159 -0
- package/docs/remote-ops.md +51 -0
- package/docs/terminals.md +53 -0
- package/docs/windows.md +13 -0
- package/lib/approval-state.js +148 -0
- package/lib/approval-state.js.map +1 -0
- package/lib/attach.js +4 -4
- package/lib/attach.js.map +1 -1
- package/lib/auth-failure.js +74 -0
- package/lib/auth-failure.js.map +1 -1
- package/lib/color-depth.js +26 -0
- package/lib/color-depth.js.map +1 -1
- package/lib/commands.js +2 -0
- package/lib/commands.js.map +1 -1
- package/lib/copy-text.js +30 -8
- package/lib/copy-text.js.map +1 -1
- package/lib/dialogs.js +124 -0
- package/lib/dialogs.js.map +1 -1
- package/lib/display-mode.js +147 -0
- package/lib/display-mode.js.map +1 -0
- package/lib/display-sock.js +490 -35
- package/lib/display-sock.js.map +1 -1
- package/lib/footer-accents.js +28 -0
- package/lib/footer-accents.js.map +1 -0
- package/lib/footer-budget.js +328 -0
- package/lib/footer-budget.js.map +1 -0
- package/lib/footer-format.js +433 -0
- package/lib/footer-format.js.map +1 -0
- package/lib/footer-layout.js +407 -0
- package/lib/footer-layout.js.map +1 -0
- package/lib/footer-meters.js +185 -0
- package/lib/footer-meters.js.map +1 -0
- package/lib/footer.js +19 -561
- package/lib/footer.js.map +1 -1
- package/lib/glyph-measure.js +92 -0
- package/lib/glyph-measure.js.map +1 -0
- package/lib/i18n/en.js +83 -7
- package/lib/i18n/en.js.map +1 -1
- package/lib/i18n/zh.js +85 -7
- package/lib/i18n/zh.js.map +1 -1
- package/lib/index.js +73 -5
- package/lib/index.js.map +1 -1
- package/lib/paint.js +75 -17
- package/lib/paint.js.map +1 -1
- package/lib/picker.js +14 -13
- package/lib/picker.js.map +1 -1
- package/lib/plan-projection.js +291 -0
- package/lib/plan-projection.js.map +1 -0
- package/lib/plan.js +61 -11
- package/lib/plan.js.map +1 -1
- package/lib/platform.js +96 -0
- package/lib/platform.js.map +1 -1
- package/lib/question-state.js +83 -0
- package/lib/question-state.js.map +1 -0
- package/lib/representation.js +283 -0
- package/lib/representation.js.map +1 -0
- package/lib/screen.js +105 -0
- package/lib/screen.js.map +1 -0
- package/lib/selection.js +5 -3
- package/lib/selection.js.map +1 -1
- package/lib/session-blank.js +81 -0
- package/lib/session-blank.js.map +1 -0
- package/lib/session-list.js +102 -81
- package/lib/session-list.js.map +1 -1
- package/lib/startup.js +56 -0
- package/lib/startup.js.map +1 -1
- package/lib/stats.js +101 -3
- package/lib/stats.js.map +1 -1
- package/lib/subagent-model.js +8 -7
- package/lib/subagent-model.js.map +1 -1
- package/lib/term-text.js +436 -69
- package/lib/term-text.js.map +1 -1
- package/lib/terminal-input.js +132 -10
- package/lib/terminal-input.js.map +1 -1
- package/lib/theme.js +335 -0
- package/lib/theme.js.map +1 -0
- package/lib/throughput.js +305 -0
- package/lib/throughput.js.map +1 -0
- package/lib/tool-present.js +67 -15
- package/lib/tool-present.js.map +1 -1
- package/lib/tui.js +4186 -1133
- package/lib/tui.js.map +1 -1
- package/lib/types/approval-state.d.ts +75 -0
- package/lib/types/attach.d.ts +6 -2
- package/lib/types/auth-failure.d.ts +55 -0
- package/lib/types/color-depth.d.ts +21 -0
- package/lib/types/commands.d.ts +6 -0
- package/lib/types/copy-text.d.ts +18 -1
- package/lib/types/dialogs.d.ts +147 -1
- package/lib/types/display-mode.d.ts +99 -0
- package/lib/types/display-sock.d.ts +90 -0
- package/lib/types/footer-accents.d.ts +22 -0
- package/lib/types/footer-budget.d.ts +204 -0
- package/lib/types/footer-format.d.ts +221 -0
- package/lib/types/footer-layout.d.ts +292 -0
- package/lib/types/footer-meters.d.ts +151 -0
- package/lib/types/footer.d.ts +19 -269
- package/lib/types/glyph-measure.d.ts +41 -0
- package/lib/types/index.d.ts +13 -0
- package/lib/types/paint.d.ts +48 -0
- package/lib/types/plan-projection.d.ts +162 -0
- package/lib/types/plan.d.ts +18 -2
- package/lib/types/platform.d.ts +83 -0
- package/lib/types/question-state.d.ts +96 -0
- package/lib/types/representation.d.ts +498 -0
- package/lib/types/screen.d.ts +126 -0
- package/lib/types/session-blank.d.ts +51 -0
- package/lib/types/session-list.d.ts +34 -0
- package/lib/types/startup.d.ts +30 -0
- package/lib/types/stats.d.ts +64 -1
- package/lib/types/subagent-model.d.ts +7 -6
- package/lib/types/term-text.d.ts +100 -24
- package/lib/types/terminal-input.d.ts +37 -0
- package/lib/types/theme.d.ts +109 -0
- package/lib/types/throughput.d.ts +163 -0
- package/lib/types/tool-present.d.ts +43 -2
- package/lib/types/transcript-types.d.ts +63 -2
- package/lib/types/tui.d.ts +960 -12
- package/package.json +73 -67
package/lib/types/platform.d.ts
CHANGED
|
@@ -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;
|